public inbox for [email protected]
help / color / mirror / Atom feed[PATCH v12 2/5] match a77315fdf2a197a925e670be2d8b376c4ac02efc
194+ messages / 25 participants
[nested] [flat]
* [PATCH v12 2/5] match a77315fdf2a197a925e670be2d8b376c4ac02efc
@ 2020-03-05 15:04 Alvaro Herrera <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Alvaro Herrera @ 2020-03-05 15:04 UTC (permalink / raw)
---
src/backend/utils/adt/multirangetypes.c | 61 ++++++++++++-------------
src/backend/utils/adt/rangetypes.c | 2 +
src/include/utils/typcache.h | 2 +-
3 files changed, 31 insertions(+), 34 deletions(-)
diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index 0c9afd5448..4d45aa581b 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -40,10 +40,9 @@
typedef struct MultirangeIOData
{
TypeCacheEntry *typcache; /* multirange type's typcache entry */
- Oid typiofunc; /* range type's I/O function */
+ FmgrInfo typioproc; /* range type's I/O proc */
Oid typioparam; /* range type's I/O parameter */
- FmgrInfo proc; /* lookup result for typiofunc */
-} MultirangeIOData;
+} MultirangeIOData;
typedef enum
{
@@ -54,10 +53,10 @@ typedef enum
MULTIRANGE_IN_RANGE_QUOTED_ESCAPED,
MULTIRANGE_AFTER_RANGE,
MULTIRANGE_FINISHED,
-} MultirangeParseState;
+} MultirangeParseState;
-static MultirangeIOData * get_multirange_io_data(FunctionCallInfo fcinfo, Oid rngtypid,
- IOFuncSelector func);
+static MultirangeIOData *get_multirange_io_data(FunctionCallInfo fcinfo, Oid rngtypid,
+ IOFuncSelector func);
static int32 multirange_canonicalize(TypeCacheEntry *rangetyp, int32 input_range_count,
RangeType **ranges);
@@ -178,8 +177,10 @@ multirange_in(PG_FUNCTION_ARGS)
repalloc(ranges, range_capacity * sizeof(RangeType *));
}
ranges_seen++;
- range = DatumGetRangeTypeP(InputFunctionCall(&cache->proc, range_str_copy,
- cache->typioparam, typmod));
+ range = DatumGetRangeTypeP(InputFunctionCall(&cache->typioproc,
+ range_str_copy,
+ cache->typioparam,
+ typmod));
if (!RangeIsEmpty(range))
ranges[range_count++] = range;
parse_state = MULTIRANGE_AFTER_RANGE;
@@ -266,7 +267,7 @@ multirange_out(PG_FUNCTION_ARGS)
if (range_count > 0)
appendStringInfoChar(&buf, ',');
range = (RangeType *) ptr;
- rangeStr = OutputFunctionCall(&cache->proc, RangeTypePGetDatum(range));
+ rangeStr = OutputFunctionCall(&cache->typioproc, RangeTypePGetDatum(range));
appendStringInfoString(&buf, rangeStr);
ptr += MAXALIGN(VARSIZE(range));
range_count++;
@@ -311,8 +312,7 @@ multirange_recv(PG_FUNCTION_ARGS)
initStringInfo(&range_buf);
appendBinaryStringInfo(&range_buf, range_data, range_len);
- ranges[i] = DatumGetRangeTypeP(
- ReceiveFunctionCall(&cache->proc,
+ ranges[i] = DatumGetRangeTypeP(ReceiveFunctionCall(&cache->typioproc,
&range_buf,
cache->typioparam,
typmod));
@@ -346,16 +346,13 @@ multirange_send(PG_FUNCTION_ARGS)
multirange_deserialize(multirange, &range_count, &ranges);
for (i = 0; i < range_count; i++)
{
- Datum range = RangeTypePGetDatum(ranges[i]);
- uint32 range_len;
- char *range_data;
+ Datum range;
- range = PointerGetDatum(SendFunctionCall(&cache->proc, range));
- range_len = VARSIZE(range) - VARHDRSZ;
- range_data = VARDATA(range);
+ range = RangeTypePGetDatum(ranges[i]);
+ range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
- pq_sendint32(buf, range_len);
- pq_sendbytes(buf, range_data, range_len);
+ pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
+ pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
}
PG_RETURN_BYTEA_P(pq_endtypsend(buf));
@@ -381,6 +378,7 @@ get_multirange_io_data(FunctionCallInfo fcinfo, Oid rngtypid, IOFuncSelector fun
if (cache == NULL || cache->typcache->type_id != rngtypid)
{
+ Oid typiofunc;
int16 typlen;
bool typbyval;
char typalign;
@@ -400,9 +398,9 @@ get_multirange_io_data(FunctionCallInfo fcinfo, Oid rngtypid, IOFuncSelector fun
&typalign,
&typdelim,
&cache->typioparam,
- &cache->typiofunc);
+ &typiofunc);
- if (!OidIsValid(cache->typiofunc))
+ if (!OidIsValid(typiofunc))
{
/* this could only happen for receive or send */
if (func == IOFunc_receive)
@@ -416,7 +414,7 @@ get_multirange_io_data(FunctionCallInfo fcinfo, Oid rngtypid, IOFuncSelector fun
errmsg("no binary output function available for type %s",
format_type_be(cache->typcache->rngtype->type_id))));
}
- fmgr_info_cxt(cache->typiofunc, &cache->proc,
+ fmgr_info_cxt(typiofunc, &cache->typioproc,
fcinfo->flinfo->fn_mcxt);
fcinfo->flinfo->fn_extra = (void *) cache;
@@ -473,7 +471,6 @@ make_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp, int32 range_count,
RangeType **ranges)
{
MultirangeType *multirange;
- RangeType *range;
int i;
int32 bytelen;
Pointer ptr;
@@ -489,10 +486,7 @@ make_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp, int32 range_count,
/* Count space for all ranges */
for (i = 0; i < range_count; i++)
- {
- range = ranges[i];
- bytelen += MAXALIGN(VARSIZE(range));
- }
+ bytelen += MAXALIGN(VARSIZE(ranges[i]));
/* Note: zero-fill is required here, just as in heap tuples */
multirange = palloc0(bytelen);
@@ -505,19 +499,20 @@ make_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp, int32 range_count,
ptr = (char *) MAXALIGN(multirange + 1);
for (i = 0; i < range_count; i++)
{
- range = ranges[i];
- memcpy(ptr, range, VARSIZE(range));
- ptr += MAXALIGN(VARSIZE(range));
+ memcpy(ptr, ranges[i], VARSIZE(ranges[i]));
+ ptr += MAXALIGN(VARSIZE(ranges[i]));
}
return multirange;
}
/*
- * Converts a list of any ranges you like into a list that is sorted and merged.
+ * Converts a list of arbitrary ranges into a list that is sorted and merged.
* Changes the contents of `ranges`.
- * Returns the number of slots actually used,
- * which may be less than input_range_count but never more.
+ *
+ * Returns the number of slots actually used, which may be less than
+ * input_range_count but never more.
+ *
* We assume that no input ranges are null, but empties are okay.
*/
static int32
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 9d1ca13e32..7077bb0309 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -2100,6 +2100,8 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
}
/*
+ * qsort callback for sorting ranges.
+ *
* Compares two ranges so we can qsort them.
* This expects that you give qsort a RangeType **,
* so the RangeTypes can be in diverse locations,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index f40a462b22..33f332a141 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -103,7 +103,7 @@ typedef struct TypeCacheEntry
/*
* Fields computed when TYPCACHE_MULTIRANGE_INFO is required.
*/
- struct TypeCacheEntry *rngtype; /* underlying range type */
+ struct TypeCacheEntry *rngtype; /* multirange's range underlying type */
/*
* Domain's base type and typmod if it's a domain type. Zeroes if not
--
2.20.1
--fdj2RfSjLxBAspz7
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v12-0003-whitespace-varlena-struct-change-comment-on-qsor.patch"
^ permalink raw reply [nested|flat] 194+ messages in thread
* [PATCH 4/8] BRIN bloom indexes
@ 2020-04-02 00:57 Tomas Vondra <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Tomas Vondra @ 2020-04-02 00:57 UTC (permalink / raw)
---
doc/src/sgml/brin.sgml | 215 +++++
doc/src/sgml/ref/create_index.sgml | 31 +
src/backend/access/brin/Makefile | 1 +
src/backend/access/brin/brin_bloom.c | 980 +++++++++++++++++++++++
src/include/access/brin.h | 2 +
src/include/access/brin_internal.h | 4 +
src/include/catalog/pg_amop.dat | 165 ++++
src/include/catalog/pg_amproc.dat | 430 ++++++++++
src/include/catalog/pg_opclass.dat | 69 ++
src/include/catalog/pg_opfamily.dat | 36 +
src/include/catalog/pg_proc.dat | 20 +
src/test/regress/expected/brin_bloom.out | 456 +++++++++++
src/test/regress/expected/opr_sanity.out | 3 +-
src/test/regress/expected/psql.out | 3 +-
src/test/regress/parallel_schedule | 5 +
src/test/regress/serial_schedule | 1 +
src/test/regress/sql/brin_bloom.sql | 404 ++++++++++
17 files changed, 2823 insertions(+), 2 deletions(-)
create mode 100644 src/backend/access/brin/brin_bloom.c
create mode 100644 src/test/regress/expected/brin_bloom.out
create mode 100644 src/test/regress/sql/brin_bloom.sql
diff --git a/doc/src/sgml/brin.sgml b/doc/src/sgml/brin.sgml
index 55b6272db6..e10fc64d6b 100644
--- a/doc/src/sgml/brin.sgml
+++ b/doc/src/sgml/brin.sgml
@@ -132,6 +132,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
</row>
</thead>
<tbody>
+ <row>
+ <entry><literal>int8_bloom_ops</literal></entry>
+ <entry><type>bigint</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>int8_minmax_ops</literal></entry>
<entry><type>bigint</type></entry>
@@ -183,6 +190,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>|&></literal>
</entry>
</row>
+ <row>
+ <entry><literal>bytea_bloom_ops</literal></entry>
+ <entry><type>bytea</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>bytea_minmax_ops</literal></entry>
<entry><type>bytea</type></entry>
@@ -194,6 +208,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>></literal>
</entry>
</row>
+ <row>
+ <entry><literal>bpchar_bloom_ops</literal></entry>
+ <entry><type>character</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>bpchar_minmax_ops</literal></entry>
<entry><type>character</type></entry>
@@ -205,6 +226,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>></literal>
</entry>
</row>
+ <row>
+ <entry><literal>char_bloom_ops</literal></entry>
+ <entry><type>"char"</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>char_minmax_ops</literal></entry>
<entry><type>"char"</type></entry>
@@ -216,6 +244,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>></literal>
</entry>
</row>
+ <row>
+ <entry><literal>date_bloom_ops</literal></entry>
+ <entry><type>date</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>date_minmax_ops</literal></entry>
<entry><type>date</type></entry>
@@ -227,6 +262,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>></literal>
</entry>
</row>
+ <row>
+ <entry><literal>float8_bloom_ops</literal></entry>
+ <entry><type>double precision</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>float8_minmax_ops</literal></entry>
<entry><type>double precision</type></entry>
@@ -238,6 +280,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>></literal>
</entry>
</row>
+ <row>
+ <entry><literal>inet_bloom_ops</literal></entry>
+ <entry><type>inet</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>inet_minmax_ops</literal></entry>
<entry><type>inet</type></entry>
@@ -261,6 +310,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal><<</literal>
</entry>
</row>
+ <row>
+ <entry><literal>int4_bloom_ops</literal></entry>
+ <entry><type>integer</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>int4_minmax_ops</literal></entry>
<entry><type>integer</type></entry>
@@ -272,6 +328,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>></literal>
</entry>
</row>
+ <row>
+ <entry><literal>interval_bloom_ops</literal></entry>
+ <entry><type>interval</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>interval_minmax_ops</literal></entry>
<entry><type>interval</type></entry>
@@ -283,6 +346,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>></literal>
</entry>
</row>
+ <row>
+ <entry><literal>macaddr_bloom_ops</literal></entry>
+ <entry><type>macaddr</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>macaddr_minmax_ops</literal></entry>
<entry><type>macaddr</type></entry>
@@ -294,6 +364,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>></literal>
</entry>
</row>
+ <row>
+ <entry><literal>macaddr8_bloom_ops</literal></entry>
+ <entry><type>macaddr8</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>macaddr8_minmax_ops</literal></entry>
<entry><type>macaddr8</type></entry>
@@ -305,6 +382,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>></literal>
</entry>
</row>
+ <row>
+ <entry><literal>name_bloom_ops</literal></entry>
+ <entry><type>name</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>name_minmax_ops</literal></entry>
<entry><type>name</type></entry>
@@ -316,6 +400,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>></literal>
</entry>
</row>
+ <row>
+ <entry><literal>numeric_bloom_ops</literal></entry>
+ <entry><type>numeric</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>numeric_minmax_ops</literal></entry>
<entry><type>numeric</type></entry>
@@ -327,6 +418,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>></literal>
</entry>
</row>
+ <row>
+ <entry><literal>pg_lsn_bloom_ops</literal></entry>
+ <entry><type>pg_lsn</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>pg_lsn_minmax_ops</literal></entry>
<entry><type>pg_lsn</type></entry>
@@ -338,6 +436,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>></literal>
</entry>
</row>
+ <row>
+ <entry><literal>oid_bloom_ops</literal></entry>
+ <entry><type>oid</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>oid_minmax_ops</literal></entry>
<entry><type>oid</type></entry>
@@ -369,6 +474,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>>=</literal>
</entry>
</row>
+ <row>
+ <entry><literal>float4_bloom_ops</literal></entry>
+ <entry><type>real</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>float4_minmax_ops</literal></entry>
<entry><type>real</type></entry>
@@ -380,6 +492,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>></literal>
</entry>
</row>
+ <row>
+ <entry><literal>int2_bloom_ops</literal></entry>
+ <entry><type>smallint</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>int2_minmax_ops</literal></entry>
<entry><type>smallint</type></entry>
@@ -391,6 +510,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>></literal>
</entry>
</row>
+ <row>
+ <entry><literal>text_bloom_ops</literal></entry>
+ <entry><type>text</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>text_minmax_ops</literal></entry>
<entry><type>text</type></entry>
@@ -413,6 +539,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>></literal>
</entry>
</row>
+ <row>
+ <entry><literal>timestamp_bloom_ops</literal></entry>
+ <entry><type>timestamp without time zone</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>timestamp_minmax_ops</literal></entry>
<entry><type>timestamp without time zone</type></entry>
@@ -424,6 +557,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>></literal>
</entry>
</row>
+ <row>
+ <entry><literal>timestamptz_bloom_ops</literal></entry>
+ <entry><type>timestamp with time zone</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>timestamptz_minmax_ops</literal></entry>
<entry><type>timestamp with time zone</type></entry>
@@ -435,6 +575,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>></literal>
</entry>
</row>
+ <row>
+ <entry><literal>time_bloom_ops</literal></entry>
+ <entry><type>time without time zone</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>time_minmax_ops</literal></entry>
<entry><type>time without time zone</type></entry>
@@ -446,6 +593,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>></literal>
</entry>
</row>
+ <row>
+ <entry><literal>timetz_bloom_ops</literal></entry>
+ <entry><type>time with time zone</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>timetz_minmax_ops</literal></entry>
<entry><type>time with time zone</type></entry>
@@ -457,6 +611,13 @@ LOG: request for BRIN range summarization for index "brin_wi_idx" page 128 was
<literal>></literal>
</entry>
</row>
+ <row>
+ <entry><literal>uuid_bloom_ops</literal></entry>
+ <entry><type>uuid</type></entry>
+ <entry>
+ <literal>=</literal>
+ </entry>
+ </row>
<row>
<entry><literal>uuid_minmax_ops</literal></entry>
<entry><type>uuid</type></entry>
@@ -841,6 +1002,60 @@ typedef struct BrinOpcInfo
function can improve index performance.
</para>
+ <para>
+ To write an operator class for a data type that implements only an equality
+ operator and supports hashing, it is possible to use the bloom support procedures
+ alongside the corresponding operators, as shown in
+ <xref linkend="brin-extensibility-bloom-table"/>.
+ All operator class members (procedures and operators) are mandatory.
+ </para>
+
+ <table id="brin-extensibility-bloom-table">
+ <title>Procedure and Support Numbers for Bloom Operator Classes</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Operator class member</entry>
+ <entry>Object</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>Support Procedure 1</entry>
+ <entry>internal function <function>brin_bloom_opcinfo()</function></entry>
+ </row>
+ <row>
+ <entry>Support Procedure 2</entry>
+ <entry>internal function <function>brin_bloom_add_value()</function></entry>
+ </row>
+ <row>
+ <entry>Support Procedure 3</entry>
+ <entry>internal function <function>brin_bloom_consistent()</function></entry>
+ </row>
+ <row>
+ <entry>Support Procedure 4</entry>
+ <entry>internal function <function>brin_bloom_union()</function></entry>
+ </row>
+ <row>
+ <entry>Support Procedure 11</entry>
+ <entry>function to compute hash of an element</entry>
+ </row>
+ <row>
+ <entry>Operator Strategy 1</entry>
+ <entry>operator equal-to</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ Support procedure numbers 1-10 are reserved for the BRIN internal
+ functions, so the SQL level functions start with number 11. Support
+ function number 11 is the main function required to build the index.
+ It should accept one argument with the same data type as the operator class,
+ and return a hash of the value.
+ </para>
+
<para>
Both minmax and inclusion operator classes support cross-data-type
operators, though with these the dependencies become more complicated.
diff --git a/doc/src/sgml/ref/create_index.sgml b/doc/src/sgml/ref/create_index.sgml
index 33aa64e81d..9c90d451a7 100644
--- a/doc/src/sgml/ref/create_index.sgml
+++ b/doc/src/sgml/ref/create_index.sgml
@@ -555,6 +555,37 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class=
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>n_distinct_per_range</literal></term>
+ <listitem>
+ <para>
+ Defines the estimated number of distinct non-null values in the block
+ range, used by <acronym>BRIN</acronym> bloom indexes for sizing of the
+ Bloom filter. It behaves similarly to <literal>n_distinct</literal> option
+ for <xref linkend="sql-altertable"/>. When set to a positive value,
+ each block range is assumed to contain this number of distinct non-null
+ values. When set to a negative value, which must be greater than or
+ equal to -1, the number of distinct non-null is assumed linear with
+ the maximum possible number of tuples in the block range (about 290
+ rows per block). The default values is <literal>-0.1</literal>, and
+ the minimum number of distinct non-null values is <literal>128</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>false_positive_rate</literal></term>
+ <listitem>
+ <para>
+ Defines the desired false positive rate used by <acronym>BRIN</acronym>
+ bloom indexes for sizing of the Bloom filter. The values must be be
+ greater than 0.0 and smaller than 1.0. The default values is 0.01,
+ which is 1% false positive rate.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect2>
diff --git a/src/backend/access/brin/Makefile b/src/backend/access/brin/Makefile
index 468e1e289a..6b56131215 100644
--- a/src/backend/access/brin/Makefile
+++ b/src/backend/access/brin/Makefile
@@ -14,6 +14,7 @@ include $(top_builddir)/src/Makefile.global
OBJS = \
brin.o \
+ brin_bloom.o \
brin_inclusion.o \
brin_minmax.o \
brin_pageops.o \
diff --git a/src/backend/access/brin/brin_bloom.c b/src/backend/access/brin/brin_bloom.c
new file mode 100644
index 0000000000..b119e4e264
--- /dev/null
+++ b/src/backend/access/brin/brin_bloom.c
@@ -0,0 +1,980 @@
+/*
+ * brin_bloom.c
+ * Implementation of Bloom opclass for BRIN
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * A BRIN opclass summarizing page range into a bloom filter.
+ *
+ * Bloom filters allow efficient test whether a given page range contains
+ * a particular value. Therefore, if we summarize each page range into a
+ * bloom filter, we can easily and cheaply test wheter it containst values
+ * we get later.
+ *
+ * The index only supports equality operator, similarly to hash indexes.
+ * BRIN bloom indexes are however much smaller, and support only bitmap
+ * scans.
+ *
+ * Note: Don't confuse this with bloom indexes, implemented in a contrib
+ * module. That extension implements an entirely new AM, building a bloom
+ * filter on multiple columns in a single row. This opclass works with an
+ * existing AM (BRIN) and builds bloom filter on a column.
+ *
+ *
+ * values vs. hashes
+ * -----------------
+ *
+ * The original column values are not used directly, but are first hashed
+ * using the regular type-specific hash function, producing a uint32 hash.
+ * And this hash value is then added to the summary - either stored as is
+ * (in the sorted mode), or hashed again and added to the bloom filter.
+ *
+ * This allows the code to treat all data types (byval/byref/...) the same
+ * way, with only minimal space requirements. For example we don't need to
+ * store varlena types differently in the sorted mode, etc. Everything is
+ * uint32 making it much simpler.
+ *
+ * Of course, this assumes the built-in hash function is reasonably good,
+ * without too many collisions etc. But that does seem to be the case, at
+ * least based on past experience. After all, the same hash functions are
+ * used for hash indexes, hash partitioning and so on.
+ *
+ *
+ * sizing the bloom filter
+ * -----------------------
+ *
+ * Size of a bloom filter depends on the number of distinct values we will
+ * store in it, and the desired false positive rate. The higher the number
+ * of distinct values and/or the lower the false positive rate, the larger
+ * the bloom filter. On the other hand, we want to keep the index as small
+ * as possible - that's one of the basic advantages of BRIN indexes.
+ *
+ * The number of distinct elements (in a page range) depends on the data,
+ * we can consider it fixed. This simplifies the trade-off to just false
+ * positive rate vs. size.
+ *
+ * At the page range level, false positive rate is a probability the bloom
+ * filter matches a random value. For the whole index (with sufficiently
+ * many page ranges) it represents the fraction of the index ranges (and
+ * thus fraction of the table to be scanned) matching the random value.
+ *
+ * Furthermore, the size of the bloom filter is subject to implementation
+ * limits - it has to fit onto a single index page (8kB by default). As
+ * the bitmap is inherently random, compression can't reliably help here.
+ * To reduce the size of a filter (to fit to a page), we have to either
+ * accept higher false positive rate (undesirable), or reduce the number
+ * of distinct items to be stored in the filter. We can't quite the input
+ * data, of course, but we may make the BRIN page ranges smaller - instead
+ * of the default 128 pages (1MB) we may build index with 16-page ranges,
+ * or something like that. This does help even for random data sets, as
+ * the number of rows per heap page is limited (to ~290 with very narrow
+ * tables, likely ~20 in practice).
+ *
+ * Of course, good sizing decisions depend on having the necessary data,
+ * i.e. number of distinct values in a page range (of a given size) and
+ * table size (to estimate cost change due to change in false positive
+ * rate due to having larger index vs. scanning larger indexes). We may
+ * not have that data - for example when building an index on empty table
+ * it's not really possible. And for some data we only have estimates for
+ * the whole table and we can only estimate per-range values (ndistinct).
+ *
+ * Another challenge is that while the bloom filter is per-column, it's
+ * the whole index tuple that has to fit into a page. And for multi-column
+ * indexes that may include pieces we have no control over (not necessarily
+ * bloom filters, the other columns may use other BRIN opclasses). So it's
+ * not entirely clear how to distrubute the space between those columns.
+ *
+ * The current logic, implemented in brin_bloom_get_ndistinct, attempts to
+ * make some basic sizing decisions, based on the table ndistinct estimate.
+ *
+ *
+ * sort vs. hash
+ * -------------
+ *
+ * As explained in the preceding section, sizing bloom filters correctly is
+ * difficult in practice. It's also true that bloom filters quickly degrade
+ * after exceeding the expected number of distinct items. It's therefore
+ * expected that people will overshoot the parameters a bit (particularly
+ * the ndistinct per page range), perhaps by a factor of 2 or more.
+ *
+ * It's also possible the data set is not uniform - it may have ranges with
+ * very many distinct items per range, but also ranges with only very few
+ * distinct items. The bloom filter has to be sized for the more variable
+ * ranges, making it rather wasteful in the less variable part.
+ *
+ * For example, if some page ranges have 1000 distinct values, that means
+ * about ~1.2kB bloom filter with 1% false positive rate. For page ranges
+ * that only contain 10 distinct values, that's wasteful, because we might
+ * store the values (the uint32 hashes) in just 40B.
+ *
+ * To address these issues, the opclass stores the raw values directly, and
+ * only switches to the actual bloom filter after reaching the same space
+ * requirements.
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/brin/brin_bloom.c
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/brin.h"
+#include "access/brin_internal.h"
+#include "access/brin_tuple.h"
+#include "access/hash.h"
+#include "access/htup_details.h"
+#include "access/reloptions.h"
+#include "access/stratnum.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_amop.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+#include <math.h>
+
+#define BloomEqualStrategyNumber 1
+
+/*
+ * Additional SQL level support functions
+ *
+ * Procedure numbers must not use values reserved for BRIN itself; see
+ * brin_internal.h.
+ */
+#define BLOOM_MAX_PROCNUMS 1 /* maximum support procs we need */
+#define PROCNUM_HASH 11 /* required */
+
+/*
+ * Subtract this from procnum to obtain index in BloomOpaque arrays
+ * (Must be equal to minimum of private procnums).
+ */
+#define PROCNUM_BASE 11
+
+/*
+ * Flags. We only use a single bit for now, to decide whether we're in the
+ * sorted or hash phase. So we have 15 bits for future use, if needed. The
+ * filters are expected to be hundreds of bytes, so this is negligible.
+ */
+#define BLOOM_FLAG_PHASE_HASH 0x0001
+
+#define BLOOM_IS_HASHED(f) ((f)->flags & BLOOM_FLAG_PHASE_HASH)
+#define BLOOM_IS_SORTED(f) (!BLOOM_IS_HASHED(f))
+
+/*
+ * Number of hashes to accumulate before deduplicating and sorting in the
+ * sort phase? We want this fairly small to reduce the amount of space and
+ * also speed-up bsearch lookups.
+ */
+#define BLOOM_MAX_UNSORTED 32
+
+/*
+ * Storage type for BRIN's reloptions.
+ */
+typedef struct BloomOptions
+{
+ int32 vl_len_; /* varlena header (do not touch directly!) */
+ double nDistinctPerRange; /* number of distinct values per range */
+ double falsePositiveRate; /* false positive for bloom filter */
+} BloomOptions;
+
+/*
+ * The current min value (16) is somewhat arbitrary, but it's based
+ * on the fact that the filter header is ~20B alone, which is about
+ * the same as the filter bitmap for 16 distinct items with 1% false
+ * positive rate. So by allowing lower values we'd not gain much. In
+ * any case, the min should not be larger than MaxHeapTuplesPerPage
+ * (~290), which is the theoretical maximum for single-page ranges.
+ */
+#define BLOOM_MIN_NDISTINCT_PER_RANGE 16
+
+/*
+ * Used to determine number of distinct items, based on the number of rows
+ * in a page range. The 10% is somewhat similar to what estimate_num_groups
+ * does, so we use the same factor here.
+ */
+#define BLOOM_DEFAULT_NDISTINCT_PER_RANGE -0.1 /* 10% of values */
+
+/*
+ * The 1% value is mostly arbitrary, it just looks nice.
+ */
+#define BLOOM_DEFAULT_FALSE_POSITIVE_RATE 0.01 /* 1% fp rate */
+
+#define BloomGetNDistinctPerRange(opts) \
+ ((opts) && (((BloomOptions *) (opts))->nDistinctPerRange != 0) ? \
+ (((BloomOptions *) (opts))->nDistinctPerRange) : \
+ BLOOM_DEFAULT_NDISTINCT_PER_RANGE)
+
+#define BloomGetFalsePositiveRate(opts) \
+ ((opts) && (((BloomOptions *) (opts))->falsePositiveRate != 0.0) ? \
+ (((BloomOptions *) (opts))->falsePositiveRate) : \
+ BLOOM_DEFAULT_FALSE_POSITIVE_RATE)
+
+/*
+ * Bloom Filter
+ *
+ * Represents a bloom filter, built on hashes of the indexed values. That is,
+ * we compute a uint32 hash of the value, and then store this hash into the
+ * bloom filter (and compute additional hashes on it).
+ *
+ * We use an optimisation that initially we store the uint32 values directly,
+ * without the extra hashing step. And only later filling the bitmap space,
+ * we switch to the regular bloom filter mode.
+ *
+ * PHASE_SORTED
+ *
+ * Initially we copy the uint32 hash into the bitmap, regularly sorting the
+ * hash values for fast lookup (we keep at most BLOOM_MAX_UNSORTED unsorted
+ * values).
+ *
+ * The idea is that if we only see very few distinct values, we can store
+ * them in less space compared to the (sparse) bloom filter bitmap. It also
+ * stores them exactly, although that's not a big advantage as almost-empty
+ * bloom filter has false positive rate close to zero anyway.
+ *
+ * PHASE_HASH
+ *
+ * Once we fill the bitmap space in the sorted phase, we switch to the hash
+ * phase, where we actually use the bloom filter. We treat the uint32 hashes
+ * as input values, and hash them again with different seeds (to get the k
+ * hash functions needed for bloom filter).
+ *
+ *
+ * XXX Perhaps we could save a few bytes by using different data types, but
+ * considering the size of the bitmap, the difference is negligible.
+ *
+ * XXX We could also implement "sparse" bloom filters, keeping only the
+ * bytes that are not entirely 0. That might make the "sorted" phase
+ * mostly unnecessary.
+ *
+ * XXX We can also watch the number of bits set in the bloom filter, and
+ * then stop using it (and not store the bitmap, to save space) when the
+ * false positive rate gets too high.
+ */
+typedef struct BloomFilter
+{
+ /* varlena header (do not touch directly!) */
+ int32 vl_len_;
+
+ /* space for various flags (phase, etc.) */
+ uint16 flags;
+
+ /* fields used only in the SORTED phase */
+ uint16 nvalues; /* number of hashes stored (total) */
+ uint16 nsorted; /* number of hashes in the sorted part */
+
+ /* fields for the HASHED phase */
+ uint8 nhashes; /* number of hash functions */
+ uint32 nbits; /* number of bits in the bitmap (size) */
+ uint32 nbits_set; /* number of bits set to 1 */
+
+ /* data of the bloom filter (used both for sorted and hashed phase) */
+ char data[FLEXIBLE_ARRAY_MEMBER];
+
+} BloomFilter;
+
+static BloomFilter *bloom_switch_to_hashing(BloomFilter *filter);
+
+
+/*
+ * bloom_init
+ * Initialize the Bloom Filter, allocate all the memory.
+ *
+ * The filter is initialized with optimal size for ndistinct expected
+ * values, and requested false positive rate. The filter is stored as
+ * varlena.
+ */
+static BloomFilter *
+bloom_init(int ndistinct, double false_positive_rate)
+{
+ Size len;
+ BloomFilter *filter;
+
+ int m; /* number of bits */
+ double k; /* number of hash functions */
+
+ Assert(ndistinct > 0);
+ Assert((false_positive_rate > 0) && (false_positive_rate < 1.0));
+
+ m = ceil((ndistinct * log(false_positive_rate)) / log(1.0 / (pow(2.0, log(2.0)))));
+
+ /* round m to whole bytes */
+ m = ((m + 7) / 8) * 8;
+
+ /*
+ * round(log(2.0) * m / ndistinct), but assume round() may not be
+ * available on Windows
+ */
+ k = log(2.0) * m / ndistinct;
+ k = (k - floor(k) >= 0.5) ? ceil(k) : floor(k);
+
+ /*
+ * Allocate the bloom filter with a minimum size 64B (about 40B in the
+ * bitmap part). We require space at least for the header.
+ *
+ * XXX Maybe the 64B min size is not really needed?
+ */
+ len = Max(offsetof(BloomFilter, data), 64);
+
+ filter = (BloomFilter *) palloc0(len);
+
+ filter->flags = 0; /* implies SORTED phase */
+ filter->nhashes = (int) k;
+ filter->nbits = m;
+
+ SET_VARSIZE(filter, len);
+
+ return filter;
+}
+
+/* simple uint32 comparator, for pg_qsort and bsearch */
+static int
+cmp_uint32(const void *a, const void *b)
+{
+ uint32 *ia = (uint32 *) a;
+ uint32 *ib = (uint32 *) b;
+
+ if (*ia == *ib)
+ return 0;
+ else if (*ia < *ib)
+ return -1;
+ else
+ return 1;
+}
+
+/*
+ * bloom_compact
+ * Compact the filter during the 'sorted' phase.
+ *
+ * We sort the uint32 hashes and remove duplicates, for two main reasons.
+ * Firstly, to keep most of the data sorted for bsearch lookups. Secondly,
+ * we try to save space by removing the duplicates, allowing us to stay
+ * in the sorted phase a bit longer.
+ *
+ * We currently don't repalloc the bitmap, i.e. we don't free the memory
+ * here - in the worst case we waste space for up to 32 unsorted hashes
+ * (if all of them are already in the sorted part), so about 128B. We can
+ * either reduce the number of unsorted items (e.g. to 8 hashes, which
+ * would mean 32B), or start doing the repalloc.
+ *
+ * We do however set the varlena length, to minimize the storage needs.
+ */
+static void
+bloom_compact(BloomFilter *filter)
+{
+ int i,
+ nvalues;
+ Size len;
+ uint32 *values;
+
+ /* never call compact on filters in HASH phase */
+ Assert(BLOOM_IS_SORTED(filter));
+
+ /* no chance to compact anything */
+ if (filter->nvalues == filter->nsorted)
+ return;
+
+ values = (uint32 *) filter->data;
+
+ /* TODO optimization: sort only the unsorted part, then merge */
+ pg_qsort(values, filter->nvalues, sizeof(uint32), cmp_uint32);
+
+ nvalues = 1;
+ for (i = 1; i < filter->nvalues; i++)
+ {
+ /* if different from the last value, keep it */
+ if (values[i] != values[nvalues - 1])
+ values[nvalues++] = values[i];
+ }
+
+ filter->nvalues = nvalues;
+ filter->nsorted = nvalues;
+
+ len = offsetof(BloomFilter, data) +
+ (filter->nvalues) * sizeof(uint32);
+
+ SET_VARSIZE(filter, len);
+}
+
+/*
+ * bloom_add_value
+ * Add value to the bloom filter.
+ */
+static BloomFilter *
+bloom_add_value(BloomFilter *filter, uint32 value, bool *updated)
+{
+ int i;
+ uint32 big_h, h, d;
+
+ /* assume 'not updated' by default */
+ Assert(filter);
+
+ /* if we're in the sorted phase, we store the hashes directly */
+ if (BLOOM_IS_SORTED(filter))
+ {
+ /* how many uint32 hashes can we fit into the bitmap */
+ int maxvalues = filter->nbits / (8 * sizeof(uint32));
+
+ /* do not overflow the bitmap space or number of unsorted items */
+ Assert(filter->nvalues <= maxvalues);
+ Assert(filter->nvalues - filter->nsorted <= BLOOM_MAX_UNSORTED);
+
+ /*
+ * In this branch we always update the filter - we either add the
+ * hash to the unsorted part, or switch the filter to hashing.
+ */
+ if (updated)
+ *updated = true;
+
+ /*
+ * If the array is full, or if we reached the limit on unsorted
+ * items, try to compact the filter first, before attempting to
+ * add the new value.
+ */
+ if ((filter->nvalues == maxvalues) ||
+ (filter->nvalues - filter->nsorted == BLOOM_MAX_UNSORTED))
+ bloom_compact(filter);
+
+ /*
+ * Can we squeeze one more uint32 hash into the bitmap? Also make
+ * sure there's enough space in the bytea value first.
+ */
+ if (filter->nvalues < maxvalues)
+ {
+ Size len = VARSIZE_ANY(filter);
+ Size need = offsetof(BloomFilter, data) +
+ (filter->nvalues + 1) * sizeof(uint32);
+
+ /*
+ * We don't double the size here, as in the first place we care about
+ * reducing storage requirements, and the doubling happens automatically
+ * in memory contexts (so the repalloc should be cheap in most cases).
+ */
+ if (len < need)
+ {
+ filter = (BloomFilter *) repalloc(filter, need);
+ SET_VARSIZE(filter, need);
+ }
+
+ /* copy the new value into the filter */
+ memcpy(&filter->data[filter->nvalues * sizeof(uint32)],
+ &value, sizeof(uint32));
+
+ filter->nvalues++;
+
+ /* we're done */
+ return filter;
+ }
+
+ /* can't add any more exact hashes, so switch to hashing */
+ filter = bloom_switch_to_hashing(filter);
+ }
+
+ /* we better be in the hashing phase */
+ Assert(BLOOM_IS_HASHED(filter));
+
+ /* compute the hashes, used for the bloom filter */
+ big_h = ((uint32) DatumGetInt64(hash_uint32(value)));
+
+ h = big_h % filter->nbits;
+ d = big_h % (filter->nbits - 1);
+
+ /* compute the requested number of hashes */
+ for (i = 0; i < filter->nhashes; i++)
+ {
+ int byte = (h / 8);
+ int bit = (h % 8);
+
+ /* if the bit is not set, set it and remember we did that */
+ if (! (filter->data[byte] & (0x01 << bit)))
+ {
+ filter->data[byte] |= (0x01 << bit);
+ filter->nbits_set++;
+ if (updated)
+ *updated = true;
+ }
+
+ /* next bit */
+ h += d++;
+ if (h >= filter->nbits)
+ h -= filter->nbits;
+
+ if (d == filter->nbits)
+ d = 0;
+ }
+
+ return filter;
+}
+
+/*
+ * bloom_switch_to_hashing
+ * Switch the bloom filter from sorted to hashing mode.
+ */
+static BloomFilter *
+bloom_switch_to_hashing(BloomFilter *filter)
+{
+ int i;
+ uint32 *values;
+ Size len;
+ BloomFilter *newfilter;
+
+ Assert(filter->nbits % 8 == 0);
+
+ /*
+ * The new filter is allocated with all the memory, directly into
+ * the HASH phase.
+ */
+ len = offsetof(BloomFilter, data) + (filter->nbits / 8);
+
+ newfilter = (BloomFilter *) palloc0(len);
+
+ newfilter->nhashes = filter->nhashes;
+ newfilter->nbits = filter->nbits;
+ newfilter->flags |= BLOOM_FLAG_PHASE_HASH;
+
+ SET_VARSIZE(newfilter, len);
+
+ values = (uint32 *) filter->data;
+
+ for (i = 0; i < filter->nvalues; i++)
+ /* ignore the return value here, re don't repalloc in hashing mode */
+ bloom_add_value(newfilter, values[i], NULL);
+
+ /* free the original filter, return the newly allocated one */
+ pfree(filter);
+
+ return newfilter;
+}
+
+/*
+ * bloom_contains_value
+ * Check if the bloom filter contains a particular value.
+ */
+static bool
+bloom_contains_value(BloomFilter *filter, uint32 value)
+{
+ int i;
+ uint32 big_h, h, d;
+
+ Assert(filter);
+
+ /* in sorted mode we simply search the two arrays (sorted, unsorted) */
+ if (BLOOM_IS_SORTED(filter))
+ {
+ int i;
+ uint32 *values = (uint32 *) filter->data;
+
+ /* first search through the sorted part */
+ if ((filter->nsorted > 0) &&
+ (bsearch(&value, values, filter->nsorted, sizeof(uint32), cmp_uint32) != NULL))
+ return true;
+
+ /* now search through the unsorted part - linear search */
+ for (i = filter->nsorted; i < filter->nvalues; i++)
+ {
+ if (value == values[i])
+ return true;
+ }
+
+ /* nothing found */
+ return false;
+ }
+
+ /* now the regular hashing mode */
+ Assert(BLOOM_IS_HASHED(filter));
+
+ big_h = ((uint32) DatumGetInt64(hash_uint32(value)));
+
+ h = big_h % filter->nbits;
+ d = big_h % (filter->nbits - 1);
+
+ /* compute the requested number of hashes */
+ for (i = 0; i < filter->nhashes; i++)
+ {
+ int byte = (h / 8);
+ int bit = (h % 8);
+
+ /* if the bit is not set, the value is not there */
+ if (! (filter->data[byte] & (0x01 << bit)))
+ return false;
+
+ /* next bit */
+ h += d++;
+ if (h >= filter->nbits)
+ h -= filter->nbits;
+
+ if (d == filter->nbits)
+ d = 0;
+ }
+
+ /* all hashes found in bloom filter */
+ return true;
+}
+
+typedef struct BloomOpaque
+{
+ /*
+ * XXX At this point we only need a single proc (to compute the hash),
+ * but let's keep the array just like inclusion and minman opclasses,
+ * for consistency. We may need additional procs in the future.
+ */
+ FmgrInfo extra_procinfos[BLOOM_MAX_PROCNUMS];
+ bool extra_proc_missing[BLOOM_MAX_PROCNUMS];
+} BloomOpaque;
+
+static FmgrInfo *bloom_get_procinfo(BrinDesc *bdesc, uint16 attno,
+ uint16 procnum);
+
+
+Datum
+brin_bloom_opcinfo(PG_FUNCTION_ARGS)
+{
+ BrinOpcInfo *result;
+
+ /*
+ * opaque->strategy_procinfos is initialized lazily; here it is set to
+ * all-uninitialized by palloc0 which sets fn_oid to InvalidOid.
+ *
+ * bloom indexes only store the filter as a single BYTEA column
+ */
+
+ result = palloc0(MAXALIGN(SizeofBrinOpcInfo(1)) +
+ sizeof(BloomOpaque));
+ result->oi_nstored = 1;
+ result->oi_regular_nulls = true;
+ result->oi_opaque = (BloomOpaque *)
+ MAXALIGN((char *) result + SizeofBrinOpcInfo(1));
+ result->oi_typcache[0] = lookup_type_cache(BYTEAOID, 0);
+
+ PG_RETURN_POINTER(result);
+}
+
+/*
+ * brin_bloom_get_ndistinct
+ * Determine the ndistinct value used to size bloom filter.
+ *
+ * Tweak the ndistinct value based on the pagesPerRange value. First,
+ * if it's negative, it's assumed to be relative to maximum number of
+ * tuples in the range (assuming each page gets MaxHeapTuplesPerPage
+ * tuples, which is likely a significant over-estimate). We also clamp
+ * the value, not to over-size the bloom filter unnecessarily.
+ *
+ * XXX We can only do this when the pagesPerRange value was supplied.
+ * If it wasn't, it has to be a read-only access to the index, in which
+ * case we don't really care. But perhaps we should fall-back to the
+ * default pagesPerRange value?
+ *
+ * XXX We might also fetch info about ndistinct estimate for the column,
+ * and compute the expected number of distinct values in a range. But
+ * that may be tricky due to data being sorted in various ways, so it
+ * seems better to rely on the upper estimate.
+ */
+static int
+brin_bloom_get_ndistinct(BrinDesc *bdesc, BloomOptions *opts)
+{
+ double ndistinct;
+ double maxtuples;
+ BlockNumber pagesPerRange;
+
+ pagesPerRange = BrinGetPagesPerRange(bdesc->bd_index);
+ ndistinct = BloomGetNDistinctPerRange(opts);
+
+ Assert(BlockNumberIsValid(pagesPerRange));
+
+ maxtuples = MaxHeapTuplesPerPage * pagesPerRange;
+
+ /*
+ * Similarly to n_distinct, negative values are relative - in this
+ * case to maximum number of tuples in the page range (maxtuples).
+ */
+ if (ndistinct < 0)
+ ndistinct = (-ndistinct) * maxtuples;
+
+ /*
+ * Positive values are to be used directly, but we still apply a
+ * couple of safeties no to use unreasonably small bloom filters.
+ */
+ ndistinct = Max(ndistinct, BLOOM_MIN_NDISTINCT_PER_RANGE);
+
+ /*
+ * And don't use more than the maximum possible number of tuples,
+ * in the range, which would be entirely wasteful.
+ */
+ ndistinct = Min(ndistinct, maxtuples);
+
+ return (int) ndistinct;
+}
+
+static double
+brin_bloom_get_fp_rate(BrinDesc *bdesc, BloomOptions *opts)
+{
+ return BloomGetFalsePositiveRate(opts);
+}
+
+/*
+ * Examine the given index tuple (which contains partial status of a certain
+ * page range) by comparing it to the given value that comes from another heap
+ * tuple. If the new value is outside the bloom filter specified by the
+ * existing tuple values, update the index tuple and return true. Otherwise,
+ * return false and do not modify in this case.
+ */
+Datum
+brin_bloom_add_value(PG_FUNCTION_ARGS)
+{
+ BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
+ BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
+ Datum newval = PG_GETARG_DATUM(2);
+ bool isnull PG_USED_FOR_ASSERTS_ONLY = PG_GETARG_DATUM(3);
+ BloomOptions *opts = (BloomOptions *) PG_GET_OPCLASS_OPTIONS();
+ Oid colloid = PG_GET_COLLATION();
+ FmgrInfo *hashFn;
+ uint32 hashValue;
+ bool updated = false;
+ AttrNumber attno;
+ BloomFilter *filter;
+
+ Assert(!isnull);
+
+ attno = column->bv_attno;
+
+ /*
+ * If this is the first non-null value, we need to initialize the bloom
+ * filter. Otherwise just extract the existing bloom filter from BrinValues.
+ */
+ if (column->bv_allnulls)
+ {
+ filter = bloom_init(brin_bloom_get_ndistinct(bdesc, opts),
+ brin_bloom_get_fp_rate(bdesc, opts));
+ column->bv_values[0] = PointerGetDatum(filter);
+ column->bv_allnulls = false;
+ updated = true;
+ }
+ else
+ filter = (BloomFilter *) PG_DETOAST_DATUM(column->bv_values[0]);
+
+ /*
+ * Compute the hash of the new value, using the supplied hash function,
+ * and then add the hash value to the bloom filter.
+ */
+ hashFn = bloom_get_procinfo(bdesc, attno, PROCNUM_HASH);
+
+ hashValue = DatumGetUInt32(FunctionCall1Coll(hashFn, colloid, newval));
+
+ filter = bloom_add_value(filter, hashValue, &updated);
+
+ column->bv_values[0] = PointerGetDatum(filter);
+
+ PG_RETURN_BOOL(updated);
+}
+
+/*
+ * Given an index tuple corresponding to a certain page range and a scan key,
+ * return whether the scan key is consistent with the index tuple's bloom
+ * filter. Return true if so, false otherwise.
+ */
+Datum
+brin_bloom_consistent(PG_FUNCTION_ARGS)
+{
+ BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
+ BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
+ ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2);
+ int nkeys = PG_GETARG_INT32(3);
+ Oid colloid = PG_GET_COLLATION();
+ AttrNumber attno;
+ Datum value;
+ Datum matches;
+ FmgrInfo *finfo;
+ uint32 hashValue;
+ BloomFilter *filter;
+ int keyno;
+
+ filter = (BloomFilter *) PG_DETOAST_DATUM(column->bv_values[0]);
+
+ Assert(filter);
+
+ matches = true;
+
+ for (keyno = 0; keyno < nkeys; keyno++)
+ {
+ ScanKey key = keys[keyno];
+
+ /* NULL keys are handled and filtered-out in bringetbitmap */
+ Assert(!(key->sk_flags & SK_ISNULL));
+
+ attno = key->sk_attno;
+ value = key->sk_argument;
+
+ switch (key->sk_strategy)
+ {
+ case BloomEqualStrategyNumber:
+
+ /*
+ * In the equality case (WHERE col = someval), we want to return
+ * the current page range if the minimum value in the range <=
+ * scan key, and the maximum value >= scan key.
+ */
+ finfo = bloom_get_procinfo(bdesc, attno, PROCNUM_HASH);
+
+ hashValue = DatumGetUInt32(FunctionCall1Coll(finfo, colloid, value));
+ matches &= bloom_contains_value(filter, hashValue);
+
+ break;
+ default:
+ /* shouldn't happen */
+ elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+ matches = 0;
+ break;
+ }
+
+ if (!matches)
+ break;
+ }
+
+ PG_RETURN_DATUM(matches);
+}
+
+/*
+ * Given two BrinValues, update the first of them as a union of the summary
+ * values contained in both. The second one is untouched.
+ *
+ * XXX We assume the bloom filters have the same parameters fow now. In the
+ * future we should have 'can union' function, to decide if we can combine
+ * two particular bloom filters.
+ */
+Datum
+brin_bloom_union(PG_FUNCTION_ARGS)
+{
+ BrinValues *col_a = (BrinValues *) PG_GETARG_POINTER(1);
+ BrinValues *col_b = (BrinValues *) PG_GETARG_POINTER(2);
+ BloomFilter *filter_a;
+ BloomFilter *filter_b;
+
+ Assert(col_a->bv_attno == col_b->bv_attno);
+ Assert(!col_a->bv_allnulls && !col_b->bv_allnulls);
+
+ filter_a = (BloomFilter *) PG_DETOAST_DATUM(col_a->bv_values[0]);
+ filter_b = (BloomFilter *) PG_DETOAST_DATUM(col_b->bv_values[0]);
+
+ /* make sure neither of the bloom filters is NULL */
+ Assert(filter_a && filter_b);
+ Assert(filter_a->nbits == filter_b->nbits);
+
+ /*
+ * Merging of the filters depends on the phase of both filters.
+ */
+ if (BLOOM_IS_SORTED(filter_b))
+ {
+ /*
+ * Simply read all items from 'b' and add them to 'a' (the phase of
+ * 'a' does not really matter).
+ */
+ int i;
+ uint32 *values = (uint32 *) filter_b->data;
+
+ for (i = 0; i < filter_b->nvalues; i++)
+ filter_a = bloom_add_value(filter_a, values[i], NULL);
+
+ col_a->bv_values[0] = PointerGetDatum(filter_a);
+ }
+ else if (BLOOM_IS_SORTED(filter_a))
+ {
+ /*
+ * 'b' hashed, 'a' sorted - copy 'b' into 'a' and then add all values
+ * from 'a' into the new copy.
+ */
+ int i;
+ BloomFilter *filter_c;
+ uint32 *values = (uint32 *) filter_a->data;
+
+ filter_c = (BloomFilter *) PG_DETOAST_DATUM(datumCopy(PointerGetDatum(filter_b), false, -1));
+
+ for (i = 0; i < filter_a->nvalues; i++)
+ filter_c = bloom_add_value(filter_c, values[i], NULL);
+
+ col_a->bv_values[0] = PointerGetDatum(filter_c);
+ }
+ else if (BLOOM_IS_HASHED(filter_a))
+ {
+ /*
+ * 'b' hashed, 'a' hashed - merge the bitmaps by OR
+ */
+ int i;
+ int nbytes = (filter_a->nbits + 7) / 8;
+
+ /* we better have "compatible" bloom filters */
+ Assert(filter_a->nbits == filter_b->nbits);
+ Assert(filter_a->nhashes == filter_b->nhashes);
+
+ for (i = 0; i < nbytes; i++)
+ filter_a->data[i] |= filter_b->data[i];
+ }
+
+ PG_RETURN_VOID();
+}
+
+/*
+ * Cache and return inclusion opclass support procedure
+ *
+ * Return the procedure corresponding to the given function support number
+ * or null if it is not exists.
+ */
+static FmgrInfo *
+bloom_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum)
+{
+ BloomOpaque *opaque;
+ uint16 basenum = procnum - PROCNUM_BASE;
+
+ /*
+ * We cache these in the opaque struct, to avoid repetitive syscache
+ * lookups.
+ */
+ opaque = (BloomOpaque *) bdesc->bd_info[attno - 1]->oi_opaque;
+
+ /*
+ * If we already searched for this proc and didn't find it, don't bother
+ * searching again.
+ */
+ if (opaque->extra_proc_missing[basenum])
+ return NULL;
+
+ if (opaque->extra_procinfos[basenum].fn_oid == InvalidOid)
+ {
+ if (RegProcedureIsValid(index_getprocid(bdesc->bd_index, attno,
+ procnum)))
+ {
+ fmgr_info_copy(&opaque->extra_procinfos[basenum],
+ index_getprocinfo(bdesc->bd_index, attno, procnum),
+ bdesc->bd_context);
+ }
+ else
+ {
+ opaque->extra_proc_missing[basenum] = true;
+ return NULL;
+ }
+ }
+
+ return &opaque->extra_procinfos[basenum];
+}
+
+Datum
+brin_bloom_options(PG_FUNCTION_ARGS)
+{
+ local_relopts *relopts = (local_relopts *) PG_GETARG_POINTER(0);
+
+ init_local_reloptions(relopts, sizeof(BloomOptions));
+
+ add_local_real_reloption(relopts, "n_distinct_per_range",
+ "number of distinct items expected in a BRIN page range",
+ BLOOM_DEFAULT_NDISTINCT_PER_RANGE,
+ -1.0, INT_MAX, offsetof(BloomOptions, nDistinctPerRange));
+
+ add_local_real_reloption(relopts, "false_positive_rate",
+ "desired false-positive rate for the bloom filters",
+ BLOOM_DEFAULT_FALSE_POSITIVE_RATE,
+ 0.001, 1.0, offsetof(BloomOptions, falsePositiveRate));
+
+ PG_RETURN_VOID();
+}
diff --git a/src/include/access/brin.h b/src/include/access/brin.h
index 0987878dd2..b02298c0aa 100644
--- a/src/include/access/brin.h
+++ b/src/include/access/brin.h
@@ -22,6 +22,8 @@ typedef struct BrinOptions
int32 vl_len_; /* varlena header (do not touch directly!) */
BlockNumber pagesPerRange;
bool autosummarize;
+ double nDistinctPerRange; /* number of distinct values per range */
+ double falsePositiveRate; /* false positive for bloom filter */
} BrinOptions;
diff --git a/src/include/access/brin_internal.h b/src/include/access/brin_internal.h
index 7c4f3da0a0..e3c9d47503 100644
--- a/src/include/access/brin_internal.h
+++ b/src/include/access/brin_internal.h
@@ -58,6 +58,10 @@ typedef struct BrinDesc
/* total number of Datum entries that are stored on-disk for all columns */
int bd_totalstored;
+ /* parameters for sizing bloom filter (BRIN bloom opclasses) */
+ double bd_nDistinctPerRange;
+ double bd_falsePositiveRange;
+
/* per-column info; bd_tupdesc->natts entries long */
BrinOpcInfo *bd_info[FLEXIBLE_ARRAY_MEMBER];
} BrinDesc;
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 1dfb6fd373..af6891e348 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1705,6 +1705,11 @@
amoprighttype => 'bytea', amopstrategy => '5', amopopr => '>(bytea,bytea)',
amopmethod => 'brin' },
+# bloom bytea
+{ amopfamily => 'brin/bytea_bloom_ops', amoplefttype => 'bytea',
+ amoprighttype => 'bytea', amopstrategy => '1', amopopr => '=(bytea,bytea)',
+ amopmethod => 'brin' },
+
# minmax "char"
{ amopfamily => 'brin/char_minmax_ops', amoplefttype => 'char',
amoprighttype => 'char', amopstrategy => '1', amopopr => '<(char,char)',
@@ -1722,6 +1727,11 @@
amoprighttype => 'char', amopstrategy => '5', amopopr => '>(char,char)',
amopmethod => 'brin' },
+# bloom "char"
+{ amopfamily => 'brin/char_bloom_ops', amoplefttype => 'char',
+ amoprighttype => 'char', amopstrategy => '1', amopopr => '=(char,char)',
+ amopmethod => 'brin' },
+
# minmax name
{ amopfamily => 'brin/name_minmax_ops', amoplefttype => 'name',
amoprighttype => 'name', amopstrategy => '1', amopopr => '<(name,name)',
@@ -1739,6 +1749,11 @@
amoprighttype => 'name', amopstrategy => '5', amopopr => '>(name,name)',
amopmethod => 'brin' },
+# bloom name
+{ amopfamily => 'brin/name_bloom_ops', amoplefttype => 'name',
+ amoprighttype => 'name', amopstrategy => '1', amopopr => '=(name,name)',
+ amopmethod => 'brin' },
+
# minmax integer
{ amopfamily => 'brin/integer_minmax_ops', amoplefttype => 'int8',
@@ -1885,6 +1900,44 @@
amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int4,int8)',
amopmethod => 'brin' },
+# bloom integer
+
+{ amopfamily => 'brin/integer_bloom_ops', amoplefttype => 'int8',
+ amoprighttype => 'int8', amopstrategy => '1', amopopr => '=(int8,int8)',
+ amopmethod => 'brin' },
+
+{ amopfamily => 'brin/integer_bloom_ops', amoplefttype => 'int8',
+ amoprighttype => 'int2', amopstrategy => '1', amopopr => '=(int8,int2)',
+ amopmethod => 'brin' },
+
+{ amopfamily => 'brin/integer_bloom_ops', amoplefttype => 'int8',
+ amoprighttype => 'int4', amopstrategy => '1', amopopr => '=(int8,int4)',
+ amopmethod => 'brin' },
+
+{ amopfamily => 'brin/integer_bloom_ops', amoplefttype => 'int2',
+ amoprighttype => 'int2', amopstrategy => '1', amopopr => '=(int2,int2)',
+ amopmethod => 'brin' },
+
+{ amopfamily => 'brin/integer_bloom_ops', amoplefttype => 'int2',
+ amoprighttype => 'int8', amopstrategy => '1', amopopr => '=(int2,int8)',
+ amopmethod => 'brin' },
+
+{ amopfamily => 'brin/integer_bloom_ops', amoplefttype => 'int2',
+ amoprighttype => 'int4', amopstrategy => '1', amopopr => '=(int2,int4)',
+ amopmethod => 'brin' },
+
+{ amopfamily => 'brin/integer_bloom_ops', amoplefttype => 'int4',
+ amoprighttype => 'int4', amopstrategy => '1', amopopr => '=(int4,int4)',
+ amopmethod => 'brin' },
+
+{ amopfamily => 'brin/integer_bloom_ops', amoplefttype => 'int4',
+ amoprighttype => 'int2', amopstrategy => '1', amopopr => '=(int4,int2)',
+ amopmethod => 'brin' },
+
+{ amopfamily => 'brin/integer_bloom_ops', amoplefttype => 'int4',
+ amoprighttype => 'int8', amopstrategy => '1', amopopr => '=(int4,int8)',
+ amopmethod => 'brin' },
+
# minmax text
{ amopfamily => 'brin/text_minmax_ops', amoplefttype => 'text',
amoprighttype => 'text', amopstrategy => '1', amopopr => '<(text,text)',
@@ -1902,6 +1955,11 @@
amoprighttype => 'text', amopstrategy => '5', amopopr => '>(text,text)',
amopmethod => 'brin' },
+# bloom text
+{ amopfamily => 'brin/text_bloom_ops', amoplefttype => 'text',
+ amoprighttype => 'text', amopstrategy => '1', amopopr => '=(text,text)',
+ amopmethod => 'brin' },
+
# minmax oid
{ amopfamily => 'brin/oid_minmax_ops', amoplefttype => 'oid',
amoprighttype => 'oid', amopstrategy => '1', amopopr => '<(oid,oid)',
@@ -1919,6 +1977,11 @@
amoprighttype => 'oid', amopstrategy => '5', amopopr => '>(oid,oid)',
amopmethod => 'brin' },
+# bloom oid
+{ amopfamily => 'brin/oid_bloom_ops', amoplefttype => 'oid',
+ amoprighttype => 'oid', amopstrategy => '1', amopopr => '=(oid,oid)',
+ amopmethod => 'brin' },
+
# minmax tid
{ amopfamily => 'brin/tid_minmax_ops', amoplefttype => 'tid',
amoprighttype => 'tid', amopstrategy => '1', amopopr => '<(tid,tid)',
@@ -2002,6 +2065,20 @@
amoprighttype => 'float8', amopstrategy => '5', amopopr => '>(float8,float8)',
amopmethod => 'brin' },
+# bloom float
+{ amopfamily => 'brin/float_bloom_ops', amoplefttype => 'float4',
+ amoprighttype => 'float4', amopstrategy => '1', amopopr => '=(float4,float4)',
+ amopmethod => 'brin' },
+{ amopfamily => 'brin/float_bloom_ops', amoplefttype => 'float4',
+ amoprighttype => 'float8', amopstrategy => '1', amopopr => '=(float4,float8)',
+ amopmethod => 'brin' },
+{ amopfamily => 'brin/float_bloom_ops', amoplefttype => 'float8',
+ amoprighttype => 'float4', amopstrategy => '1', amopopr => '=(float8,float4)',
+ amopmethod => 'brin' },
+{ amopfamily => 'brin/float_bloom_ops', amoplefttype => 'float8',
+ amoprighttype => 'float8', amopstrategy => '1', amopopr => '=(float8,float8)',
+ amopmethod => 'brin' },
+
# minmax macaddr
{ amopfamily => 'brin/macaddr_minmax_ops', amoplefttype => 'macaddr',
amoprighttype => 'macaddr', amopstrategy => '1',
@@ -2019,6 +2096,11 @@
amoprighttype => 'macaddr', amopstrategy => '5',
amopopr => '>(macaddr,macaddr)', amopmethod => 'brin' },
+# bloom macaddr
+{ amopfamily => 'brin/macaddr_bloom_ops', amoplefttype => 'macaddr',
+ amoprighttype => 'macaddr', amopstrategy => '1',
+ amopopr => '=(macaddr,macaddr)', amopmethod => 'brin' },
+
# minmax macaddr8
{ amopfamily => 'brin/macaddr8_minmax_ops', amoplefttype => 'macaddr8',
amoprighttype => 'macaddr8', amopstrategy => '1',
@@ -2036,6 +2118,11 @@
amoprighttype => 'macaddr8', amopstrategy => '5',
amopopr => '>(macaddr8,macaddr8)', amopmethod => 'brin' },
+# bloom macaddr8
+{ amopfamily => 'brin/macaddr8_bloom_ops', amoplefttype => 'macaddr8',
+ amoprighttype => 'macaddr8', amopstrategy => '1',
+ amopopr => '=(macaddr8,macaddr8)', amopmethod => 'brin' },
+
# minmax inet
{ amopfamily => 'brin/network_minmax_ops', amoplefttype => 'inet',
amoprighttype => 'inet', amopstrategy => '1', amopopr => '<(inet,inet)',
@@ -2053,6 +2140,11 @@
amoprighttype => 'inet', amopstrategy => '5', amopopr => '>(inet,inet)',
amopmethod => 'brin' },
+# bloom inet
+{ amopfamily => 'brin/network_bloom_ops', amoplefttype => 'inet',
+ amoprighttype => 'inet', amopstrategy => '1', amopopr => '=(inet,inet)',
+ amopmethod => 'brin' },
+
# inclusion inet
{ amopfamily => 'brin/network_inclusion_ops', amoplefttype => 'inet',
amoprighttype => 'inet', amopstrategy => '3', amopopr => '&&(inet,inet)',
@@ -2090,6 +2182,11 @@
amoprighttype => 'bpchar', amopstrategy => '5', amopopr => '>(bpchar,bpchar)',
amopmethod => 'brin' },
+# bloom character
+{ amopfamily => 'brin/bpchar_bloom_ops', amoplefttype => 'bpchar',
+ amoprighttype => 'bpchar', amopstrategy => '1', amopopr => '=(bpchar,bpchar)',
+ amopmethod => 'brin' },
+
# minmax time without time zone
{ amopfamily => 'brin/time_minmax_ops', amoplefttype => 'time',
amoprighttype => 'time', amopstrategy => '1', amopopr => '<(time,time)',
@@ -2107,6 +2204,11 @@
amoprighttype => 'time', amopstrategy => '5', amopopr => '>(time,time)',
amopmethod => 'brin' },
+# bloom time without time zone
+{ amopfamily => 'brin/time_bloom_ops', amoplefttype => 'time',
+ amoprighttype => 'time', amopstrategy => '1', amopopr => '=(time,time)',
+ amopmethod => 'brin' },
+
# minmax datetime (date, timestamp, timestamptz)
{ amopfamily => 'brin/datetime_minmax_ops', amoplefttype => 'timestamp',
@@ -2253,6 +2355,44 @@
amoprighttype => 'timestamptz', amopstrategy => '5',
amopopr => '>(timestamptz,timestamptz)', amopmethod => 'brin' },
+# bloom datetime (date, timestamp, timestamptz)
+
+{ amopfamily => 'brin/datetime_bloom_ops', amoplefttype => 'timestamp',
+ amoprighttype => 'timestamp', amopstrategy => '1',
+ amopopr => '=(timestamp,timestamp)', amopmethod => 'brin' },
+
+{ amopfamily => 'brin/datetime_bloom_ops', amoplefttype => 'timestamp',
+ amoprighttype => 'date', amopstrategy => '1', amopopr => '=(timestamp,date)',
+ amopmethod => 'brin' },
+
+{ amopfamily => 'brin/datetime_bloom_ops', amoplefttype => 'timestamp',
+ amoprighttype => 'timestamptz', amopstrategy => '1',
+ amopopr => '=(timestamp,timestamptz)', amopmethod => 'brin' },
+
+{ amopfamily => 'brin/datetime_bloom_ops', amoplefttype => 'date',
+ amoprighttype => 'date', amopstrategy => '1', amopopr => '=(date,date)',
+ amopmethod => 'brin' },
+
+{ amopfamily => 'brin/datetime_bloom_ops', amoplefttype => 'date',
+ amoprighttype => 'timestamp', amopstrategy => '1',
+ amopopr => '=(date,timestamp)', amopmethod => 'brin' },
+
+{ amopfamily => 'brin/datetime_bloom_ops', amoplefttype => 'date',
+ amoprighttype => 'timestamptz', amopstrategy => '1',
+ amopopr => '=(date,timestamptz)', amopmethod => 'brin' },
+
+{ amopfamily => 'brin/datetime_bloom_ops', amoplefttype => 'timestamptz',
+ amoprighttype => 'date', amopstrategy => '1',
+ amopopr => '=(timestamptz,date)', amopmethod => 'brin' },
+
+{ amopfamily => 'brin/datetime_bloom_ops', amoplefttype => 'timestamptz',
+ amoprighttype => 'timestamp', amopstrategy => '1',
+ amopopr => '=(timestamptz,timestamp)', amopmethod => 'brin' },
+
+{ amopfamily => 'brin/datetime_bloom_ops', amoplefttype => 'timestamptz',
+ amoprighttype => 'timestamptz', amopstrategy => '1',
+ amopopr => '=(timestamptz,timestamptz)', amopmethod => 'brin' },
+
# minmax interval
{ amopfamily => 'brin/interval_minmax_ops', amoplefttype => 'interval',
amoprighttype => 'interval', amopstrategy => '1',
@@ -2270,6 +2410,11 @@
amoprighttype => 'interval', amopstrategy => '5',
amopopr => '>(interval,interval)', amopmethod => 'brin' },
+# bloom interval
+{ amopfamily => 'brin/interval_bloom_ops', amoplefttype => 'interval',
+ amoprighttype => 'interval', amopstrategy => '1',
+ amopopr => '=(interval,interval)', amopmethod => 'brin' },
+
# minmax time with time zone
{ amopfamily => 'brin/timetz_minmax_ops', amoplefttype => 'timetz',
amoprighttype => 'timetz', amopstrategy => '1', amopopr => '<(timetz,timetz)',
@@ -2287,6 +2432,11 @@
amoprighttype => 'timetz', amopstrategy => '5', amopopr => '>(timetz,timetz)',
amopmethod => 'brin' },
+# bloom time with time zone
+{ amopfamily => 'brin/timetz_bloom_ops', amoplefttype => 'timetz',
+ amoprighttype => 'timetz', amopstrategy => '1', amopopr => '=(timetz,timetz)',
+ amopmethod => 'brin' },
+
# minmax bit
{ amopfamily => 'brin/bit_minmax_ops', amoplefttype => 'bit',
amoprighttype => 'bit', amopstrategy => '1', amopopr => '<(bit,bit)',
@@ -2338,6 +2488,11 @@
amoprighttype => 'numeric', amopstrategy => '5',
amopopr => '>(numeric,numeric)', amopmethod => 'brin' },
+# bloom numeric
+{ amopfamily => 'brin/numeric_bloom_ops', amoplefttype => 'numeric',
+ amoprighttype => 'numeric', amopstrategy => '1',
+ amopopr => '=(numeric,numeric)', amopmethod => 'brin' },
+
# minmax uuid
{ amopfamily => 'brin/uuid_minmax_ops', amoplefttype => 'uuid',
amoprighttype => 'uuid', amopstrategy => '1', amopopr => '<(uuid,uuid)',
@@ -2355,6 +2510,11 @@
amoprighttype => 'uuid', amopstrategy => '5', amopopr => '>(uuid,uuid)',
amopmethod => 'brin' },
+# bloom uuid
+{ amopfamily => 'brin/uuid_bloom_ops', amoplefttype => 'uuid',
+ amoprighttype => 'uuid', amopstrategy => '1', amopopr => '=(uuid,uuid)',
+ amopmethod => 'brin' },
+
# inclusion range types
{ amopfamily => 'brin/range_inclusion_ops', amoplefttype => 'anyrange',
amoprighttype => 'anyrange', amopstrategy => '1',
@@ -2416,6 +2576,11 @@
amoprighttype => 'pg_lsn', amopstrategy => '5', amopopr => '>(pg_lsn,pg_lsn)',
amopmethod => 'brin' },
+# bloom pg_lsn
+{ amopfamily => 'brin/pg_lsn_bloom_ops', amoplefttype => 'pg_lsn',
+ amoprighttype => 'pg_lsn', amopstrategy => '1', amopopr => '=(pg_lsn,pg_lsn)',
+ amopmethod => 'brin' },
+
# inclusion box
{ amopfamily => 'brin/box_inclusion_ops', amoplefttype => 'box',
amoprighttype => 'box', amopstrategy => '1', amopopr => '<<(box,box)',
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 37b580883f..c24a80545a 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -770,6 +770,24 @@
{ amprocfamily => 'brin/bytea_minmax_ops', amproclefttype => 'bytea',
amprocrighttype => 'bytea', amprocnum => '4', amproc => 'brin_minmax_union' },
+# bloom bytea
+{ amprocfamily => 'brin/bytea_bloom_ops', amproclefttype => 'bytea',
+ amprocrighttype => 'bytea', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/bytea_bloom_ops', amproclefttype => 'bytea',
+ amprocrighttype => 'bytea', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/bytea_bloom_ops', amproclefttype => 'bytea',
+ amprocrighttype => 'bytea', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/bytea_bloom_ops', amproclefttype => 'bytea',
+ amprocrighttype => 'bytea', amprocnum => '4', amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/bytea_bloom_ops', amproclefttype => 'bytea',
+ amprocrighttype => 'bytea', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/bytea_bloom_ops', amproclefttype => 'bytea',
+ amprocrighttype => 'bytea', amprocnum => '11', amproc => 'hashvarlena' },
+
# minmax "char"
{ amprocfamily => 'brin/char_minmax_ops', amproclefttype => 'char',
amprocrighttype => 'char', amprocnum => '1',
@@ -783,6 +801,24 @@
{ amprocfamily => 'brin/char_minmax_ops', amproclefttype => 'char',
amprocrighttype => 'char', amprocnum => '4', amproc => 'brin_minmax_union' },
+# bloom "char"
+{ amprocfamily => 'brin/char_bloom_ops', amproclefttype => 'char',
+ amprocrighttype => 'char', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/char_bloom_ops', amproclefttype => 'char',
+ amprocrighttype => 'char', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/char_bloom_ops', amproclefttype => 'char',
+ amprocrighttype => 'char', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/char_bloom_ops', amproclefttype => 'char',
+ amprocrighttype => 'char', amprocnum => '4', amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/char_bloom_ops', amproclefttype => 'char',
+ amprocrighttype => 'char', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/char_bloom_ops', amproclefttype => 'char',
+ amprocrighttype => 'char', amprocnum => '11', amproc => 'hashchar' },
+
# minmax name
{ amprocfamily => 'brin/name_minmax_ops', amproclefttype => 'name',
amprocrighttype => 'name', amprocnum => '1',
@@ -796,6 +832,24 @@
{ amprocfamily => 'brin/name_minmax_ops', amproclefttype => 'name',
amprocrighttype => 'name', amprocnum => '4', amproc => 'brin_minmax_union' },
+# bloom name
+{ amprocfamily => 'brin/name_bloom_ops', amproclefttype => 'name',
+ amprocrighttype => 'name', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/name_bloom_ops', amproclefttype => 'name',
+ amprocrighttype => 'name', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/name_bloom_ops', amproclefttype => 'name',
+ amprocrighttype => 'name', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/name_bloom_ops', amproclefttype => 'name',
+ amprocrighttype => 'name', amprocnum => '4', amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/name_bloom_ops', amproclefttype => 'name',
+ amprocrighttype => 'name', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/name_bloom_ops', amproclefttype => 'name',
+ amprocrighttype => 'name', amprocnum => '11', amproc => 'hashname' },
+
# minmax integer: int2, int4, int8
{ amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int8',
amprocrighttype => 'int8', amprocnum => '1',
@@ -897,6 +951,58 @@
{ amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int4',
amprocrighttype => 'int2', amprocnum => '4', amproc => 'brin_minmax_union' },
+# bloom integer: int2, int4, int8
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int8',
+ amprocrighttype => 'int8', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int8',
+ amprocrighttype => 'int8', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int8',
+ amprocrighttype => 'int8', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int8',
+ amprocrighttype => 'int8', amprocnum => '4', amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int8',
+ amprocrighttype => 'int8', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int8',
+ amprocrighttype => 'int8', amprocnum => '11', amproc => 'hashint8' },
+
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int2',
+ amprocrighttype => 'int2', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int2',
+ amprocrighttype => 'int2', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int2',
+ amprocrighttype => 'int2', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int2',
+ amprocrighttype => 'int2', amprocnum => '4', amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int2',
+ amprocrighttype => 'int2', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int2',
+ amprocrighttype => 'int2', amprocnum => '11', amproc => 'hashint2' },
+
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int4',
+ amprocrighttype => 'int4', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int4',
+ amprocrighttype => 'int4', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int4',
+ amprocrighttype => 'int4', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int4',
+ amprocrighttype => 'int4', amprocnum => '4', amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int4',
+ amprocrighttype => 'int4', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int4',
+ amprocrighttype => 'int4', amprocnum => '11', amproc => 'hashint4' },
+
# minmax text
{ amprocfamily => 'brin/text_minmax_ops', amproclefttype => 'text',
amprocrighttype => 'text', amprocnum => '1',
@@ -910,6 +1016,24 @@
{ amprocfamily => 'brin/text_minmax_ops', amproclefttype => 'text',
amprocrighttype => 'text', amprocnum => '4', amproc => 'brin_minmax_union' },
+# bloom text
+{ amprocfamily => 'brin/text_bloom_ops', amproclefttype => 'text',
+ amprocrighttype => 'text', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/text_bloom_ops', amproclefttype => 'text',
+ amprocrighttype => 'text', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/text_bloom_ops', amproclefttype => 'text',
+ amprocrighttype => 'text', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/text_bloom_ops', amproclefttype => 'text',
+ amprocrighttype => 'text', amprocnum => '4', amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/text_bloom_ops', amproclefttype => 'text',
+ amprocrighttype => 'text', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/text_bloom_ops', amproclefttype => 'text',
+ amprocrighttype => 'text', amprocnum => '11', amproc => 'hashtext' },
+
# minmax oid
{ amprocfamily => 'brin/oid_minmax_ops', amproclefttype => 'oid',
amprocrighttype => 'oid', amprocnum => '1', amproc => 'brin_minmax_opcinfo' },
@@ -922,6 +1046,23 @@
{ amprocfamily => 'brin/oid_minmax_ops', amproclefttype => 'oid',
amprocrighttype => 'oid', amprocnum => '4', amproc => 'brin_minmax_union' },
+# bloom oid
+{ amprocfamily => 'brin/oid_bloom_ops', amproclefttype => 'oid',
+ amprocrighttype => 'oid', amprocnum => '1', amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/oid_bloom_ops', amproclefttype => 'oid',
+ amprocrighttype => 'oid', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/oid_bloom_ops', amproclefttype => 'oid',
+ amprocrighttype => 'oid', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/oid_bloom_ops', amproclefttype => 'oid',
+ amprocrighttype => 'oid', amprocnum => '4', amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/oid_bloom_ops', amproclefttype => 'oid',
+ amprocrighttype => 'oid', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/oid_bloom_ops', amproclefttype => 'oid',
+ amprocrighttype => 'oid', amprocnum => '11', amproc => 'hashoid' },
+
# minmax tid
{ amprocfamily => 'brin/tid_minmax_ops', amproclefttype => 'tid',
amprocrighttype => 'tid', amprocnum => '1', amproc => 'brin_minmax_opcinfo' },
@@ -984,6 +1125,45 @@
amprocrighttype => 'float4', amprocnum => '4',
amproc => 'brin_minmax_union' },
+# bloom float
+{ amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float4',
+ amprocrighttype => 'float4', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float4',
+ amprocrighttype => 'float4', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float4',
+ amprocrighttype => 'float4', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float4',
+ amprocrighttype => 'float4', amprocnum => '4',
+ amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float4',
+ amprocrighttype => 'float4', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float4',
+ amprocrighttype => 'float4', amprocnum => '11',
+ amproc => 'hashfloat4' },
+
+{ amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float8',
+ amprocrighttype => 'float8', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float8',
+ amprocrighttype => 'float8', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float8',
+ amprocrighttype => 'float8', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float8',
+ amprocrighttype => 'float8', amprocnum => '4',
+ amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float8',
+ amprocrighttype => 'float8', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float8',
+ amprocrighttype => 'float8', amprocnum => '11',
+ amproc => 'hashfloat8' },
+
# minmax macaddr
{ amprocfamily => 'brin/macaddr_minmax_ops', amproclefttype => 'macaddr',
amprocrighttype => 'macaddr', amprocnum => '1',
@@ -998,6 +1178,26 @@
amprocrighttype => 'macaddr', amprocnum => '4',
amproc => 'brin_minmax_union' },
+# bloom macaddr
+{ amprocfamily => 'brin/macaddr_bloom_ops', amproclefttype => 'macaddr',
+ amprocrighttype => 'macaddr', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/macaddr_bloom_ops', amproclefttype => 'macaddr',
+ amprocrighttype => 'macaddr', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/macaddr_bloom_ops', amproclefttype => 'macaddr',
+ amprocrighttype => 'macaddr', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/macaddr_bloom_ops', amproclefttype => 'macaddr',
+ amprocrighttype => 'macaddr', amprocnum => '4',
+ amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/macaddr_bloom_ops', amproclefttype => 'macaddr',
+ amprocrighttype => 'macaddr', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/macaddr_bloom_ops', amproclefttype => 'macaddr',
+ amprocrighttype => 'macaddr', amprocnum => '11',
+ amproc => 'hashmacaddr' },
+
# minmax macaddr8
{ amprocfamily => 'brin/macaddr8_minmax_ops', amproclefttype => 'macaddr8',
amprocrighttype => 'macaddr8', amprocnum => '1',
@@ -1012,6 +1212,26 @@
amprocrighttype => 'macaddr8', amprocnum => '4',
amproc => 'brin_minmax_union' },
+# bloom macaddr8
+{ amprocfamily => 'brin/macaddr8_bloom_ops', amproclefttype => 'macaddr8',
+ amprocrighttype => 'macaddr8', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/macaddr8_bloom_ops', amproclefttype => 'macaddr8',
+ amprocrighttype => 'macaddr8', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/macaddr8_bloom_ops', amproclefttype => 'macaddr8',
+ amprocrighttype => 'macaddr8', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/macaddr8_bloom_ops', amproclefttype => 'macaddr8',
+ amprocrighttype => 'macaddr8', amprocnum => '4',
+ amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/macaddr8_bloom_ops', amproclefttype => 'macaddr8',
+ amprocrighttype => 'macaddr8', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/macaddr8_bloom_ops', amproclefttype => 'macaddr8',
+ amprocrighttype => 'macaddr8', amprocnum => '11',
+ amproc => 'hashmacaddr8' },
+
# minmax inet
{ amprocfamily => 'brin/network_minmax_ops', amproclefttype => 'inet',
amprocrighttype => 'inet', amprocnum => '1',
@@ -1025,6 +1245,24 @@
{ amprocfamily => 'brin/network_minmax_ops', amproclefttype => 'inet',
amprocrighttype => 'inet', amprocnum => '4', amproc => 'brin_minmax_union' },
+# bloom inet
+{ amprocfamily => 'brin/network_bloom_ops', amproclefttype => 'inet',
+ amprocrighttype => 'inet', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/network_bloom_ops', amproclefttype => 'inet',
+ amprocrighttype => 'inet', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/network_bloom_ops', amproclefttype => 'inet',
+ amprocrighttype => 'inet', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/network_bloom_ops', amproclefttype => 'inet',
+ amprocrighttype => 'inet', amprocnum => '4', amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/network_bloom_ops', amproclefttype => 'inet',
+ amprocrighttype => 'inet', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/network_bloom_ops', amproclefttype => 'inet',
+ amprocrighttype => 'inet', amprocnum => '11', amproc => 'hashinet' },
+
# inclusion inet
{ amprocfamily => 'brin/network_inclusion_ops', amproclefttype => 'inet',
amprocrighttype => 'inet', amprocnum => '1',
@@ -1059,6 +1297,26 @@
amprocrighttype => 'bpchar', amprocnum => '4',
amproc => 'brin_minmax_union' },
+# bloom character
+{ amprocfamily => 'brin/bpchar_bloom_ops', amproclefttype => 'bpchar',
+ amprocrighttype => 'bpchar', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/bpchar_bloom_ops', amproclefttype => 'bpchar',
+ amprocrighttype => 'bpchar', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/bpchar_bloom_ops', amproclefttype => 'bpchar',
+ amprocrighttype => 'bpchar', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/bpchar_bloom_ops', amproclefttype => 'bpchar',
+ amprocrighttype => 'bpchar', amprocnum => '4',
+ amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/bpchar_bloom_ops', amproclefttype => 'bpchar',
+ amprocrighttype => 'bpchar', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/bpchar_bloom_ops', amproclefttype => 'bpchar',
+ amprocrighttype => 'bpchar', amprocnum => '11',
+ amproc => 'hashchar' },
+
# minmax time without time zone
{ amprocfamily => 'brin/time_minmax_ops', amproclefttype => 'time',
amprocrighttype => 'time', amprocnum => '1',
@@ -1072,6 +1330,24 @@
{ amprocfamily => 'brin/time_minmax_ops', amproclefttype => 'time',
amprocrighttype => 'time', amprocnum => '4', amproc => 'brin_minmax_union' },
+# bloom time without time zone
+{ amprocfamily => 'brin/time_bloom_ops', amproclefttype => 'time',
+ amprocrighttype => 'time', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/time_bloom_ops', amproclefttype => 'time',
+ amprocrighttype => 'time', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/time_bloom_ops', amproclefttype => 'time',
+ amprocrighttype => 'time', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/time_bloom_ops', amproclefttype => 'time',
+ amprocrighttype => 'time', amprocnum => '4', amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/time_bloom_ops', amproclefttype => 'time',
+ amprocrighttype => 'time', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/time_bloom_ops', amproclefttype => 'time',
+ amprocrighttype => 'time', amprocnum => '11', amproc => 'time_hash' },
+
# minmax datetime (date, timestamp, timestamptz)
{ amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'timestamp',
amprocrighttype => 'timestamp', amprocnum => '1',
@@ -1179,6 +1455,62 @@
amprocrighttype => 'timestamptz', amprocnum => '4',
amproc => 'brin_minmax_union' },
+# bloom datetime (date, timestamp, timestamptz)
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamp',
+ amprocrighttype => 'timestamp', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamp',
+ amprocrighttype => 'timestamp', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamp',
+ amprocrighttype => 'timestamp', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamp',
+ amprocrighttype => 'timestamp', amprocnum => '4',
+ amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamp',
+ amprocrighttype => 'timestamp', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamp',
+ amprocrighttype => 'timestamp', amprocnum => '11',
+ amproc => 'timestamp_hash' },
+
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamptz',
+ amprocrighttype => 'timestamptz', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamptz',
+ amprocrighttype => 'timestamptz', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamptz',
+ amprocrighttype => 'timestamptz', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamptz',
+ amprocrighttype => 'timestamptz', amprocnum => '4',
+ amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamptz',
+ amprocrighttype => 'timestamptz', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamptz',
+ amprocrighttype => 'timestamptz', amprocnum => '11',
+ amproc => 'timestamp_hash' },
+
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'date',
+ amprocrighttype => 'date', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'date',
+ amprocrighttype => 'date', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'date',
+ amprocrighttype => 'date', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'date',
+ amprocrighttype => 'date', amprocnum => '4', amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'date',
+ amprocrighttype => 'date', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'date',
+ amprocrighttype => 'date', amprocnum => '11', amproc => 'hashint4' },
+
# minmax interval
{ amprocfamily => 'brin/interval_minmax_ops', amproclefttype => 'interval',
amprocrighttype => 'interval', amprocnum => '1',
@@ -1193,6 +1525,26 @@
amprocrighttype => 'interval', amprocnum => '4',
amproc => 'brin_minmax_union' },
+# bloom interval
+{ amprocfamily => 'brin/interval_bloom_ops', amproclefttype => 'interval',
+ amprocrighttype => 'interval', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/interval_bloom_ops', amproclefttype => 'interval',
+ amprocrighttype => 'interval', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/interval_bloom_ops', amproclefttype => 'interval',
+ amprocrighttype => 'interval', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/interval_bloom_ops', amproclefttype => 'interval',
+ amprocrighttype => 'interval', amprocnum => '4',
+ amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/interval_bloom_ops', amproclefttype => 'interval',
+ amprocrighttype => 'interval', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/interval_bloom_ops', amproclefttype => 'interval',
+ amprocrighttype => 'interval', amprocnum => '11',
+ amproc => 'interval_hash' },
+
# minmax time with time zone
{ amprocfamily => 'brin/timetz_minmax_ops', amproclefttype => 'timetz',
amprocrighttype => 'timetz', amprocnum => '1',
@@ -1207,6 +1559,26 @@
amprocrighttype => 'timetz', amprocnum => '4',
amproc => 'brin_minmax_union' },
+# bloom time with time zone
+{ amprocfamily => 'brin/timetz_bloom_ops', amproclefttype => 'timetz',
+ amprocrighttype => 'timetz', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/timetz_bloom_ops', amproclefttype => 'timetz',
+ amprocrighttype => 'timetz', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/timetz_bloom_ops', amproclefttype => 'timetz',
+ amprocrighttype => 'timetz', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/timetz_bloom_ops', amproclefttype => 'timetz',
+ amprocrighttype => 'timetz', amprocnum => '4',
+ amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/timetz_bloom_ops', amproclefttype => 'timetz',
+ amprocrighttype => 'timetz', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/timetz_bloom_ops', amproclefttype => 'timetz',
+ amprocrighttype => 'timetz', amprocnum => '11',
+ amproc => 'timetz_hash' },
+
# minmax bit
{ amprocfamily => 'brin/bit_minmax_ops', amproclefttype => 'bit',
amprocrighttype => 'bit', amprocnum => '1', amproc => 'brin_minmax_opcinfo' },
@@ -1247,6 +1619,26 @@
amprocrighttype => 'numeric', amprocnum => '4',
amproc => 'brin_minmax_union' },
+# bloom numeric
+{ amprocfamily => 'brin/numeric_bloom_ops', amproclefttype => 'numeric',
+ amprocrighttype => 'numeric', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/numeric_bloom_ops', amproclefttype => 'numeric',
+ amprocrighttype => 'numeric', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/numeric_bloom_ops', amproclefttype => 'numeric',
+ amprocrighttype => 'numeric', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/numeric_bloom_ops', amproclefttype => 'numeric',
+ amprocrighttype => 'numeric', amprocnum => '4',
+ amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/numeric_bloom_ops', amproclefttype => 'numeric',
+ amprocrighttype => 'numeric', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/numeric_bloom_ops', amproclefttype => 'numeric',
+ amprocrighttype => 'numeric', amprocnum => '11',
+ amproc => 'hash_numeric' },
+
# minmax uuid
{ amprocfamily => 'brin/uuid_minmax_ops', amproclefttype => 'uuid',
amprocrighttype => 'uuid', amprocnum => '1',
@@ -1260,6 +1652,24 @@
{ amprocfamily => 'brin/uuid_minmax_ops', amproclefttype => 'uuid',
amprocrighttype => 'uuid', amprocnum => '4', amproc => 'brin_minmax_union' },
+# bloom uuid
+{ amprocfamily => 'brin/uuid_bloom_ops', amproclefttype => 'uuid',
+ amprocrighttype => 'uuid', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/uuid_bloom_ops', amproclefttype => 'uuid',
+ amprocrighttype => 'uuid', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/uuid_bloom_ops', amproclefttype => 'uuid',
+ amprocrighttype => 'uuid', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/uuid_bloom_ops', amproclefttype => 'uuid',
+ amprocrighttype => 'uuid', amprocnum => '4', amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/uuid_bloom_ops', amproclefttype => 'uuid',
+ amprocrighttype => 'uuid', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/uuid_bloom_ops', amproclefttype => 'uuid',
+ amprocrighttype => 'uuid', amprocnum => '11', amproc => 'uuid_hash' },
+
# inclusion range types
{ amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
amprocrighttype => 'anyrange', amprocnum => '1',
@@ -1295,6 +1705,26 @@
amprocrighttype => 'pg_lsn', amprocnum => '4',
amproc => 'brin_minmax_union' },
+# bloom pg_lsn
+{ amprocfamily => 'brin/pg_lsn_bloom_ops', amproclefttype => 'pg_lsn',
+ amprocrighttype => 'pg_lsn', amprocnum => '1',
+ amproc => 'brin_bloom_opcinfo' },
+{ amprocfamily => 'brin/pg_lsn_bloom_ops', amproclefttype => 'pg_lsn',
+ amprocrighttype => 'pg_lsn', amprocnum => '2',
+ amproc => 'brin_bloom_add_value' },
+{ amprocfamily => 'brin/pg_lsn_bloom_ops', amproclefttype => 'pg_lsn',
+ amprocrighttype => 'pg_lsn', amprocnum => '3',
+ amproc => 'brin_bloom_consistent' },
+{ amprocfamily => 'brin/pg_lsn_bloom_ops', amproclefttype => 'pg_lsn',
+ amprocrighttype => 'pg_lsn', amprocnum => '4',
+ amproc => 'brin_bloom_union' },
+{ amprocfamily => 'brin/pg_lsn_bloom_ops', amproclefttype => 'pg_lsn',
+ amprocrighttype => 'pg_lsn', amprocnum => '5',
+ amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/pg_lsn_bloom_ops', amproclefttype => 'pg_lsn',
+ amprocrighttype => 'pg_lsn', amprocnum => '11',
+ amproc => 'pg_lsn_hash' },
+
# inclusion box
{ amprocfamily => 'brin/box_inclusion_ops', amproclefttype => 'box',
amprocrighttype => 'box', amprocnum => '1',
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index f2342bb328..16d6111ab6 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -257,67 +257,127 @@
{ opcmethod => 'brin', opcname => 'bytea_minmax_ops',
opcfamily => 'brin/bytea_minmax_ops', opcintype => 'bytea',
opckeytype => 'bytea' },
+{ opcmethod => 'brin', opcname => 'bytea_bloom_ops',
+ opcfamily => 'brin/bytea_bloom_ops', opcintype => 'bytea',
+ opckeytype => 'bytea', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'char_minmax_ops',
opcfamily => 'brin/char_minmax_ops', opcintype => 'char',
opckeytype => 'char' },
+{ opcmethod => 'brin', opcname => 'char_bloom_ops',
+ opcfamily => 'brin/char_bloom_ops', opcintype => 'char',
+ opckeytype => 'char', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'name_minmax_ops',
opcfamily => 'brin/name_minmax_ops', opcintype => 'name',
opckeytype => 'name' },
+{ opcmethod => 'brin', opcname => 'name_bloom_ops',
+ opcfamily => 'brin/name_bloom_ops', opcintype => 'name',
+ opckeytype => 'name', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'int8_minmax_ops',
opcfamily => 'brin/integer_minmax_ops', opcintype => 'int8',
opckeytype => 'int8' },
+{ opcmethod => 'brin', opcname => 'int8_bloom_ops',
+ opcfamily => 'brin/integer_bloom_ops', opcintype => 'int8',
+ opckeytype => 'int8', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'int2_minmax_ops',
opcfamily => 'brin/integer_minmax_ops', opcintype => 'int2',
opckeytype => 'int2' },
+{ opcmethod => 'brin', opcname => 'int2_bloom_ops',
+ opcfamily => 'brin/integer_bloom_ops', opcintype => 'int2',
+ opckeytype => 'int2', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'int4_minmax_ops',
opcfamily => 'brin/integer_minmax_ops', opcintype => 'int4',
opckeytype => 'int4' },
+{ opcmethod => 'brin', opcname => 'int4_bloom_ops',
+ opcfamily => 'brin/integer_bloom_ops', opcintype => 'int4',
+ opckeytype => 'int4', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'text_minmax_ops',
opcfamily => 'brin/text_minmax_ops', opcintype => 'text',
opckeytype => 'text' },
+{ opcmethod => 'brin', opcname => 'text_bloom_ops',
+ opcfamily => 'brin/text_bloom_ops', opcintype => 'text',
+ opckeytype => 'text', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'oid_minmax_ops',
opcfamily => 'brin/oid_minmax_ops', opcintype => 'oid', opckeytype => 'oid' },
+{ opcmethod => 'brin', opcname => 'oid_bloom_ops',
+ opcfamily => 'brin/oid_bloom_ops', opcintype => 'oid',
+ opckeytype => 'oid', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'tid_minmax_ops',
opcfamily => 'brin/tid_minmax_ops', opcintype => 'tid', opckeytype => 'tid' },
{ opcmethod => 'brin', opcname => 'float4_minmax_ops',
opcfamily => 'brin/float_minmax_ops', opcintype => 'float4',
opckeytype => 'float4' },
+{ opcmethod => 'brin', opcname => 'float4_bloom_ops',
+ opcfamily => 'brin/float_bloom_ops', opcintype => 'float4',
+ opckeytype => 'float4', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'float8_minmax_ops',
opcfamily => 'brin/float_minmax_ops', opcintype => 'float8',
opckeytype => 'float8' },
+{ opcmethod => 'brin', opcname => 'float8_bloom_ops',
+ opcfamily => 'brin/float_bloom_ops', opcintype => 'float8',
+ opckeytype => 'float8', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'macaddr_minmax_ops',
opcfamily => 'brin/macaddr_minmax_ops', opcintype => 'macaddr',
opckeytype => 'macaddr' },
+{ opcmethod => 'brin', opcname => 'macaddr_bloom_ops',
+ opcfamily => 'brin/macaddr_bloom_ops', opcintype => 'macaddr',
+ opckeytype => 'macaddr', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'macaddr8_minmax_ops',
opcfamily => 'brin/macaddr8_minmax_ops', opcintype => 'macaddr8',
opckeytype => 'macaddr8' },
+{ opcmethod => 'brin', opcname => 'macaddr8_bloom_ops',
+ opcfamily => 'brin/macaddr8_bloom_ops', opcintype => 'macaddr8',
+ opckeytype => 'macaddr8', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'inet_minmax_ops',
opcfamily => 'brin/network_minmax_ops', opcintype => 'inet',
opcdefault => 'f', opckeytype => 'inet' },
+{ opcmethod => 'brin', opcname => 'inet_bloom_ops',
+ opcfamily => 'brin/network_bloom_ops', opcintype => 'inet',
+ opcdefault => 'f', opckeytype => 'inet', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'inet_inclusion_ops',
opcfamily => 'brin/network_inclusion_ops', opcintype => 'inet',
opckeytype => 'inet' },
{ opcmethod => 'brin', opcname => 'bpchar_minmax_ops',
opcfamily => 'brin/bpchar_minmax_ops', opcintype => 'bpchar',
opckeytype => 'bpchar' },
+{ opcmethod => 'brin', opcname => 'bpchar_bloom_ops',
+ opcfamily => 'brin/bpchar_bloom_ops', opcintype => 'bpchar',
+ opckeytype => 'bpchar', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'time_minmax_ops',
opcfamily => 'brin/time_minmax_ops', opcintype => 'time',
opckeytype => 'time' },
+{ opcmethod => 'brin', opcname => 'time_bloom_ops',
+ opcfamily => 'brin/time_bloom_ops', opcintype => 'time',
+ opckeytype => 'time', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'date_minmax_ops',
opcfamily => 'brin/datetime_minmax_ops', opcintype => 'date',
opckeytype => 'date' },
+{ opcmethod => 'brin', opcname => 'date_bloom_ops',
+ opcfamily => 'brin/datetime_bloom_ops', opcintype => 'date',
+ opckeytype => 'date', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'timestamp_minmax_ops',
opcfamily => 'brin/datetime_minmax_ops', opcintype => 'timestamp',
opckeytype => 'timestamp' },
+{ opcmethod => 'brin', opcname => 'timestamp_bloom_ops',
+ opcfamily => 'brin/datetime_bloom_ops', opcintype => 'timestamp',
+ opckeytype => 'timestamp', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'timestamptz_minmax_ops',
opcfamily => 'brin/datetime_minmax_ops', opcintype => 'timestamptz',
opckeytype => 'timestamptz' },
+{ opcmethod => 'brin', opcname => 'timestamptz_bloom_ops',
+ opcfamily => 'brin/datetime_bloom_ops', opcintype => 'timestamptz',
+ opckeytype => 'timestamptz', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'interval_minmax_ops',
opcfamily => 'brin/interval_minmax_ops', opcintype => 'interval',
opckeytype => 'interval' },
+{ opcmethod => 'brin', opcname => 'interval_bloom_ops',
+ opcfamily => 'brin/interval_bloom_ops', opcintype => 'interval',
+ opckeytype => 'interval', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'timetz_minmax_ops',
opcfamily => 'brin/timetz_minmax_ops', opcintype => 'timetz',
opckeytype => 'timetz' },
+{ opcmethod => 'brin', opcname => 'timetz_bloom_ops',
+ opcfamily => 'brin/timetz_bloom_ops', opcintype => 'timetz',
+ opckeytype => 'timetz', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'bit_minmax_ops',
opcfamily => 'brin/bit_minmax_ops', opcintype => 'bit', opckeytype => 'bit' },
{ opcmethod => 'brin', opcname => 'varbit_minmax_ops',
@@ -326,18 +386,27 @@
{ opcmethod => 'brin', opcname => 'numeric_minmax_ops',
opcfamily => 'brin/numeric_minmax_ops', opcintype => 'numeric',
opckeytype => 'numeric' },
+{ opcmethod => 'brin', opcname => 'numeric_bloom_ops',
+ opcfamily => 'brin/numeric_bloom_ops', opcintype => 'numeric',
+ opckeytype => 'numeric', opcdefault => 'f' },
# no brin opclass for record, anyarray
{ opcmethod => 'brin', opcname => 'uuid_minmax_ops',
opcfamily => 'brin/uuid_minmax_ops', opcintype => 'uuid',
opckeytype => 'uuid' },
+{ opcmethod => 'brin', opcname => 'uuid_bloom_ops',
+ opcfamily => 'brin/uuid_bloom_ops', opcintype => 'uuid',
+ opckeytype => 'uuid', opcdefault => 'f' },
{ opcmethod => 'brin', opcname => 'range_inclusion_ops',
opcfamily => 'brin/range_inclusion_ops', opcintype => 'anyrange',
opckeytype => 'anyrange' },
{ opcmethod => 'brin', opcname => 'pg_lsn_minmax_ops',
opcfamily => 'brin/pg_lsn_minmax_ops', opcintype => 'pg_lsn',
opckeytype => 'pg_lsn' },
+{ opcmethod => 'brin', opcname => 'pg_lsn_bloom_ops',
+ opcfamily => 'brin/pg_lsn_bloom_ops', opcintype => 'pg_lsn',
+ opckeytype => 'pg_lsn', opcdefault => 'f' },
# no brin opclass for enum, tsvector, tsquery, jsonb
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index cf0fb325b3..7f733aeab1 100644
--- a/src/include/catalog/pg_opfamily.dat
+++ b/src/include/catalog/pg_opfamily.dat
@@ -180,50 +180,86 @@
opfmethod => 'gin', opfname => 'jsonb_path_ops' },
{ oid => '4054',
opfmethod => 'brin', opfname => 'integer_minmax_ops' },
+{ oid => '8100',
+ opfmethod => 'brin', opfname => 'integer_bloom_ops' },
{ oid => '4055',
opfmethod => 'brin', opfname => 'numeric_minmax_ops' },
{ oid => '4056',
opfmethod => 'brin', opfname => 'text_minmax_ops' },
+{ oid => '8101',
+ opfmethod => 'brin', opfname => 'text_bloom_ops' },
+{ oid => '8102',
+ opfmethod => 'brin', opfname => 'numeric_bloom_ops' },
{ oid => '4058',
opfmethod => 'brin', opfname => 'timetz_minmax_ops' },
+{ oid => '8103',
+ opfmethod => 'brin', opfname => 'timetz_bloom_ops' },
{ oid => '4059',
opfmethod => 'brin', opfname => 'datetime_minmax_ops' },
+{ oid => '8104',
+ opfmethod => 'brin', opfname => 'datetime_bloom_ops' },
{ oid => '4062',
opfmethod => 'brin', opfname => 'char_minmax_ops' },
+{ oid => '8105',
+ opfmethod => 'brin', opfname => 'char_bloom_ops' },
{ oid => '4064',
opfmethod => 'brin', opfname => 'bytea_minmax_ops' },
+{ oid => '8106',
+ opfmethod => 'brin', opfname => 'bytea_bloom_ops' },
{ oid => '4065',
opfmethod => 'brin', opfname => 'name_minmax_ops' },
+{ oid => '8107',
+ opfmethod => 'brin', opfname => 'name_bloom_ops' },
{ oid => '4068',
opfmethod => 'brin', opfname => 'oid_minmax_ops' },
+{ oid => '8108',
+ opfmethod => 'brin', opfname => 'oid_bloom_ops' },
{ oid => '4069',
opfmethod => 'brin', opfname => 'tid_minmax_ops' },
{ oid => '4070',
opfmethod => 'brin', opfname => 'float_minmax_ops' },
+{ oid => '8109',
+ opfmethod => 'brin', opfname => 'float_bloom_ops' },
{ oid => '4074',
opfmethod => 'brin', opfname => 'macaddr_minmax_ops' },
+{ oid => '8110',
+ opfmethod => 'brin', opfname => 'macaddr_bloom_ops' },
{ oid => '4109',
opfmethod => 'brin', opfname => 'macaddr8_minmax_ops' },
+{ oid => '8111',
+ opfmethod => 'brin', opfname => 'macaddr8_bloom_ops' },
{ oid => '4075',
opfmethod => 'brin', opfname => 'network_minmax_ops' },
{ oid => '4102',
opfmethod => 'brin', opfname => 'network_inclusion_ops' },
+{ oid => '8112',
+ opfmethod => 'brin', opfname => 'network_bloom_ops' },
{ oid => '4076',
opfmethod => 'brin', opfname => 'bpchar_minmax_ops' },
+{ oid => '8113',
+ opfmethod => 'brin', opfname => 'bpchar_bloom_ops' },
{ oid => '4077',
opfmethod => 'brin', opfname => 'time_minmax_ops' },
+{ oid => '8114',
+ opfmethod => 'brin', opfname => 'time_bloom_ops' },
{ oid => '4078',
opfmethod => 'brin', opfname => 'interval_minmax_ops' },
+{ oid => '8115',
+ opfmethod => 'brin', opfname => 'interval_bloom_ops' },
{ oid => '4079',
opfmethod => 'brin', opfname => 'bit_minmax_ops' },
{ oid => '4080',
opfmethod => 'brin', opfname => 'varbit_minmax_ops' },
{ oid => '4081',
opfmethod => 'brin', opfname => 'uuid_minmax_ops' },
+{ oid => '8116',
+ opfmethod => 'brin', opfname => 'uuid_bloom_ops' },
{ oid => '4103',
opfmethod => 'brin', opfname => 'range_inclusion_ops' },
{ oid => '4082',
opfmethod => 'brin', opfname => 'pg_lsn_minmax_ops' },
+{ oid => '8117',
+ opfmethod => 'brin', opfname => 'pg_lsn_bloom_ops' },
{ oid => '4104',
opfmethod => 'brin', opfname => 'box_inclusion_ops' },
{ oid => '5000',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 018a6490fb..5336aa7b9c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8109,6 +8109,26 @@
proargtypes => 'internal internal internal',
prosrc => 'brin_inclusion_union' },
+# BRIN bloom
+{ oid => '8118', descr => 'BRIN bloom support',
+ proname => 'brin_bloom_opcinfo', prorettype => 'internal',
+ proargtypes => 'internal', prosrc => 'brin_bloom_opcinfo' },
+{ oid => '8119', descr => 'BRIN bloom support',
+ proname => 'brin_bloom_add_value', prorettype => 'bool',
+ proargtypes => 'internal internal internal internal',
+ prosrc => 'brin_bloom_add_value' },
+{ oid => '8120', descr => 'BRIN bloom support',
+ proname => 'brin_bloom_consistent', prorettype => 'bool',
+ proargtypes => 'internal internal internal int4',
+ prosrc => 'brin_bloom_consistent' },
+{ oid => '8121', descr => 'BRIN bloom support',
+ proname => 'brin_bloom_union', prorettype => 'bool',
+ proargtypes => 'internal internal internal',
+ prosrc => 'brin_bloom_union' },
+{ oid => '8122', descr => 'BRIN bloom support',
+ proname => 'brin_bloom_options', prorettype => 'void', proisstrict => 'f',
+ proargtypes => 'internal', prosrc => 'brin_bloom_options' },
+
# userlock replacements
{ oid => '2880', descr => 'obtain exclusive advisory lock',
proname => 'pg_advisory_lock', provolatile => 'v', proparallel => 'r',
diff --git a/src/test/regress/expected/brin_bloom.out b/src/test/regress/expected/brin_bloom.out
new file mode 100644
index 0000000000..d58a4a483c
--- /dev/null
+++ b/src/test/regress/expected/brin_bloom.out
@@ -0,0 +1,456 @@
+CREATE TABLE brintest_bloom (byteacol bytea,
+ charcol "char",
+ namecol name,
+ int8col bigint,
+ int2col smallint,
+ int4col integer,
+ textcol text,
+ oidcol oid,
+ float4col real,
+ float8col double precision,
+ macaddrcol macaddr,
+ inetcol inet,
+ cidrcol cidr,
+ bpcharcol character,
+ datecol date,
+ timecol time without time zone,
+ timestampcol timestamp without time zone,
+ timestamptzcol timestamp with time zone,
+ intervalcol interval,
+ timetzcol time with time zone,
+ numericcol numeric,
+ uuidcol uuid,
+ lsncol pg_lsn
+) WITH (fillfactor=10);
+INSERT INTO brintest_bloom SELECT
+ repeat(stringu1, 8)::bytea,
+ substr(stringu1, 1, 1)::"char",
+ stringu1::name, 142857 * tenthous,
+ thousand,
+ twothousand,
+ repeat(stringu1, 8),
+ unique1::oid,
+ (four + 1.0)/(hundred+1),
+ odd::float8 / (tenthous + 1),
+ format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr,
+ inet '10.2.3.4/24' + tenthous,
+ cidr '10.2.3/24' + tenthous,
+ substr(stringu1, 1, 1)::bpchar,
+ date '1995-08-15' + tenthous,
+ time '01:20:30' + thousand * interval '18.5 second',
+ timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours',
+ timestamptz '1972-10-10 03:00' + thousand * interval '1 hour',
+ justify_days(justify_hours(tenthous * interval '12 minutes')),
+ timetz '01:30:20+02' + hundred * interval '15 seconds',
+ tenthous::numeric(36,30) * fivethous * even / (hundred + 1),
+ format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid,
+ format('%s/%s%s', odd, even, tenthous)::pg_lsn
+FROM tenk1 ORDER BY unique2 LIMIT 100;
+-- throw in some NULL's and different values
+INSERT INTO brintest_bloom (inetcol, cidrcol) SELECT
+ inet 'fe80::6e40:8ff:fea9:8c46' + tenthous,
+ cidr 'fe80::6e40:8ff:fea9:8c46' + tenthous
+FROM tenk1 ORDER BY thousand, tenthous LIMIT 25;
+-- test bloom specific index options
+-- ndistinct must be >= -1.0
+CREATE INDEX brinidx_bloom ON brintest_bloom USING brin (
+ byteacol bytea_bloom_ops(n_distinct_per_range = -1.1)
+);
+ERROR: value -1.1 out of bounds for option "n_distinct_per_range"
+DETAIL: Valid values are between "-1.000000" and "2147483647.000000".
+-- false_positive_rate must be between 0.001 and 1.0
+CREATE INDEX brinidx_bloom ON brintest_bloom USING brin (
+ byteacol bytea_bloom_ops(false_positive_rate = 0.0)
+);
+ERROR: value 0.0 out of bounds for option "false_positive_rate"
+DETAIL: Valid values are between "0.001000" and "1.000000".
+CREATE INDEX brinidx_bloom ON brintest_bloom USING brin (
+ byteacol bytea_bloom_ops(false_positive_rate = 1.01)
+);
+ERROR: value 1.01 out of bounds for option "false_positive_rate"
+DETAIL: Valid values are between "0.001000" and "1.000000".
+CREATE INDEX brinidx_bloom ON brintest_bloom USING brin (
+ byteacol bytea_bloom_ops,
+ charcol char_bloom_ops,
+ namecol name_bloom_ops,
+ int8col int8_bloom_ops,
+ int2col int2_bloom_ops,
+ int4col int4_bloom_ops,
+ textcol text_bloom_ops,
+ oidcol oid_bloom_ops,
+ float4col float4_bloom_ops,
+ float8col float8_bloom_ops,
+ macaddrcol macaddr_bloom_ops,
+ inetcol inet_bloom_ops,
+ cidrcol inet_bloom_ops,
+ bpcharcol bpchar_bloom_ops,
+ datecol date_bloom_ops,
+ timecol time_bloom_ops,
+ timestampcol timestamp_bloom_ops,
+ timestamptzcol timestamptz_bloom_ops,
+ intervalcol interval_bloom_ops,
+ timetzcol timetz_bloom_ops,
+ numericcol numeric_bloom_ops,
+ uuidcol uuid_bloom_ops,
+ lsncol pg_lsn_bloom_ops
+) with (pages_per_range = 1);
+CREATE TABLE brinopers_bloom (colname name, typ text,
+ op text[], value text[], matches int[],
+ check (cardinality(op) = cardinality(value)),
+ check (cardinality(op) = cardinality(matches)));
+INSERT INTO brinopers_bloom VALUES
+ ('byteacol', 'bytea',
+ '{=}',
+ '{BNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAA}',
+ '{1}'),
+ ('charcol', '"char"',
+ '{=}',
+ '{M}',
+ '{6}'),
+ ('namecol', 'name',
+ '{=}',
+ '{MAAAAA}',
+ '{2}'),
+ ('int2col', 'int2',
+ '{=}',
+ '{800}',
+ '{1}'),
+ ('int2col', 'int4',
+ '{=}',
+ '{800}',
+ '{1}'),
+ ('int2col', 'int8',
+ '{=}',
+ '{800}',
+ '{1}'),
+ ('int4col', 'int2',
+ '{=}',
+ '{800}',
+ '{1}'),
+ ('int4col', 'int4',
+ '{=}',
+ '{800}',
+ '{1}'),
+ ('int4col', 'int8',
+ '{=}',
+ '{800}',
+ '{1}'),
+ ('int8col', 'int8',
+ '{=}',
+ '{1257141600}',
+ '{1}'),
+ ('textcol', 'text',
+ '{=}',
+ '{BNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAA}',
+ '{1}'),
+ ('oidcol', 'oid',
+ '{=}',
+ '{8800}',
+ '{1}'),
+ ('float4col', 'float4',
+ '{=}',
+ '{1}',
+ '{4}'),
+ ('float4col', 'float8',
+ '{=}',
+ '{1}',
+ '{4}'),
+ ('float8col', 'float4',
+ '{=}',
+ '{0}',
+ '{1}'),
+ ('float8col', 'float8',
+ '{=}',
+ '{0}',
+ '{1}'),
+ ('macaddrcol', 'macaddr',
+ '{=}',
+ '{2c:00:2d:00:16:00}',
+ '{2}'),
+ ('inetcol', 'inet',
+ '{=}',
+ '{10.2.14.231/24}',
+ '{1}'),
+ ('inetcol', 'cidr',
+ '{=}',
+ '{fe80::6e40:8ff:fea9:8c46}',
+ '{1}'),
+ ('cidrcol', 'inet',
+ '{=}',
+ '{10.2.14/24}',
+ '{2}'),
+ ('cidrcol', 'inet',
+ '{=}',
+ '{fe80::6e40:8ff:fea9:8c46}',
+ '{1}'),
+ ('cidrcol', 'cidr',
+ '{=}',
+ '{10.2.14/24}',
+ '{2}'),
+ ('cidrcol', 'cidr',
+ '{=}',
+ '{fe80::6e40:8ff:fea9:8c46}',
+ '{1}'),
+ ('bpcharcol', 'bpchar',
+ '{=}',
+ '{W}',
+ '{6}'),
+ ('datecol', 'date',
+ '{=}',
+ '{2009-12-01}',
+ '{1}'),
+ ('timecol', 'time',
+ '{=}',
+ '{02:28:57}',
+ '{1}'),
+ ('timestampcol', 'timestamp',
+ '{=}',
+ '{1964-03-24 19:26:45}',
+ '{1}'),
+ ('timestampcol', 'timestamptz',
+ '{=}',
+ '{1964-03-24 19:26:45}',
+ '{1}'),
+ ('timestamptzcol', 'timestamptz',
+ '{=}',
+ '{1972-10-19 09:00:00-07}',
+ '{1}'),
+ ('intervalcol', 'interval',
+ '{=}',
+ '{1 mons 13 days 12:24}',
+ '{1}'),
+ ('timetzcol', 'timetz',
+ '{=}',
+ '{01:35:50+02}',
+ '{2}'),
+ ('numericcol', 'numeric',
+ '{=}',
+ '{2268164.347826086956521739130434782609}',
+ '{1}'),
+ ('uuidcol', 'uuid',
+ '{=}',
+ '{52225222-5222-5222-5222-522252225222}',
+ '{1}'),
+ ('lsncol', 'pg_lsn',
+ '{=, IS, IS NOT}',
+ '{44/455222, NULL, NULL}',
+ '{1, 25, 100}');
+DO $x$
+DECLARE
+ r record;
+ r2 record;
+ cond text;
+ idx_ctids tid[];
+ ss_ctids tid[];
+ count int;
+ plan_ok bool;
+ plan_line text;
+BEGIN
+ FOR r IN SELECT colname, oper, typ, value[ordinality], matches[ordinality] FROM brinopers_bloom, unnest(op) WITH ORDINALITY AS oper LOOP
+
+ -- prepare the condition
+ IF r.value IS NULL THEN
+ cond := format('%I %s %L', r.colname, r.oper, r.value);
+ ELSE
+ cond := format('%I %s %L::%s', r.colname, r.oper, r.value, r.typ);
+ END IF;
+
+ -- run the query using the brin index
+ SET enable_seqscan = 0;
+ SET enable_bitmapscan = 1;
+
+ plan_ok := false;
+ FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond) LOOP
+ IF plan_line LIKE '%Bitmap Heap Scan on brintest_bloom%' THEN
+ plan_ok := true;
+ END IF;
+ END LOOP;
+ IF NOT plan_ok THEN
+ RAISE WARNING 'did not get bitmap indexscan plan for %', r;
+ END IF;
+
+ EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond)
+ INTO idx_ctids;
+
+ -- run the query using a seqscan
+ SET enable_seqscan = 1;
+ SET enable_bitmapscan = 0;
+
+ plan_ok := false;
+ FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond) LOOP
+ IF plan_line LIKE '%Seq Scan on brintest_bloom%' THEN
+ plan_ok := true;
+ END IF;
+ END LOOP;
+ IF NOT plan_ok THEN
+ RAISE WARNING 'did not get seqscan plan for %', r;
+ END IF;
+
+ EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond)
+ INTO ss_ctids;
+
+ -- make sure both return the same results
+ count := array_length(idx_ctids, 1);
+
+ IF NOT (count = array_length(ss_ctids, 1) AND
+ idx_ctids @> ss_ctids AND
+ idx_ctids <@ ss_ctids) THEN
+ -- report the results of each scan to make the differences obvious
+ RAISE WARNING 'something not right in %: count %', r, count;
+ SET enable_seqscan = 1;
+ SET enable_bitmapscan = 0;
+ FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_bloom WHERE ' || cond LOOP
+ RAISE NOTICE 'seqscan: %', r2;
+ END LOOP;
+
+ SET enable_seqscan = 0;
+ SET enable_bitmapscan = 1;
+ FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_bloom WHERE ' || cond LOOP
+ RAISE NOTICE 'bitmapscan: %', r2;
+ END LOOP;
+ END IF;
+
+ -- make sure we found expected number of matches
+ IF count != r.matches THEN RAISE WARNING 'unexpected number of results % for %', count, r; END IF;
+ END LOOP;
+END;
+$x$;
+RESET enable_seqscan;
+RESET enable_bitmapscan;
+INSERT INTO brintest_bloom SELECT
+ repeat(stringu1, 42)::bytea,
+ substr(stringu1, 1, 1)::"char",
+ stringu1::name, 142857 * tenthous,
+ thousand,
+ twothousand,
+ repeat(stringu1, 42),
+ unique1::oid,
+ (four + 1.0)/(hundred+1),
+ odd::float8 / (tenthous + 1),
+ format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr,
+ inet '10.2.3.4' + tenthous,
+ cidr '10.2.3/24' + tenthous,
+ substr(stringu1, 1, 1)::bpchar,
+ date '1995-08-15' + tenthous,
+ time '01:20:30' + thousand * interval '18.5 second',
+ timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours',
+ timestamptz '1972-10-10 03:00' + thousand * interval '1 hour',
+ justify_days(justify_hours(tenthous * interval '12 minutes')),
+ timetz '01:30:20' + hundred * interval '15 seconds',
+ tenthous::numeric(36,30) * fivethous * even / (hundred + 1),
+ format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid,
+ format('%s/%s%s', odd, even, tenthous)::pg_lsn
+FROM tenk1 ORDER BY unique2 LIMIT 5 OFFSET 5;
+SELECT brin_desummarize_range('brinidx_bloom', 0);
+ brin_desummarize_range
+------------------------
+
+(1 row)
+
+VACUUM brintest_bloom; -- force a summarization cycle in brinidx
+UPDATE brintest_bloom SET int8col = int8col * int4col;
+UPDATE brintest_bloom SET textcol = '' WHERE textcol IS NOT NULL;
+-- Tests for brin_summarize_new_values
+SELECT brin_summarize_new_values('brintest_bloom'); -- error, not an index
+ERROR: "brintest_bloom" is not an index
+SELECT brin_summarize_new_values('tenk1_unique1'); -- error, not a BRIN index
+ERROR: "tenk1_unique1" is not a BRIN index
+SELECT brin_summarize_new_values('brinidx_bloom'); -- ok, no change expected
+ brin_summarize_new_values
+---------------------------
+ 0
+(1 row)
+
+-- Tests for brin_desummarize_range
+SELECT brin_desummarize_range('brinidx_bloom', -1); -- error, invalid range
+ERROR: block number out of range: -1
+SELECT brin_desummarize_range('brinidx_bloom', 0);
+ brin_desummarize_range
+------------------------
+
+(1 row)
+
+SELECT brin_desummarize_range('brinidx_bloom', 0);
+ brin_desummarize_range
+------------------------
+
+(1 row)
+
+SELECT brin_desummarize_range('brinidx_bloom', 100000000);
+ brin_desummarize_range
+------------------------
+
+(1 row)
+
+-- Test brin_summarize_range
+CREATE TABLE brin_summarize_bloom (
+ value int
+) WITH (fillfactor=10, autovacuum_enabled=false);
+CREATE INDEX brin_summarize_bloom_idx ON brin_summarize_bloom USING brin (value) WITH (pages_per_range=2);
+-- Fill a few pages
+DO $$
+DECLARE curtid tid;
+BEGIN
+ LOOP
+ INSERT INTO brin_summarize_bloom VALUES (1) RETURNING ctid INTO curtid;
+ EXIT WHEN curtid > tid '(2, 0)';
+ END LOOP;
+END;
+$$;
+-- summarize one range
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 0);
+ brin_summarize_range
+----------------------
+ 0
+(1 row)
+
+-- nothing: already summarized
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 1);
+ brin_summarize_range
+----------------------
+ 0
+(1 row)
+
+-- summarize one range
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 2);
+ brin_summarize_range
+----------------------
+ 1
+(1 row)
+
+-- nothing: page doesn't exist in table
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 4294967295);
+ brin_summarize_range
+----------------------
+ 0
+(1 row)
+
+-- invalid block number values
+SELECT brin_summarize_range('brin_summarize_bloom_idx', -1);
+ERROR: block number out of range: -1
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 4294967296);
+ERROR: block number out of range: 4294967296
+-- test brin cost estimates behave sanely based on correlation of values
+CREATE TABLE brin_test_bloom (a INT, b INT);
+INSERT INTO brin_test_bloom SELECT x/100,x%100 FROM generate_series(1,10000) x(x);
+CREATE INDEX brin_test_bloom_a_idx ON brin_test_bloom USING brin (a) WITH (pages_per_range = 2);
+CREATE INDEX brin_test_bloom_b_idx ON brin_test_bloom USING brin (b) WITH (pages_per_range = 2);
+VACUUM ANALYZE brin_test_bloom;
+-- Ensure brin index is used when columns are perfectly correlated
+EXPLAIN (COSTS OFF) SELECT * FROM brin_test_bloom WHERE a = 1;
+ QUERY PLAN
+--------------------------------------------------
+ Bitmap Heap Scan on brin_test_bloom
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on brin_test_bloom_a_idx
+ Index Cond: (a = 1)
+(4 rows)
+
+-- Ensure brin index is not used when values are not correlated
+EXPLAIN (COSTS OFF) SELECT * FROM brin_test_bloom WHERE b = 1;
+ QUERY PLAN
+-----------------------------
+ Seq Scan on brin_test_bloom
+ Filter: (b = 1)
+(2 rows)
+
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 1b3c146e4c..dca8e9eb34 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -2034,6 +2034,7 @@ ORDER BY 1, 2, 3;
2742 | 16 | @@
3580 | 1 | <
3580 | 1 | <<
+ 3580 | 1 | =
3580 | 2 | &<
3580 | 2 | <=
3580 | 3 | &&
@@ -2097,7 +2098,7 @@ ORDER BY 1, 2, 3;
4000 | 26 | >>
4000 | 27 | >>=
4000 | 28 | ^@
-(125 rows)
+(126 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 555d464f91..c1d19b1e0c 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4929,8 +4929,9 @@ List of access methods
List of operator classes
AM | Input type | Storage type | Operator class | Default?
------+------------+--------------+----------------+----------
+ brin | oid | | oid_bloom_ops | no
brin | oid | | oid_minmax_ops | yes
-(1 row)
+(2 rows)
\dAf spgist
List of operator families
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 026ea880cd..b49239b1d0 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -75,6 +75,11 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
# ----------
test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password identity generated join_hash
+# ----------
+# Additional BRIN tests
+# ----------
+test: brin_bloom
+
# ----------
# Another group of parallel tests
# ----------
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 979d926119..abce8e180a 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -107,6 +107,7 @@ test: delete
test: namespace
test: prepared_xacts
test: brin
+test: brin_bloom
test: gin
test: gist
test: spgist
diff --git a/src/test/regress/sql/brin_bloom.sql b/src/test/regress/sql/brin_bloom.sql
new file mode 100644
index 0000000000..abd5a33deb
--- /dev/null
+++ b/src/test/regress/sql/brin_bloom.sql
@@ -0,0 +1,404 @@
+CREATE TABLE brintest_bloom (byteacol bytea,
+ charcol "char",
+ namecol name,
+ int8col bigint,
+ int2col smallint,
+ int4col integer,
+ textcol text,
+ oidcol oid,
+ float4col real,
+ float8col double precision,
+ macaddrcol macaddr,
+ inetcol inet,
+ cidrcol cidr,
+ bpcharcol character,
+ datecol date,
+ timecol time without time zone,
+ timestampcol timestamp without time zone,
+ timestamptzcol timestamp with time zone,
+ intervalcol interval,
+ timetzcol time with time zone,
+ numericcol numeric,
+ uuidcol uuid,
+ lsncol pg_lsn
+) WITH (fillfactor=10);
+
+INSERT INTO brintest_bloom SELECT
+ repeat(stringu1, 8)::bytea,
+ substr(stringu1, 1, 1)::"char",
+ stringu1::name, 142857 * tenthous,
+ thousand,
+ twothousand,
+ repeat(stringu1, 8),
+ unique1::oid,
+ (four + 1.0)/(hundred+1),
+ odd::float8 / (tenthous + 1),
+ format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr,
+ inet '10.2.3.4/24' + tenthous,
+ cidr '10.2.3/24' + tenthous,
+ substr(stringu1, 1, 1)::bpchar,
+ date '1995-08-15' + tenthous,
+ time '01:20:30' + thousand * interval '18.5 second',
+ timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours',
+ timestamptz '1972-10-10 03:00' + thousand * interval '1 hour',
+ justify_days(justify_hours(tenthous * interval '12 minutes')),
+ timetz '01:30:20+02' + hundred * interval '15 seconds',
+ tenthous::numeric(36,30) * fivethous * even / (hundred + 1),
+ format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid,
+ format('%s/%s%s', odd, even, tenthous)::pg_lsn
+FROM tenk1 ORDER BY unique2 LIMIT 100;
+
+-- throw in some NULL's and different values
+INSERT INTO brintest_bloom (inetcol, cidrcol) SELECT
+ inet 'fe80::6e40:8ff:fea9:8c46' + tenthous,
+ cidr 'fe80::6e40:8ff:fea9:8c46' + tenthous
+FROM tenk1 ORDER BY thousand, tenthous LIMIT 25;
+
+-- test bloom specific index options
+-- ndistinct must be >= -1.0
+CREATE INDEX brinidx_bloom ON brintest_bloom USING brin (
+ byteacol bytea_bloom_ops(n_distinct_per_range = -1.1)
+);
+-- false_positive_rate must be between 0.001 and 1.0
+CREATE INDEX brinidx_bloom ON brintest_bloom USING brin (
+ byteacol bytea_bloom_ops(false_positive_rate = 0.0)
+);
+CREATE INDEX brinidx_bloom ON brintest_bloom USING brin (
+ byteacol bytea_bloom_ops(false_positive_rate = 1.01)
+);
+
+CREATE INDEX brinidx_bloom ON brintest_bloom USING brin (
+ byteacol bytea_bloom_ops,
+ charcol char_bloom_ops,
+ namecol name_bloom_ops,
+ int8col int8_bloom_ops,
+ int2col int2_bloom_ops,
+ int4col int4_bloom_ops,
+ textcol text_bloom_ops,
+ oidcol oid_bloom_ops,
+ float4col float4_bloom_ops,
+ float8col float8_bloom_ops,
+ macaddrcol macaddr_bloom_ops,
+ inetcol inet_bloom_ops,
+ cidrcol inet_bloom_ops,
+ bpcharcol bpchar_bloom_ops,
+ datecol date_bloom_ops,
+ timecol time_bloom_ops,
+ timestampcol timestamp_bloom_ops,
+ timestamptzcol timestamptz_bloom_ops,
+ intervalcol interval_bloom_ops,
+ timetzcol timetz_bloom_ops,
+ numericcol numeric_bloom_ops,
+ uuidcol uuid_bloom_ops,
+ lsncol pg_lsn_bloom_ops
+) with (pages_per_range = 1);
+
+CREATE TABLE brinopers_bloom (colname name, typ text,
+ op text[], value text[], matches int[],
+ check (cardinality(op) = cardinality(value)),
+ check (cardinality(op) = cardinality(matches)));
+
+INSERT INTO brinopers_bloom VALUES
+ ('byteacol', 'bytea',
+ '{=}',
+ '{BNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAA}',
+ '{1}'),
+ ('charcol', '"char"',
+ '{=}',
+ '{M}',
+ '{6}'),
+ ('namecol', 'name',
+ '{=}',
+ '{MAAAAA}',
+ '{2}'),
+ ('int2col', 'int2',
+ '{=}',
+ '{800}',
+ '{1}'),
+ ('int2col', 'int4',
+ '{=}',
+ '{800}',
+ '{1}'),
+ ('int2col', 'int8',
+ '{=}',
+ '{800}',
+ '{1}'),
+ ('int4col', 'int2',
+ '{=}',
+ '{800}',
+ '{1}'),
+ ('int4col', 'int4',
+ '{=}',
+ '{800}',
+ '{1}'),
+ ('int4col', 'int8',
+ '{=}',
+ '{800}',
+ '{1}'),
+ ('int8col', 'int8',
+ '{=}',
+ '{1257141600}',
+ '{1}'),
+ ('textcol', 'text',
+ '{=}',
+ '{BNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAA}',
+ '{1}'),
+ ('oidcol', 'oid',
+ '{=}',
+ '{8800}',
+ '{1}'),
+ ('float4col', 'float4',
+ '{=}',
+ '{1}',
+ '{4}'),
+ ('float4col', 'float8',
+ '{=}',
+ '{1}',
+ '{4}'),
+ ('float8col', 'float4',
+ '{=}',
+ '{0}',
+ '{1}'),
+ ('float8col', 'float8',
+ '{=}',
+ '{0}',
+ '{1}'),
+ ('macaddrcol', 'macaddr',
+ '{=}',
+ '{2c:00:2d:00:16:00}',
+ '{2}'),
+ ('inetcol', 'inet',
+ '{=}',
+ '{10.2.14.231/24}',
+ '{1}'),
+ ('inetcol', 'cidr',
+ '{=}',
+ '{fe80::6e40:8ff:fea9:8c46}',
+ '{1}'),
+ ('cidrcol', 'inet',
+ '{=}',
+ '{10.2.14/24}',
+ '{2}'),
+ ('cidrcol', 'inet',
+ '{=}',
+ '{fe80::6e40:8ff:fea9:8c46}',
+ '{1}'),
+ ('cidrcol', 'cidr',
+ '{=}',
+ '{10.2.14/24}',
+ '{2}'),
+ ('cidrcol', 'cidr',
+ '{=}',
+ '{fe80::6e40:8ff:fea9:8c46}',
+ '{1}'),
+ ('bpcharcol', 'bpchar',
+ '{=}',
+ '{W}',
+ '{6}'),
+ ('datecol', 'date',
+ '{=}',
+ '{2009-12-01}',
+ '{1}'),
+ ('timecol', 'time',
+ '{=}',
+ '{02:28:57}',
+ '{1}'),
+ ('timestampcol', 'timestamp',
+ '{=}',
+ '{1964-03-24 19:26:45}',
+ '{1}'),
+ ('timestampcol', 'timestamptz',
+ '{=}',
+ '{1964-03-24 19:26:45}',
+ '{1}'),
+ ('timestamptzcol', 'timestamptz',
+ '{=}',
+ '{1972-10-19 09:00:00-07}',
+ '{1}'),
+ ('intervalcol', 'interval',
+ '{=}',
+ '{1 mons 13 days 12:24}',
+ '{1}'),
+ ('timetzcol', 'timetz',
+ '{=}',
+ '{01:35:50+02}',
+ '{2}'),
+ ('numericcol', 'numeric',
+ '{=}',
+ '{2268164.347826086956521739130434782609}',
+ '{1}'),
+ ('uuidcol', 'uuid',
+ '{=}',
+ '{52225222-5222-5222-5222-522252225222}',
+ '{1}'),
+ ('lsncol', 'pg_lsn',
+ '{=, IS, IS NOT}',
+ '{44/455222, NULL, NULL}',
+ '{1, 25, 100}');
+
+DO $x$
+DECLARE
+ r record;
+ r2 record;
+ cond text;
+ idx_ctids tid[];
+ ss_ctids tid[];
+ count int;
+ plan_ok bool;
+ plan_line text;
+BEGIN
+ FOR r IN SELECT colname, oper, typ, value[ordinality], matches[ordinality] FROM brinopers_bloom, unnest(op) WITH ORDINALITY AS oper LOOP
+
+ -- prepare the condition
+ IF r.value IS NULL THEN
+ cond := format('%I %s %L', r.colname, r.oper, r.value);
+ ELSE
+ cond := format('%I %s %L::%s', r.colname, r.oper, r.value, r.typ);
+ END IF;
+
+ -- run the query using the brin index
+ SET enable_seqscan = 0;
+ SET enable_bitmapscan = 1;
+
+ plan_ok := false;
+ FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond) LOOP
+ IF plan_line LIKE '%Bitmap Heap Scan on brintest_bloom%' THEN
+ plan_ok := true;
+ END IF;
+ END LOOP;
+ IF NOT plan_ok THEN
+ RAISE WARNING 'did not get bitmap indexscan plan for %', r;
+ END IF;
+
+ EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond)
+ INTO idx_ctids;
+
+ -- run the query using a seqscan
+ SET enable_seqscan = 1;
+ SET enable_bitmapscan = 0;
+
+ plan_ok := false;
+ FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond) LOOP
+ IF plan_line LIKE '%Seq Scan on brintest_bloom%' THEN
+ plan_ok := true;
+ END IF;
+ END LOOP;
+ IF NOT plan_ok THEN
+ RAISE WARNING 'did not get seqscan plan for %', r;
+ END IF;
+
+ EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond)
+ INTO ss_ctids;
+
+ -- make sure both return the same results
+ count := array_length(idx_ctids, 1);
+
+ IF NOT (count = array_length(ss_ctids, 1) AND
+ idx_ctids @> ss_ctids AND
+ idx_ctids <@ ss_ctids) THEN
+ -- report the results of each scan to make the differences obvious
+ RAISE WARNING 'something not right in %: count %', r, count;
+ SET enable_seqscan = 1;
+ SET enable_bitmapscan = 0;
+ FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_bloom WHERE ' || cond LOOP
+ RAISE NOTICE 'seqscan: %', r2;
+ END LOOP;
+
+ SET enable_seqscan = 0;
+ SET enable_bitmapscan = 1;
+ FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_bloom WHERE ' || cond LOOP
+ RAISE NOTICE 'bitmapscan: %', r2;
+ END LOOP;
+ END IF;
+
+ -- make sure we found expected number of matches
+ IF count != r.matches THEN RAISE WARNING 'unexpected number of results % for %', count, r; END IF;
+ END LOOP;
+END;
+$x$;
+
+RESET enable_seqscan;
+RESET enable_bitmapscan;
+
+INSERT INTO brintest_bloom SELECT
+ repeat(stringu1, 42)::bytea,
+ substr(stringu1, 1, 1)::"char",
+ stringu1::name, 142857 * tenthous,
+ thousand,
+ twothousand,
+ repeat(stringu1, 42),
+ unique1::oid,
+ (four + 1.0)/(hundred+1),
+ odd::float8 / (tenthous + 1),
+ format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr,
+ inet '10.2.3.4' + tenthous,
+ cidr '10.2.3/24' + tenthous,
+ substr(stringu1, 1, 1)::bpchar,
+ date '1995-08-15' + tenthous,
+ time '01:20:30' + thousand * interval '18.5 second',
+ timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours',
+ timestamptz '1972-10-10 03:00' + thousand * interval '1 hour',
+ justify_days(justify_hours(tenthous * interval '12 minutes')),
+ timetz '01:30:20' + hundred * interval '15 seconds',
+ tenthous::numeric(36,30) * fivethous * even / (hundred + 1),
+ format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid,
+ format('%s/%s%s', odd, even, tenthous)::pg_lsn
+FROM tenk1 ORDER BY unique2 LIMIT 5 OFFSET 5;
+
+SELECT brin_desummarize_range('brinidx_bloom', 0);
+VACUUM brintest_bloom; -- force a summarization cycle in brinidx
+
+UPDATE brintest_bloom SET int8col = int8col * int4col;
+UPDATE brintest_bloom SET textcol = '' WHERE textcol IS NOT NULL;
+
+-- Tests for brin_summarize_new_values
+SELECT brin_summarize_new_values('brintest_bloom'); -- error, not an index
+SELECT brin_summarize_new_values('tenk1_unique1'); -- error, not a BRIN index
+SELECT brin_summarize_new_values('brinidx_bloom'); -- ok, no change expected
+
+-- Tests for brin_desummarize_range
+SELECT brin_desummarize_range('brinidx_bloom', -1); -- error, invalid range
+SELECT brin_desummarize_range('brinidx_bloom', 0);
+SELECT brin_desummarize_range('brinidx_bloom', 0);
+SELECT brin_desummarize_range('brinidx_bloom', 100000000);
+
+-- Test brin_summarize_range
+CREATE TABLE brin_summarize_bloom (
+ value int
+) WITH (fillfactor=10, autovacuum_enabled=false);
+CREATE INDEX brin_summarize_bloom_idx ON brin_summarize_bloom USING brin (value) WITH (pages_per_range=2);
+-- Fill a few pages
+DO $$
+DECLARE curtid tid;
+BEGIN
+ LOOP
+ INSERT INTO brin_summarize_bloom VALUES (1) RETURNING ctid INTO curtid;
+ EXIT WHEN curtid > tid '(2, 0)';
+ END LOOP;
+END;
+$$;
+
+-- summarize one range
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 0);
+-- nothing: already summarized
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 1);
+-- summarize one range
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 2);
+-- nothing: page doesn't exist in table
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 4294967295);
+-- invalid block number values
+SELECT brin_summarize_range('brin_summarize_bloom_idx', -1);
+SELECT brin_summarize_range('brin_summarize_bloom_idx', 4294967296);
+
+
+-- test brin cost estimates behave sanely based on correlation of values
+CREATE TABLE brin_test_bloom (a INT, b INT);
+INSERT INTO brin_test_bloom SELECT x/100,x%100 FROM generate_series(1,10000) x(x);
+CREATE INDEX brin_test_bloom_a_idx ON brin_test_bloom USING brin (a) WITH (pages_per_range = 2);
+CREATE INDEX brin_test_bloom_b_idx ON brin_test_bloom USING brin (b) WITH (pages_per_range = 2);
+VACUUM ANALYZE brin_test_bloom;
+
+-- Ensure brin index is used when columns are perfectly correlated
+EXPLAIN (COSTS OFF) SELECT * FROM brin_test_bloom WHERE a = 1;
+-- Ensure brin index is not used when values are not correlated
+EXPLAIN (COSTS OFF) SELECT * FROM brin_test_bloom WHERE b = 1;
--
2.25.4
--jsivyprvf2oxfjz3
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment;
filename="0005-add-special-pg_brin_bloom_summary-data-type-20200807.patch"
^ permalink raw reply [nested|flat] 194+ messages in thread
* [PoC] Federated Authn/z with OAUTHBEARER
@ 2021-06-08 16:37 Jacob Champion <[email protected]>
2021-06-18 04:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Michael Paquier <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
0 siblings, 2 replies; 194+ messages in thread
From: Jacob Champion @ 2021-06-08 16:37 UTC (permalink / raw)
To: pgsql-hackers
Hi all,
We've been working on ways to expand the list of third-party auth
methods that Postgres provides. Some example use cases might be "I want
to let anyone with a Google account read this table" or "let anyone who
belongs to this GitHub organization connect as a superuser".
Attached is a proof of concept that implements pieces of OAuth 2.0
federated authorization, via the OAUTHBEARER SASL mechanism from RFC
7628 [1]. Currently, only Linux is supported due to some ugly hacks in
the backend.
The architecture can support the following use cases, as long as your
OAuth issuer of choice implements the necessary specs, and you know how
to write a validator for your issuer's bearer tokens:
- Authentication only, where an external validator uses the bearer
token to determine the end user's identity, and Postgres decides
whether that user ID is authorized to connect via the standard pg_ident
user mapping.
- Authorization only, where the validator uses the bearer token to
determine the allowed roles for the end user, and then checks to make
sure that the connection's role is one of those. This bypasses pg_ident
and allows pseudonymous connections, where Postgres doesn't care who
you are as long as the token proves you're allowed to assume the role
you want.
- A combination, where the validator provides both an authn_id (for
later audits of database access) and an authorization decision based on
the bearer token and role provided.
It looks kinda like this during use:
$ psql 'host=example.org oauth_client_id=f02c6361-0635-...'
Visit https://oauth.example.org/login and enter the code: FPQ2-M4BG
= Quickstart =
For anyone who likes building and seeing green tests ASAP.
Prerequisite software:
- iddawc v0.9.9 [2], library and dev headers, for client support
- Python 3, for the test suite only
(Some newer distributions have dev packages for iddawc, but mine did
not.)
Configure using --with-oauth (and, if you've installed iddawc into a
non-standard location, be sure to use --with-includes and --with-
libraries. Make sure either rpath or LD_LIBRARY_PATH will get you what
you need). Install as usual.
To run the test suite, make sure the contrib/authn_id extension is
installed, then init and start your dev cluster. No other configuration
is required; the test will do it for you. Switch to the src/test/python
directory, point your PG* envvars to a superuser connection on the
cluster (so that a "bare" psql will connect automatically), and run
`make installcheck`.
= Production Setup =
(but don't use this in production, please)
Actually setting up a "real" system requires knowing the specifics of
your third-party issuer of choice. Your issuer MUST implement OpenID
Discovery and the OAuth Device Authorization flow! Seriously, check
this before spending a lot of time writing a validator against an
issuer that can't actually talk to libpq.
The broad strokes are as follows:
1. Register a new public client with your issuer to get an OAuth client
ID for libpq. You'll use this as the oauth_client_id in the connection
string. (If your issuer doesn't support public clients and gives you a
client secret, you can use the oauth_client_secret connection parameter
to provide that too.)
The client you register must be able to use a device authorization
flow; some issuers require additional setup for that.
2. Set up your HBA with the 'oauth' auth method, and set the 'issuer'
and 'scope' options. 'issuer' is the base URL identifying your third-
party issuer (for example, https://accounts.google.com), and 'scope' is
the set of OAuth scopes that the client and server will need to
authenticate and/or authorize the user (e.g. "openid email").
So a sample HBA line might look like
host all all samehost oauth issuer="https://accounts.google.com"; scope="openid email"
3. In postgresql.conf, set up an oauth_validator_command that's capable
of verifying bearer tokens and implements the validator protocol. This
is the hardest part. See below.
= Design =
On the client side, I've implemented the Device Authorization flow (RFC
8628, [3]). What this means in practice is that libpq reaches out to a
third-party issuer (e.g. Google, Azure, etc.), identifies itself with a
client ID, and requests permission to act on behalf of the end user.
The issuer responds with a login URL and a one-time code, which libpq
presents to the user using the notice hook. The end user then navigates
to that URL, presents their code, authenticates to the issuer, and
grants permission for libpq to retrieve a bearer token. libpq grabs a
token and sends it to the server for verification.
(The bearer token, in this setup, is essentially a plaintext password,
and you must secure it like you would a plaintext password. The token
has an expiration date and can be explicitly revoked, which makes it
slightly better than a password, but this is still a step backwards
from something like SCRAM with channel binding. There are ways to bind
a bearer token to a client certificate [4], which would mitigate the
risk of token theft -- but your issuer has to support that, and I
haven't found much support in the wild.)
The server side is where things get more difficult for the DBA. The
OAUTHBEARER spec has this to say about the server side implementation:
The server validates the response according to the specification for
the OAuth Access Token Types used.
And here's what the Bearer Token specification [5] says:
This document does not specify the encoding or the contents of the
token; hence, detailed recommendations about the means of
guaranteeing token integrity protection are outside the scope of
this document.
It's the Wild West. Every issuer does their own thing in their own
special way. Some don't really give you a way to introspect information
about a bearer token at all, because they assume that the issuer of the
token and the consumer of the token are essentially the same service.
Some major players provide their own custom libraries, implemented in
your-language-of-choice, to deal with their particular brand of magic.
So I punted and added the oauth_validator_command GUC. A token
validator command reads the bearer token from a file descriptor that's
passed to it, then does whatever magic is necessary to validate that
token and find out who owns it. Optionally, it can look at the role
that's being connected and make sure that the token authorizes the user
to actually use that role. Then it says yea or nay to Postgres, and
optionally tells the server who the user is so that their ID can be
logged and mapped through pg_ident.
(See the commit message in 0005 for a full description of the protocol.
The test suite also has two toy implementations that illustrate the
protocol, but they provide zero security.)
This is easily the worst part of the patch, not only because my
implementation is a bad hack on OpenPipeStream(), but because it
balances the security of the entire system on the shoulders of a DBA
who does not have time to read umpteen OAuth specifications cover to
cover. More thought and coding effort is needed here, but I didn't want
to gold-plate a bad design. I'm not sure what alternatives there are
within the rules laid out by OAUTHBEARER. And the system is _extremely_
flexible, in the way that only code that's maintained by somebody else
can be.
= Patchset Roadmap =
The seven patches can be grouped into three:
1. Prep
0001 decouples the SASL code from the SCRAM implementation.
0002 makes it possible to use common/jsonapi from the frontend.
0003 lets the json_errdetail() result be freed, to avoid leaks.
2. OAUTHBEARER Implementation
0004 implements the client with libiddawc.
0005 implements server HBA support and oauth_validator_command.
3. Testing
0006 adds a simple test extension to retrieve the authn_id.
0007 adds the Python test suite I've been developing against.
The first three patches are, hopefully, generally useful outside of
this implementation, and I'll plan to register them in the next
commitfest. The middle two patches are the "interesting" pieces, and
I've split them into client and server for ease of understanding,
though neither is particularly useful without the other.
The last two patches grew out of a test suite that I originally built
to be able to exercise NSS corner cases at the protocol/byte level. It
was incredibly helpful during implementation of this new SASL
mechanism, since I could write the client and server independently of
each other and get high coverage of broken/malicious implementations.
It's based on pytest and Construct, and the Python 3 requirement might
turn some away, but I wanted to include it in case anyone else wanted
to hack on the code. src/test/python/README explains more.
= Thoughts/Reflections =
...in no particular order.
I picked OAuth 2.0 as my first experiment in federated auth mostly
because I was already familiar with pieces of it. I think SAML (via the
SAML20 mechanism, RFC 6595) would be a good companion to this proof of
concept, if there is general interest in federated deployments.
I don't really like the OAUTHBEARER spec, but I'm not sure there's a
better alternative. Everything is left as an exercise for the reader.
It's not particularly extensible. Standard OAuth is built for
authorization, not authentication, and from reading the RFC's history,
it feels like it was a hack to just get something working. New
standards like OpenID Connect have begun to fill in the gaps, but the
SASL mechanisms have not kept up. (The OPENID20 mechanism is, to my
understanding, unrelated/obsolete.) And support for helpful OIDC
features seems to be spotty in the real world.
The iddawc dependency for client-side OAuth was extremely helpful to
develop this proof of concept quickly, but I don't think it would be an
appropriate component to build a real feature on. It's extremely
heavyweight -- it incorporates a huge stack of dependencies, including
a logging framework and a web server, to implement features we would
probably never use -- and it's fairly difficult to debug in practice.
If a device authorization flow were the only thing that libpq needed to
support natively, I think we should just depend on a widely used HTTP
client, like libcurl or neon, and implement the minimum spec directly
against the existing test suite.
There are a huge number of other authorization flows besides Device
Authorization; most would involve libpq automatically opening a web
browser for you. I felt like that wasn't an appropriate thing for a
library to do by default, especially when one of the most important
clients is a command-line application. Perhaps there could be a hook
for applications to be able to override the builtin flow and substitute
their own.
Since bearer tokens are essentially plaintext passwords, the relevant
specs require the use of transport-level protection, and I think it'd
be wise for the client to require TLS to be in place before performing
the initial handshake or sending a token.
Not every OAuth issuer is also an OpenID Discovery provider, so it's
frustrating that OAUTHBEARER (which is purportedly an OAuth 2.0
feature) requires OIDD for real-world implementations. Perhaps we could
hack around this with a data: URI or something.
The client currently performs the OAuth login dance every single time a
connection is made, but a proper OAuth client would cache its tokens to
reuse later, and keep an eye on their expiration times. This would make
daily use a little more like that of Kerberos, but we would have to
design a way to create and secure a token cache on disk.
If you've read this far, thank you for your interest, and I hope you
enjoy playing with it!
--Jacob
[1] https://datatracker.ietf.org/doc/html/rfc7628
[2] https://github.com/babelouest/iddawc
[3] https://datatracker.ietf.org/doc/html/rfc8628
[4] https://datatracker.ietf.org/doc/html/rfc8705
[5] https://datatracker.ietf.org/doc/html/rfc6750#section-5.2
Attachments:
[text/x-patch] 0001-auth-generalize-SASL-mechanisms.patch (16.3K, ../../[email protected]/2-0001-auth-generalize-SASL-mechanisms.patch)
download | inline diff:
From a6a65b66cc3dc5da7219378dbadb090ff10fd42b Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Tue, 13 Apr 2021 10:25:48 -0700
Subject: [PATCH 1/7] auth: generalize SASL mechanisms
Split the SASL logic out from the SCRAM implementation, so that it can
be reused by other mechanisms. New implementations will implement both
a pg_sasl_mech and a pg_be_sasl_mech.
---
src/backend/libpq/auth-scram.c | 34 ++++++++++++++---------
src/backend/libpq/auth.c | 34 ++++++++++++++++-------
src/include/libpq/sasl.h | 34 +++++++++++++++++++++++
src/include/libpq/scram.h | 13 +++------
src/interfaces/libpq/fe-auth-scram.c | 40 +++++++++++++++++++---------
src/interfaces/libpq/fe-auth.c | 16 ++++++++---
src/interfaces/libpq/fe-auth.h | 11 ++------
src/interfaces/libpq/fe-connect.c | 6 +----
src/interfaces/libpq/libpq-int.h | 14 ++++++++++
9 files changed, 139 insertions(+), 63 deletions(-)
create mode 100644 src/include/libpq/sasl.h
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index f9e1026a12..db3ca75a60 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -101,11 +101,25 @@
#include "common/sha2.h"
#include "libpq/auth.h"
#include "libpq/crypt.h"
+#include "libpq/sasl.h"
#include "libpq/scram.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/timestamp.h"
+static void scram_get_mechanisms(Port *port, StringInfo buf);
+static void *scram_init(Port *port, const char *selected_mech,
+ const char *shadow_pass);
+static int scram_exchange(void *opaq, const char *input, int inputlen,
+ char **output, int *outputlen, char **logdetail);
+
+/* Mechanism declaration */
+const pg_be_sasl_mech pg_be_scram_mech = {
+ scram_get_mechanisms,
+ scram_init,
+ scram_exchange,
+};
+
/*
* Status data for a SCRAM authentication exchange. This should be kept
* internal to this file.
@@ -170,16 +184,14 @@ static char *sanitize_str(const char *s);
static char *scram_mock_salt(const char *username);
/*
- * pg_be_scram_get_mechanisms
- *
* Get a list of SASL mechanisms that this module supports.
*
* For the convenience of building the FE/BE packet that lists the
* mechanisms, the names are appended to the given StringInfo buffer,
* separated by '\0' bytes.
*/
-void
-pg_be_scram_get_mechanisms(Port *port, StringInfo buf)
+static void
+scram_get_mechanisms(Port *port, StringInfo buf)
{
/*
* Advertise the mechanisms in decreasing order of importance. So the
@@ -199,8 +211,6 @@ pg_be_scram_get_mechanisms(Port *port, StringInfo buf)
}
/*
- * pg_be_scram_init
- *
* Initialize a new SCRAM authentication exchange status tracker. This
* needs to be called before doing any exchange. It will be filled later
* after the beginning of the exchange with authentication information.
@@ -215,10 +225,8 @@ pg_be_scram_get_mechanisms(Port *port, StringInfo buf)
* an authentication exchange, but it will fail, as if an incorrect password
* was given.
*/
-void *
-pg_be_scram_init(Port *port,
- const char *selected_mech,
- const char *shadow_pass)
+static void *
+scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
{
scram_state *state;
bool got_secret;
@@ -325,9 +333,9 @@ pg_be_scram_init(Port *port,
* string at *logdetail that will be sent to the postmaster log (but not
* the client).
*/
-int
-pg_be_scram_exchange(void *opaq, const char *input, int inputlen,
- char **output, int *outputlen, char **logdetail)
+static int
+scram_exchange(void *opaq, const char *input, int inputlen,
+ char **output, int *outputlen, char **logdetail)
{
scram_state *state = (scram_state *) opaq;
int result;
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 68372fcea8..e20740a7c5 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -26,11 +26,11 @@
#include "commands/user.h"
#include "common/ip.h"
#include "common/md5.h"
-#include "common/scram-common.h"
#include "libpq/auth.h"
#include "libpq/crypt.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
+#include "libpq/sasl.h"
#include "libpq/scram.h"
#include "miscadmin.h"
#include "port/pg_bswap.h"
@@ -51,6 +51,13 @@ static void auth_failed(Port *port, int status, char *logdetail);
static char *recv_password_packet(Port *port);
static void set_authn_id(Port *port, const char *id);
+/*----------------------------------------------------------------
+ * SASL common authentication
+ *----------------------------------------------------------------
+ */
+static int SASL_exchange(const pg_be_sasl_mech *mech, Port *port,
+ char *shadow_pass, char **logdetail);
+
/*----------------------------------------------------------------
* Password-based authentication methods (password, md5, and scram-sha-256)
@@ -912,12 +919,13 @@ CheckMD5Auth(Port *port, char *shadow_pass, char **logdetail)
}
static int
-CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
+SASL_exchange(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
+ char **logdetail)
{
StringInfoData sasl_mechs;
int mtype;
StringInfoData buf;
- void *scram_opaq = NULL;
+ void *opaq = NULL;
char *output = NULL;
int outputlen = 0;
const char *input;
@@ -931,7 +939,7 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
*/
initStringInfo(&sasl_mechs);
- pg_be_scram_get_mechanisms(port, &sasl_mechs);
+ mech->get_mechanisms(port, &sasl_mechs);
/* Put another '\0' to mark that list is finished. */
appendStringInfoChar(&sasl_mechs, '\0');
@@ -998,7 +1006,7 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
* This is because we don't want to reveal to an attacker what
* usernames are valid, nor which users have a valid password.
*/
- scram_opaq = pg_be_scram_init(port, selected_mech, shadow_pass);
+ opaq = mech->init(port, selected_mech, shadow_pass);
inputlen = pq_getmsgint(&buf, 4);
if (inputlen == -1)
@@ -1022,12 +1030,11 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
Assert(input == NULL || input[inputlen] == '\0');
/*
- * we pass 'logdetail' as NULL when doing a mock authentication,
- * because we should already have a better error message in that case
+ * Hand the incoming message to the mechanism implementation.
*/
- result = pg_be_scram_exchange(scram_opaq, input, inputlen,
- &output, &outputlen,
- logdetail);
+ result = mech->exchange(opaq, input, inputlen,
+ &output, &outputlen,
+ logdetail);
/* input buffer no longer used */
pfree(buf.data);
@@ -1039,6 +1046,7 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
*/
elog(DEBUG4, "sending SASL challenge of length %u", outputlen);
+ /* TODO: SASL_EXCHANGE_FAILURE with output is forbidden in SASL */
if (result == SASL_EXCHANGE_SUCCESS)
sendAuthRequest(port, AUTH_REQ_SASL_FIN, output, outputlen);
else
@@ -1057,6 +1065,12 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
return STATUS_OK;
}
+static int
+CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
+{
+ return SASL_exchange(&pg_be_scram_mech, port, shadow_pass, logdetail);
+}
+
/*----------------------------------------------------------------
* GSSAPI authentication system
diff --git a/src/include/libpq/sasl.h b/src/include/libpq/sasl.h
new file mode 100644
index 0000000000..8c9c9983d4
--- /dev/null
+++ b/src/include/libpq/sasl.h
@@ -0,0 +1,34 @@
+/*-------------------------------------------------------------------------
+ *
+ * sasl.h
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/libpq/sasl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_SASL_H
+#define PG_SASL_H
+
+#include "libpq/libpq-be.h"
+
+/* Status codes for message exchange */
+#define SASL_EXCHANGE_CONTINUE 0
+#define SASL_EXCHANGE_SUCCESS 1
+#define SASL_EXCHANGE_FAILURE 2
+
+/* Backend mechanism API */
+typedef void (*pg_be_sasl_mechanism_func)(Port *, StringInfo);
+typedef void *(*pg_be_sasl_init_func)(Port *, const char *, const char *);
+typedef int (*pg_be_sasl_exchange_func)(void *, const char *, int, char **, int *, char **);
+
+typedef struct
+{
+ pg_be_sasl_mechanism_func get_mechanisms;
+ pg_be_sasl_init_func init;
+ pg_be_sasl_exchange_func exchange;
+} pg_be_sasl_mech;
+
+#endif /* PG_SASL_H */
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
index 2c879150da..9e4540bde3 100644
--- a/src/include/libpq/scram.h
+++ b/src/include/libpq/scram.h
@@ -15,17 +15,10 @@
#include "lib/stringinfo.h"
#include "libpq/libpq-be.h"
+#include "libpq/sasl.h"
-/* Status codes for message exchange */
-#define SASL_EXCHANGE_CONTINUE 0
-#define SASL_EXCHANGE_SUCCESS 1
-#define SASL_EXCHANGE_FAILURE 2
-
-/* Routines dedicated to authentication */
-extern void pg_be_scram_get_mechanisms(Port *port, StringInfo buf);
-extern void *pg_be_scram_init(Port *port, const char *selected_mech, const char *shadow_pass);
-extern int pg_be_scram_exchange(void *opaq, const char *input, int inputlen,
- char **output, int *outputlen, char **logdetail);
+/* Implementation */
+extern const pg_be_sasl_mech pg_be_scram_mech;
/* Routines to handle and check SCRAM-SHA-256 secret */
extern char *pg_be_scram_build_secret(const char *password);
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
index 5881386e37..04d5703d89 100644
--- a/src/interfaces/libpq/fe-auth-scram.c
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -21,6 +21,22 @@
#include "fe-auth.h"
+/* The exported SCRAM callback mechanism. */
+static void *scram_init(PGconn *conn, const char *password,
+ const char *sasl_mechanism);
+static void scram_exchange(void *opaq, char *input, int inputlen,
+ char **output, int *outputlen,
+ bool *done, bool *success);
+static bool scram_channel_bound(void *opaq);
+static void scram_free(void *opaq);
+
+const pg_sasl_mech pg_scram_mech = {
+ scram_init,
+ scram_exchange,
+ scram_channel_bound,
+ scram_free,
+};
+
/*
* Status of exchange messages used for SCRAM authentication via the
* SASL protocol.
@@ -72,10 +88,10 @@ static bool calculate_client_proof(fe_scram_state *state,
/*
* Initialize SCRAM exchange status.
*/
-void *
-pg_fe_scram_init(PGconn *conn,
- const char *password,
- const char *sasl_mechanism)
+static void *
+scram_init(PGconn *conn,
+ const char *password,
+ const char *sasl_mechanism)
{
fe_scram_state *state;
char *prep_password;
@@ -128,8 +144,8 @@ pg_fe_scram_init(PGconn *conn,
* Note that the caller must also ensure that the exchange was actually
* successful.
*/
-bool
-pg_fe_scram_channel_bound(void *opaq)
+static bool
+scram_channel_bound(void *opaq)
{
fe_scram_state *state = (fe_scram_state *) opaq;
@@ -152,8 +168,8 @@ pg_fe_scram_channel_bound(void *opaq)
/*
* Free SCRAM exchange status
*/
-void
-pg_fe_scram_free(void *opaq)
+static void
+scram_free(void *opaq)
{
fe_scram_state *state = (fe_scram_state *) opaq;
@@ -188,10 +204,10 @@ pg_fe_scram_free(void *opaq)
/*
* Exchange a SCRAM message with backend.
*/
-void
-pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
- char **output, int *outputlen,
- bool *done, bool *success)
+static void
+scram_exchange(void *opaq, char *input, int inputlen,
+ char **output, int *outputlen,
+ bool *done, bool *success)
{
fe_scram_state *state = (fe_scram_state *) opaq;
PGconn *conn = state->conn;
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index e8062647e6..d5cbac108e 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -482,7 +482,10 @@ pg_SASL_init(PGconn *conn, int payloadlen)
* channel_binding is not disabled.
*/
if (conn->channel_binding[0] != 'd') /* disable */
+ {
selected_mechanism = SCRAM_SHA_256_PLUS_NAME;
+ conn->sasl = &pg_scram_mech;
+ }
#else
/*
* The client does not support channel binding. If it is
@@ -516,7 +519,10 @@ pg_SASL_init(PGconn *conn, int payloadlen)
}
else if (strcmp(mechanism_buf.data, SCRAM_SHA_256_NAME) == 0 &&
!selected_mechanism)
+ {
selected_mechanism = SCRAM_SHA_256_NAME;
+ conn->sasl = &pg_scram_mech;
+ }
}
if (!selected_mechanism)
@@ -555,20 +561,22 @@ pg_SASL_init(PGconn *conn, int payloadlen)
goto error;
}
+ Assert(conn->sasl);
+
/*
* Initialize the SASL state information with all the information gathered
* during the initial exchange.
*
* Note: Only tls-unique is supported for the moment.
*/
- conn->sasl_state = pg_fe_scram_init(conn,
+ conn->sasl_state = conn->sasl->init(conn,
password,
selected_mechanism);
if (!conn->sasl_state)
goto oom_error;
/* Get the mechanism-specific Initial Client Response, if any */
- pg_fe_scram_exchange(conn->sasl_state,
+ conn->sasl->exchange(conn->sasl_state,
NULL, -1,
&initialresponse, &initialresponselen,
&done, &success);
@@ -649,7 +657,7 @@ pg_SASL_continue(PGconn *conn, int payloadlen, bool final)
/* For safety and convenience, ensure the buffer is NULL-terminated. */
challenge[payloadlen] = '\0';
- pg_fe_scram_exchange(conn->sasl_state,
+ conn->sasl->exchange(conn->sasl_state,
challenge, payloadlen,
&output, &outputlen,
&done, &success);
@@ -830,7 +838,7 @@ check_expected_areq(AuthRequest areq, PGconn *conn)
case AUTH_REQ_SASL_FIN:
break;
case AUTH_REQ_OK:
- if (!pg_fe_scram_channel_bound(conn->sasl_state))
+ if (!conn->sasl || !conn->sasl->channel_bound(conn->sasl_state))
{
appendPQExpBufferStr(&conn->errorMessage,
libpq_gettext("channel binding required, but server authenticated client without channel binding\n"));
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index 7877dcbd09..1e4fcbff62 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -22,15 +22,8 @@
extern int pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn);
extern char *pg_fe_getauthname(PQExpBuffer errorMessage);
-/* Prototypes for functions in fe-auth-scram.c */
-extern void *pg_fe_scram_init(PGconn *conn,
- const char *password,
- const char *sasl_mechanism);
-extern bool pg_fe_scram_channel_bound(void *opaq);
-extern void pg_fe_scram_free(void *opaq);
-extern void pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
- char **output, int *outputlen,
- bool *done, bool *success);
+/* Mechanisms in fe-auth-scram.c */
+extern const pg_sasl_mech pg_scram_mech;
extern char *pg_fe_scram_build_secret(const char *password);
#endif /* FE_AUTH_H */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 80703698b8..10d007582c 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -517,11 +517,7 @@ pqDropConnection(PGconn *conn, bool flushInput)
#endif
if (conn->sasl_state)
{
- /*
- * XXX: if support for more authentication mechanisms is added, this
- * needs to call the right 'free' function.
- */
- pg_fe_scram_free(conn->sasl_state);
+ conn->sasl->free(conn->sasl_state);
conn->sasl_state = NULL;
}
}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index e81dc37906..25eaa231c5 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -339,6 +339,19 @@ typedef struct pg_conn_host
* found in password file. */
} pg_conn_host;
+typedef void *(*pg_sasl_init_func)(PGconn *, const char *, const char *);
+typedef void (*pg_sasl_exchange_func)(void *, char *, int, char **, int *, bool *, bool *);
+typedef bool (*pg_sasl_channel_bound_func)(void *);
+typedef void (*pg_sasl_free_func)(void *);
+
+typedef struct
+{
+ pg_sasl_init_func init;
+ pg_sasl_exchange_func exchange;
+ pg_sasl_channel_bound_func channel_bound;
+ pg_sasl_free_func free;
+} pg_sasl_mech;
+
/*
* PGconn stores all the state data associated with a single connection
* to a backend.
@@ -500,6 +513,7 @@ struct pg_conn
PGresult *next_result; /* next result (used in single-row mode) */
/* Assorted state for SASL, SSL, GSS, etc */
+ const pg_sasl_mech *sasl;
void *sasl_state;
/* SSL structures */
--
2.25.1
[text/x-patch] 0002-src-common-remove-logging-from-jsonapi-for-shlib.patch (1.7K, ../../[email protected]/3-0002-src-common-remove-logging-from-jsonapi-for-shlib.patch)
download | inline diff:
From 0541598e4f0bad1b9ff41a4640ec69491b393d54 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Mon, 3 May 2021 11:15:15 -0700
Subject: [PATCH 2/7] src/common: remove logging from jsonapi for shlib
The can't-happen code in jsonapi was pulling in logging code, which for
libpq is not included.
---
src/common/Makefile | 4 ++++
src/common/jsonapi.c | 11 ++++++++---
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/src/common/Makefile b/src/common/Makefile
index 38a8599337..6f1039bc78 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -28,6 +28,10 @@ subdir = src/common
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
+# For use in shared libraries, jsonapi needs to not link in any logging
+# functions.
+override CFLAGS_SL += -DJSONAPI_NO_LOG
+
# don't include subdirectory-path-dependent -I and -L switches
STD_CPPFLAGS := $(filter-out -I$(top_srcdir)/src/include -I$(top_builddir)/src/include,$(CPPFLAGS))
STD_LDFLAGS := $(filter-out -L$(top_builddir)/src/common -L$(top_builddir)/src/port,$(LDFLAGS))
diff --git a/src/common/jsonapi.c b/src/common/jsonapi.c
index 1bf38d7b42..6b6001b118 100644
--- a/src/common/jsonapi.c
+++ b/src/common/jsonapi.c
@@ -27,11 +27,16 @@
#endif
#ifdef FRONTEND
-#define check_stack_depth()
-#define json_log_and_abort(...) \
+# define check_stack_depth()
+# ifdef JSONAPI_NO_LOG
+# define json_log_and_abort(...) \
+ do { fprintf(stderr, __VA_ARGS__); exit(1); } while(0)
+# else
+# define json_log_and_abort(...) \
do { pg_log_fatal(__VA_ARGS__); exit(1); } while(0)
+# endif
#else
-#define json_log_and_abort(...) elog(ERROR, __VA_ARGS__)
+# define json_log_and_abort(...) elog(ERROR, __VA_ARGS__)
#endif
/*
--
2.25.1
[text/x-patch] 0003-common-jsonapi-always-palloc-the-error-strings.patch (2.2K, ../../[email protected]/4-0003-common-jsonapi-always-palloc-the-error-strings.patch)
download | inline diff:
From 5ad4b3c7835fe9e0f284702ec7b827c27770854e Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Mon, 3 May 2021 15:38:26 -0700
Subject: [PATCH 3/7] common/jsonapi: always palloc the error strings
...so that client code can pfree() to avoid memory leaks in long-running
operations.
---
src/common/jsonapi.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/common/jsonapi.c b/src/common/jsonapi.c
index 6b6001b118..f7304f584f 100644
--- a/src/common/jsonapi.c
+++ b/src/common/jsonapi.c
@@ -1089,7 +1089,7 @@ json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
return psprintf(_("Expected JSON value, but found \"%s\"."),
extract_token(lex));
case JSON_EXPECTED_MORE:
- return _("The input string ended unexpectedly.");
+ return pstrdup(_("The input string ended unexpectedly."));
case JSON_EXPECTED_OBJECT_FIRST:
return psprintf(_("Expected string or \"}\", but found \"%s\"."),
extract_token(lex));
@@ -1103,16 +1103,16 @@ json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
return psprintf(_("Token \"%s\" is invalid."),
extract_token(lex));
case JSON_UNICODE_CODE_POINT_ZERO:
- return _("\\u0000 cannot be converted to text.");
+ return pstrdup(_("\\u0000 cannot be converted to text."));
case JSON_UNICODE_ESCAPE_FORMAT:
- return _("\"\\u\" must be followed by four hexadecimal digits.");
+ return pstrdup(_("\"\\u\" must be followed by four hexadecimal digits."));
case JSON_UNICODE_HIGH_ESCAPE:
/* note: this case is only reachable in frontend not backend */
- return _("Unicode escape values cannot be used for code point values above 007F when the encoding is not UTF8.");
+ return pstrdup(_("Unicode escape values cannot be used for code point values above 007F when the encoding is not UTF8."));
case JSON_UNICODE_HIGH_SURROGATE:
- return _("Unicode high surrogate must not follow a high surrogate.");
+ return pstrdup(_("Unicode high surrogate must not follow a high surrogate."));
case JSON_UNICODE_LOW_SURROGATE:
- return _("Unicode low surrogate must follow a high surrogate.");
+ return pstrdup(_("Unicode low surrogate must follow a high surrogate."));
}
/*
--
2.25.1
[text/x-patch] 0004-libpq-add-OAUTHBEARER-SASL-mechanism.patch (34.5K, ../../[email protected]/5-0004-libpq-add-OAUTHBEARER-SASL-mechanism.patch)
download | inline diff:
From e3d95709e147ae3670bd8acd0c265493a6116b9a Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Tue, 13 Apr 2021 10:27:27 -0700
Subject: [PATCH 4/7] libpq: add OAUTHBEARER SASL mechanism
DO NOT USE THIS PROOF OF CONCEPT IN PRODUCTION.
Implement OAUTHBEARER (RFC 7628) and OAuth 2.0 Device Authorization
Grants (RFC 8628) on the client side. When speaking to a OAuth-enabled
server, it looks a bit like this:
$ psql 'host=example.org oauth_client_id=f02c6361-0635-...'
Visit https://oauth.example.org/login and enter the code: FPQ2-M4BG
The OAuth issuer must support device authorization. No other OAuth flows
are currently implemented.
The client implementation requires libiddawc and its development
headers. Configure --with-oauth (and --with-includes/--with-libraries to
point at the iddawc installation, if it's in a custom location).
Several TODOs:
- don't retry forever if the server won't accept our token
- perform several sanity checks on the OAuth issuer's responses
- handle cases where the client has been set up with an issuer and
scope, but the Postgres server wants to use something different
- improve error debuggability during the OAuth handshake
- ...and more.
---
configure | 100 ++++
configure.ac | 19 +
src/Makefile.global.in | 1 +
src/include/common/oauth-common.h | 19 +
src/include/pg_config.h.in | 6 +
src/interfaces/libpq/Makefile | 7 +-
src/interfaces/libpq/fe-auth-oauth.c | 724 +++++++++++++++++++++++++++
src/interfaces/libpq/fe-auth-scram.c | 6 +-
src/interfaces/libpq/fe-auth.c | 42 +-
src/interfaces/libpq/fe-auth.h | 3 +
src/interfaces/libpq/fe-connect.c | 38 ++
src/interfaces/libpq/libpq-int.h | 10 +-
12 files changed, 956 insertions(+), 19 deletions(-)
create mode 100644 src/include/common/oauth-common.h
create mode 100644 src/interfaces/libpq/fe-auth-oauth.c
diff --git a/configure b/configure
index e9b98f442f..c3b7a89bf0 100755
--- a/configure
+++ b/configure
@@ -713,6 +713,7 @@ with_uuid
with_readline
with_systemd
with_selinux
+with_oauth
with_ldap
with_krb_srvnam
krb_srvtab
@@ -856,6 +857,7 @@ with_krb_srvnam
with_pam
with_bsd_auth
with_ldap
+with_oauth
with_bonjour
with_selinux
with_systemd
@@ -1562,6 +1564,7 @@ Optional Packages:
--with-pam build with PAM support
--with-bsd-auth build with BSD Authentication support
--with-ldap build with LDAP support
+ --with-oauth build with OAuth 2.0 support
--with-bonjour build with Bonjour support
--with-selinux build with SELinux support
--with-systemd build with systemd support
@@ -8046,6 +8049,42 @@ $as_echo "$with_ldap" >&6; }
+#
+# OAuth 2.0
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with OAuth support" >&5
+$as_echo_n "checking whether to build with OAuth support... " >&6; }
+
+
+
+# Check whether --with-oauth was given.
+if test "${with_oauth+set}" = set; then :
+ withval=$with_oauth;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_OAUTH 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-oauth option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_oauth=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_oauth" >&5
+$as_echo "$with_oauth" >&6; }
+
+
+
#
# Bonjour
#
@@ -13048,6 +13087,56 @@ fi
+if test "$with_oauth" = yes ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for i_init_session in -liddawc" >&5
+$as_echo_n "checking for i_init_session in -liddawc... " >&6; }
+if ${ac_cv_lib_iddawc_i_init_session+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-liddawc $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char i_init_session ();
+int
+main ()
+{
+return i_init_session ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_iddawc_i_init_session=yes
+else
+ ac_cv_lib_iddawc_i_init_session=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_iddawc_i_init_session" >&5
+$as_echo "$ac_cv_lib_iddawc_i_init_session" >&6; }
+if test "x$ac_cv_lib_iddawc_i_init_session" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBIDDAWC 1
+_ACEOF
+
+ LIBS="-liddawc $LIBS"
+
+else
+ as_fn_error $? "library 'iddawc' is required for OAuth support" "$LINENO" 5
+fi
+
+fi
+
# for contrib/sepgsql
if test "$with_selinux" = yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for security_compute_create_name in -lselinux" >&5
@@ -13942,6 +14031,17 @@ fi
done
+fi
+
+if test "$with_oauth" != no; then
+ ac_fn_c_check_header_mongrel "$LINENO" "iddawc.h" "ac_cv_header_iddawc_h" "$ac_includes_default"
+if test "x$ac_cv_header_iddawc_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <iddawc.h> is required for OAuth" "$LINENO" 5
+fi
+
+
fi
if test "$PORTNAME" = "win32" ; then
diff --git a/configure.ac b/configure.ac
index 3b42d8bdc9..f15f6f64d5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -842,6 +842,17 @@ AC_MSG_RESULT([$with_ldap])
AC_SUBST(with_ldap)
+#
+# OAuth 2.0
+#
+AC_MSG_CHECKING([whether to build with OAuth support])
+PGAC_ARG_BOOL(with, oauth, no,
+ [build with OAuth 2.0 support],
+ [AC_DEFINE([USE_OAUTH], 1, [Define to 1 to build with OAuth 2.0 support. (--with-oauth)])])
+AC_MSG_RESULT([$with_oauth])
+AC_SUBST(with_oauth)
+
+
#
# Bonjour
#
@@ -1313,6 +1324,10 @@ fi
AC_SUBST(LDAP_LIBS_FE)
AC_SUBST(LDAP_LIBS_BE)
+if test "$with_oauth" = yes ; then
+ AC_CHECK_LIB(iddawc, i_init_session, [], [AC_MSG_ERROR([library 'iddawc' is required for OAuth support])])
+fi
+
# for contrib/sepgsql
if test "$with_selinux" = yes; then
AC_CHECK_LIB(selinux, security_compute_create_name, [],
@@ -1523,6 +1538,10 @@ elif test "$with_uuid" = ossp ; then
[AC_MSG_ERROR([header file <ossp/uuid.h> or <uuid.h> is required for OSSP UUID])])])
fi
+if test "$with_oauth" != no; then
+ AC_CHECK_HEADER(iddawc.h, [], [AC_MSG_ERROR([header file <iddawc.h> is required for OAuth])])
+fi
+
if test "$PORTNAME" = "win32" ; then
AC_CHECK_HEADERS(crtdefs.h)
fi
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 8f05840821..3a61dd46d3 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -193,6 +193,7 @@ with_ldap = @with_ldap@
with_libxml = @with_libxml@
with_libxslt = @with_libxslt@
with_llvm = @with_llvm@
+with_oauth = @with_oauth@
with_system_tzdata = @with_system_tzdata@
with_uuid = @with_uuid@
with_zlib = @with_zlib@
diff --git a/src/include/common/oauth-common.h b/src/include/common/oauth-common.h
new file mode 100644
index 0000000000..3fa95ac7e8
--- /dev/null
+++ b/src/include/common/oauth-common.h
@@ -0,0 +1,19 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-common.h
+ * Declarations for helper functions used for OAuth/OIDC authentication
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/oauth-common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef OAUTH_COMMON_H
+#define OAUTH_COMMON_H
+
+/* Name of SASL mechanism per IANA */
+#define OAUTHBEARER_NAME "OAUTHBEARER"
+
+#endif /* OAUTH_COMMON_H */
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 783b8fc1ba..db5bc56ac5 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -319,6 +319,9 @@
/* Define to 1 if you have the `crypto' library (-lcrypto). */
#undef HAVE_LIBCRYPTO
+/* Define to 1 if you have the `iddawc' library (-liddawc). */
+#undef HAVE_LIBIDDAWC
+
/* Define to 1 if you have the `ldap' library (-lldap). */
#undef HAVE_LIBLDAP
@@ -920,6 +923,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 to build with OAuth 2.0 support. (--with-oauth) */
+#undef USE_OAUTH
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 0c4e55b6ad..8e89d50900 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -62,6 +62,11 @@ OBJS += \
fe-secure-gssapi.o
endif
+ifeq ($(with_oauth),yes)
+OBJS += \
+ fe-auth-oauth.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
@@ -83,7 +88,7 @@ endif
# that are built correctly for use in a shlib.
SHLIB_LINK_INTERNAL = -lpgcommon_shlib -lpgport_shlib
ifneq ($(PORTNAME), win32)
-SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
+SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -liddawc -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
else
SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi32 -lssl -lsocket -lnsl -lresolv -lintl -lm $(PTHREAD_LIBS), $(LIBS)) $(LDAP_LIBS_FE)
endif
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
new file mode 100644
index 0000000000..a27f974369
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -0,0 +1,724 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-oauth.c
+ * The front-end (client) implementation of OAuth/OIDC authentication.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-auth-oauth.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include <iddawc.h>
+
+#include "postgres_fe.h"
+
+#include "common/base64.h"
+#include "common/hmac.h"
+#include "common/jsonapi.h"
+#include "common/oauth-common.h"
+#include "fe-auth.h"
+#include "mb/pg_wchar.h"
+
+/* The exported OAuth callback mechanism. */
+static void *oauth_init(PGconn *conn, const char *password,
+ const char *sasl_mechanism);
+static void oauth_exchange(void *opaq, bool final,
+ char *input, int inputlen,
+ char **output, int *outputlen,
+ bool *done, bool *success);
+static bool oauth_channel_bound(void *opaq);
+static void oauth_free(void *opaq);
+
+const pg_sasl_mech pg_oauth_mech = {
+ oauth_init,
+ oauth_exchange,
+ oauth_channel_bound,
+ oauth_free,
+};
+
+typedef enum
+{
+ FE_OAUTH_INIT,
+ FE_OAUTH_BEARER_SENT,
+ FE_OAUTH_SERVER_ERROR,
+} fe_oauth_state_enum;
+
+typedef struct
+{
+ fe_oauth_state_enum state;
+
+ PGconn *conn;
+} fe_oauth_state;
+
+static void *
+oauth_init(PGconn *conn, const char *password,
+ const char *sasl_mechanism)
+{
+ fe_oauth_state *state;
+
+ /*
+ * We only support one SASL mechanism here; anything else is programmer
+ * error.
+ */
+ Assert(sasl_mechanism != NULL);
+ Assert(!strcmp(sasl_mechanism, OAUTHBEARER_NAME));
+
+ state = malloc(sizeof(*state));
+ if (!state)
+ return NULL;
+
+ state->state = FE_OAUTH_INIT;
+ state->conn = conn;
+
+ return state;
+}
+
+static const char *
+iddawc_error_string(int errcode)
+{
+ switch (errcode)
+ {
+ case I_OK:
+ return "I_OK";
+
+ case I_ERROR:
+ return "I_ERROR";
+
+ case I_ERROR_PARAM:
+ return "I_ERROR_PARAM";
+
+ case I_ERROR_MEMORY:
+ return "I_ERROR_MEMORY";
+
+ case I_ERROR_UNAUTHORIZED:
+ return "I_ERROR_UNAUTHORIZED";
+
+ case I_ERROR_SERVER:
+ return "I_ERROR_SERVER";
+ }
+
+ return "<unknown>";
+}
+
+static void
+iddawc_error(PGconn *conn, int errcode, const char *msg)
+{
+ appendPQExpBufferStr(&conn->errorMessage, libpq_gettext(msg));
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext(" (iddawc error %s)\n"),
+ iddawc_error_string(errcode));
+}
+
+static void
+iddawc_request_error(PGconn *conn, struct _i_session *i, int err, const char *msg)
+{
+ const char *error_code;
+ const char *desc;
+
+ appendPQExpBuffer(&conn->errorMessage, "%s: ", libpq_gettext(msg));
+
+ error_code = i_get_str_parameter(i, I_OPT_ERROR);
+ if (!error_code)
+ {
+ /*
+ * The server didn't give us any useful information, so just print the
+ * error code.
+ */
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("(iddawc error %s)\n"),
+ iddawc_error_string(err));
+ return;
+ }
+
+ /* If the server gave a string description, print that too. */
+ desc = i_get_str_parameter(i, I_OPT_ERROR_DESCRIPTION);
+ if (desc)
+ appendPQExpBuffer(&conn->errorMessage, "%s ", desc);
+
+ appendPQExpBuffer(&conn->errorMessage, "(%s)\n", error_code);
+}
+
+static char *
+get_auth_token(PGconn *conn)
+{
+ PQExpBuffer token_buf = NULL;
+ struct _i_session session;
+ int err;
+ int auth_method;
+ bool user_prompted = false;
+ const char *verification_uri;
+ const char *user_code;
+ const char *access_token;
+ const char *token_type;
+ char *token = NULL;
+
+ if (!conn->oauth_discovery_uri)
+ return strdup(""); /* ask the server for one */
+
+ i_init_session(&session);
+
+ if (!conn->oauth_client_id)
+ {
+ /* We can't talk to a server without a client identifier. */
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("no oauth_client_id is set for the connection"));
+ goto cleanup;
+ }
+
+ token_buf = createPQExpBuffer();
+
+ if (!token_buf)
+ goto cleanup;
+
+ err = i_set_str_parameter(&session, I_OPT_OPENID_CONFIG_ENDPOINT, conn->oauth_discovery_uri);
+ if (err)
+ {
+ iddawc_error(conn, err, "failed to set OpenID config endpoint");
+ goto cleanup;
+ }
+
+ err = i_get_openid_config(&session);
+ if (err)
+ {
+ iddawc_error(conn, err, "failed to fetch OpenID discovery document");
+ goto cleanup;
+ }
+
+ if (!i_get_str_parameter(&session, I_OPT_TOKEN_ENDPOINT))
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("issuer has no token endpoint"));
+ goto cleanup;
+ }
+
+ if (!i_get_str_parameter(&session, I_OPT_DEVICE_AUTHORIZATION_ENDPOINT))
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("issuer does not support device authorization"));
+ goto cleanup;
+ }
+
+ err = i_set_response_type(&session, I_RESPONSE_TYPE_DEVICE_CODE);
+ if (err)
+ {
+ iddawc_error(conn, err, "failed to set device code response type");
+ goto cleanup;
+ }
+
+ auth_method = I_TOKEN_AUTH_METHOD_NONE;
+ if (conn->oauth_client_secret && *conn->oauth_client_secret)
+ auth_method = I_TOKEN_AUTH_METHOD_SECRET_BASIC;
+
+ err = i_set_parameter_list(&session,
+ I_OPT_CLIENT_ID, conn->oauth_client_id,
+ I_OPT_CLIENT_SECRET, conn->oauth_client_secret,
+ I_OPT_TOKEN_METHOD, auth_method,
+ I_OPT_SCOPE, conn->oauth_scope,
+ I_OPT_NONE
+ );
+ if (err)
+ {
+ iddawc_error(conn, err, "failed to set client identifier");
+ goto cleanup;
+ }
+
+ err = i_run_device_auth_request(&session);
+ if (err)
+ {
+ iddawc_request_error(conn, &session, err,
+ "failed to obtain device authorization");
+ goto cleanup;
+ }
+
+ verification_uri = i_get_str_parameter(&session, I_OPT_DEVICE_AUTH_VERIFICATION_URI);
+ if (!verification_uri)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("issuer did not provide a verification URI"));
+ goto cleanup;
+ }
+
+ user_code = i_get_str_parameter(&session, I_OPT_DEVICE_AUTH_USER_CODE);
+ if (!user_code)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("issuer did not provide a user code"));
+ goto cleanup;
+ }
+
+ /*
+ * Poll the token endpoint until either the user logs in and authorizes the
+ * use of a token, or a hard failure occurs. We perform one ping _before_
+ * prompting the user, so that we don't make them do the work of logging in
+ * only to find that the token endpoint is completely unreachable.
+ */
+ err = i_run_token_request(&session);
+ while (err)
+ {
+ const char *error_code;
+ uint interval;
+
+ error_code = i_get_str_parameter(&session, I_OPT_ERROR);
+
+ /*
+ * authorization_pending and slow_down are the only acceptable errors;
+ * anything else and we bail.
+ */
+ if (!error_code || (strcmp(error_code, "authorization_pending")
+ && strcmp(error_code, "slow_down")))
+ {
+ iddawc_request_error(conn, &session, err,
+ "OAuth token retrieval failed");
+ goto cleanup;
+ }
+
+ if (!user_prompted)
+ {
+ /*
+ * Now that we know the token endpoint isn't broken, give the user
+ * the login instructions.
+ */
+ pqInternalNotice(&conn->noticeHooks,
+ "Visit %s and enter the code: %s",
+ verification_uri, user_code);
+
+ user_prompted = true;
+ }
+
+ /*
+ * We are required to wait between polls; the server tells us how long.
+ * TODO: if interval's not set, we need to default to five seconds
+ * TODO: sanity check the interval
+ */
+ interval = i_get_int_parameter(&session, I_OPT_DEVICE_AUTH_INTERVAL);
+
+ /*
+ * A slow_down error requires us to permanently increase our retry
+ * interval by five seconds. RFC 8628, Sec. 3.5.
+ */
+ if (!strcmp(error_code, "slow_down"))
+ {
+ interval += 5;
+ i_set_int_parameter(&session, I_OPT_DEVICE_AUTH_INTERVAL, interval);
+ }
+
+ sleep(interval);
+
+ /*
+ * XXX Reset the error code before every call, because iddawc won't do
+ * that for us. This matters if the server first sends a "pending" error
+ * code, then later hard-fails without sending an error code to
+ * overwrite the first one.
+ *
+ * That we have to do this at all seems like a bug in iddawc.
+ */
+ i_set_str_parameter(&session, I_OPT_ERROR, NULL);
+
+ err = i_run_token_request(&session);
+ }
+
+ access_token = i_get_str_parameter(&session, I_OPT_ACCESS_TOKEN);
+ token_type = i_get_str_parameter(&session, I_OPT_TOKEN_TYPE);
+
+ if (!access_token || !token_type || strcasecmp(token_type, "Bearer"))
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("issuer did not provide a bearer token"));
+ goto cleanup;
+ }
+
+ appendPQExpBufferStr(token_buf, "Bearer ");
+ appendPQExpBufferStr(token_buf, access_token);
+
+ if (PQExpBufferBroken(token_buf))
+ goto cleanup;
+
+ token = strdup(token_buf->data);
+
+cleanup:
+ if (token_buf)
+ destroyPQExpBuffer(token_buf);
+ i_clean_session(&session);
+
+ return token;
+}
+
+#define kvsep "\x01"
+
+static char *
+client_initial_response(PGconn *conn)
+{
+ static const char * const resp_format = "n,," kvsep "auth=%s" kvsep kvsep;
+
+ PQExpBuffer token_buf;
+ PQExpBuffer discovery_buf = NULL;
+ char *token = NULL;
+ char *response = NULL;
+
+ token_buf = createPQExpBuffer();
+ if (!token_buf)
+ goto cleanup;
+
+ /*
+ * If we don't yet have a discovery URI, but the user gave us an explicit
+ * issuer, use the .well-known discovery URI for that issuer.
+ */
+ if (!conn->oauth_discovery_uri && conn->oauth_issuer)
+ {
+ discovery_buf = createPQExpBuffer();
+ if (!discovery_buf)
+ goto cleanup;
+
+ appendPQExpBufferStr(discovery_buf, conn->oauth_issuer);
+ appendPQExpBufferStr(discovery_buf, "/.well-known/openid-configuration");
+
+ if (PQExpBufferBroken(discovery_buf))
+ goto cleanup;
+
+ conn->oauth_discovery_uri = strdup(discovery_buf->data);
+ }
+
+ token = get_auth_token(conn);
+ if (!token)
+ goto cleanup;
+
+ appendPQExpBuffer(token_buf, resp_format, token);
+ if (PQExpBufferBroken(token_buf))
+ goto cleanup;
+
+ response = strdup(token_buf->data);
+
+cleanup:
+ if (token)
+ free(token);
+ if (discovery_buf)
+ destroyPQExpBuffer(discovery_buf);
+ if (token_buf)
+ destroyPQExpBuffer(token_buf);
+
+ return response;
+}
+
+#define ERROR_STATUS_FIELD "status"
+#define ERROR_SCOPE_FIELD "scope"
+#define ERROR_OPENID_CONFIGURATION_FIELD "openid-configuration"
+
+struct json_ctx
+{
+ char *errmsg; /* any non-NULL value stops all processing */
+ int nested; /* nesting level (zero is the top) */
+
+ const char *target_field_name; /* points to a static allocation */
+ char **target_field; /* see below */
+
+ /* target_field, if set, points to one of the following: */
+ char *status;
+ char *scope;
+ char *discovery_uri;
+};
+
+static void
+oauth_json_object_start(void *state)
+{
+ struct json_ctx *ctx = state;
+
+ if (ctx->errmsg)
+ return; /* short-circuit */
+
+ if (ctx->target_field)
+ {
+ Assert(ctx->nested == 1);
+
+ ctx->errmsg = psprintf(libpq_gettext("field \"%s\" must be a string"),
+ ctx->target_field_name);
+ }
+
+ ++ctx->nested;
+}
+
+static void
+oauth_json_object_end(void *state)
+{
+ struct json_ctx *ctx = state;
+
+ if (ctx->errmsg)
+ return; /* short-circuit */
+
+ --ctx->nested;
+}
+
+static void
+oauth_json_object_field_start(void *state, char *name, bool isnull)
+{
+ struct json_ctx *ctx = state;
+
+ if (ctx->errmsg)
+ {
+ /* short-circuit */
+ pfree(name);
+ return;
+ }
+
+ if (ctx->nested == 1)
+ {
+ if (!strcmp(name, ERROR_STATUS_FIELD))
+ {
+ ctx->target_field_name = ERROR_STATUS_FIELD;
+ ctx->target_field = &ctx->status;
+ }
+ else if (!strcmp(name, ERROR_SCOPE_FIELD))
+ {
+ ctx->target_field_name = ERROR_SCOPE_FIELD;
+ ctx->target_field = &ctx->scope;
+ }
+ else if (!strcmp(name, ERROR_OPENID_CONFIGURATION_FIELD))
+ {
+ ctx->target_field_name = ERROR_OPENID_CONFIGURATION_FIELD;
+ ctx->target_field = &ctx->discovery_uri;
+ }
+ }
+
+ pfree(name);
+}
+
+static void
+oauth_json_array_start(void *state)
+{
+ struct json_ctx *ctx = state;
+
+ if (ctx->errmsg)
+ return; /* short-circuit */
+
+ if (!ctx->nested)
+ {
+ ctx->errmsg = pstrdup(libpq_gettext("top-level element must be an object"));
+ }
+ else if (ctx->target_field)
+ {
+ Assert(ctx->nested == 1);
+
+ ctx->errmsg = psprintf(libpq_gettext("field \"%s\" must be a string"),
+ ctx->target_field_name);
+ }
+}
+
+static void
+oauth_json_scalar(void *state, char *token, JsonTokenType type)
+{
+ struct json_ctx *ctx = state;
+
+ if (ctx->errmsg)
+ {
+ /* short-circuit */
+ pfree(token);
+ return;
+ }
+
+ if (!ctx->nested)
+ {
+ ctx->errmsg = pstrdup(libpq_gettext("top-level element must be an object"));
+ }
+ else if (ctx->target_field)
+ {
+ Assert(ctx->nested == 1);
+
+ if (type == JSON_TOKEN_STRING)
+ {
+ *ctx->target_field = token;
+
+ ctx->target_field = NULL;
+ ctx->target_field_name = NULL;
+
+ return; /* don't pfree the token we're using */
+ }
+
+ ctx->errmsg = psprintf(libpq_gettext("field \"%s\" must be a string"),
+ ctx->target_field_name);
+ }
+
+ pfree(token);
+}
+
+static bool
+handle_oauth_sasl_error(PGconn *conn, char *msg, int msglen)
+{
+ JsonLexContext *lex;
+ JsonSemAction sem = {0};
+ JsonParseErrorType err;
+ struct json_ctx ctx = {0};
+ char *errmsg = NULL;
+
+ /* Sanity check. */
+ if (strlen(msg) != msglen)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("server's error message contained an embedded NULL"));
+ return false;
+ }
+
+ lex = makeJsonLexContextCstringLen(msg, msglen, PG_UTF8, true);
+
+ sem.semstate = &ctx;
+
+ sem.object_start = oauth_json_object_start;
+ sem.object_end = oauth_json_object_end;
+ sem.object_field_start = oauth_json_object_field_start;
+ sem.array_start = oauth_json_array_start;
+ sem.scalar = oauth_json_scalar;
+
+ err = pg_parse_json(lex, &sem);
+
+ if (err != JSON_SUCCESS)
+ {
+ errmsg = json_errdetail(err, lex);
+ }
+ else if (ctx.errmsg)
+ {
+ errmsg = ctx.errmsg;
+ }
+
+ if (errmsg)
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("failed to parse server's error response: %s"),
+ errmsg);
+ pfree(errmsg);
+ return false;
+ }
+
+ /* TODO: what if these override what the user already specified? */
+ if (ctx.discovery_uri)
+ {
+ if (conn->oauth_discovery_uri)
+ free(conn->oauth_discovery_uri);
+
+ conn->oauth_discovery_uri = ctx.discovery_uri;
+ }
+
+ if (ctx.scope)
+ {
+ if (conn->oauth_scope)
+ free(conn->oauth_scope);
+
+ conn->oauth_scope = ctx.scope;
+ }
+ /* TODO: missing error scope should clear any existing connection scope */
+
+ if (!ctx.status)
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("server sent error response without a status"));
+ return false;
+ }
+
+ if (!strcmp(ctx.status, "invalid_token"))
+ {
+ /*
+ * invalid_token is the only error code we'll automatically retry for,
+ * but only if we have enough information to do so.
+ */
+ if (conn->oauth_discovery_uri)
+ conn->oauth_want_retry = true;
+ }
+ /* TODO: include status in hard failure message */
+
+ return true;
+}
+
+static void
+oauth_exchange(void *opaq, bool final,
+ char *input, int inputlen,
+ char **output, int *outputlen,
+ bool *done, bool *success)
+{
+ fe_oauth_state *state = opaq;
+ PGconn *conn = state->conn;
+
+ *done = false;
+ *success = false;
+ *output = NULL;
+ *outputlen = 0;
+
+ switch (state->state)
+ {
+ case FE_OAUTH_INIT:
+ Assert(inputlen == -1);
+
+ *output = client_initial_response(conn);
+ if (!*output)
+ goto error;
+
+ *outputlen = strlen(*output);
+ state->state = FE_OAUTH_BEARER_SENT;
+
+ break;
+
+ case FE_OAUTH_BEARER_SENT:
+ if (final)
+ {
+ /* TODO: ensure there is no message content here. */
+ *done = true;
+ *success = true;
+
+ break;
+ }
+
+ /*
+ * Error message sent by the server.
+ */
+ if (!handle_oauth_sasl_error(conn, input, inputlen))
+ goto error;
+
+ /*
+ * Respond with the required dummy message (RFC 7628, sec. 3.2.3).
+ */
+ *output = strdup(kvsep);
+ *outputlen = strlen(*output); /* == 1 */
+
+ state->state = FE_OAUTH_SERVER_ERROR;
+ break;
+
+ case FE_OAUTH_SERVER_ERROR:
+ /*
+ * After an error, the server should send an error response to fail
+ * the SASL handshake, which is handled in higher layers.
+ *
+ * If we get here, the server either sent *another* challenge which
+ * isn't defined in the RFC, or completed the handshake successfully
+ * after telling us it was going to fail. Neither is acceptable.
+ */
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("server sent additional OAuth data after error\n"));
+ goto error;
+
+ default:
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("invalid OAuth exchange state\n"));
+ goto error;
+ }
+
+ return;
+
+error:
+ *done = true;
+ *success = false;
+}
+
+static bool
+oauth_channel_bound(void *opaq)
+{
+ /* This mechanism does not support channel binding. */
+ return false;
+}
+
+static void
+oauth_free(void *opaq)
+{
+ fe_oauth_state *state = opaq;
+
+ free(state);
+}
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
index 04d5703d89..f2ba3bca37 100644
--- a/src/interfaces/libpq/fe-auth-scram.c
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -24,7 +24,8 @@
/* The exported SCRAM callback mechanism. */
static void *scram_init(PGconn *conn, const char *password,
const char *sasl_mechanism);
-static void scram_exchange(void *opaq, char *input, int inputlen,
+static void scram_exchange(void *opaq, bool final,
+ char *input, int inputlen,
char **output, int *outputlen,
bool *done, bool *success);
static bool scram_channel_bound(void *opaq);
@@ -205,7 +206,8 @@ scram_free(void *opaq)
* Exchange a SCRAM message with backend.
*/
static void
-scram_exchange(void *opaq, char *input, int inputlen,
+scram_exchange(void *opaq, bool final,
+ char *input, int inputlen,
char **output, int *outputlen,
bool *done, bool *success)
{
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index d5cbac108e..690b23b9d9 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -39,6 +39,7 @@
#endif
#include "common/md5.h"
+#include "common/oauth-common.h"
#include "common/scram-common.h"
#include "fe-auth.h"
#include "libpq-fe.h"
@@ -422,7 +423,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
bool success;
const char *selected_mechanism;
PQExpBufferData mechanism_buf;
- char *password;
+ char *password = NULL;
initPQExpBuffer(&mechanism_buf);
@@ -444,8 +445,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
/*
* Parse the list of SASL authentication mechanisms in the
* AuthenticationSASL message, and select the best mechanism that we
- * support. SCRAM-SHA-256-PLUS and SCRAM-SHA-256 are the only ones
- * supported at the moment, listed by order of decreasing importance.
+ * support. Mechanisms are listed by order of decreasing importance.
*/
selected_mechanism = NULL;
for (;;)
@@ -485,6 +485,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
{
selected_mechanism = SCRAM_SHA_256_PLUS_NAME;
conn->sasl = &pg_scram_mech;
+ conn->password_needed = true;
}
#else
/*
@@ -522,7 +523,17 @@ pg_SASL_init(PGconn *conn, int payloadlen)
{
selected_mechanism = SCRAM_SHA_256_NAME;
conn->sasl = &pg_scram_mech;
+ conn->password_needed = true;
}
+#ifdef USE_OAUTH
+ else if (strcmp(mechanism_buf.data, OAUTHBEARER_NAME) == 0 &&
+ !selected_mechanism)
+ {
+ selected_mechanism = OAUTHBEARER_NAME;
+ conn->sasl = &pg_oauth_mech;
+ conn->password_needed = false;
+ }
+#endif
}
if (!selected_mechanism)
@@ -547,18 +558,19 @@ pg_SASL_init(PGconn *conn, int payloadlen)
/*
* First, select the password to use for the exchange, complaining if
- * there isn't one. Currently, all supported SASL mechanisms require a
- * password, so we can just go ahead here without further distinction.
+ * there isn't one and the SASL mechanism needs it.
*/
- conn->password_needed = true;
- password = conn->connhost[conn->whichhost].password;
- if (password == NULL)
- password = conn->pgpass;
- if (password == NULL || password[0] == '\0')
+ if (conn->password_needed)
{
- appendPQExpBufferStr(&conn->errorMessage,
- PQnoPasswordSupplied);
- goto error;
+ password = conn->connhost[conn->whichhost].password;
+ if (password == NULL)
+ password = conn->pgpass;
+ if (password == NULL || password[0] == '\0')
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ PQnoPasswordSupplied);
+ goto error;
+ }
}
Assert(conn->sasl);
@@ -576,7 +588,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
goto oom_error;
/* Get the mechanism-specific Initial Client Response, if any */
- conn->sasl->exchange(conn->sasl_state,
+ conn->sasl->exchange(conn->sasl_state, false,
NULL, -1,
&initialresponse, &initialresponselen,
&done, &success);
@@ -657,7 +669,7 @@ pg_SASL_continue(PGconn *conn, int payloadlen, bool final)
/* For safety and convenience, ensure the buffer is NULL-terminated. */
challenge[payloadlen] = '\0';
- conn->sasl->exchange(conn->sasl_state,
+ conn->sasl->exchange(conn->sasl_state, final,
challenge, payloadlen,
&output, &outputlen,
&done, &success);
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index 1e4fcbff62..edc748fd3a 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -26,4 +26,7 @@ extern char *pg_fe_getauthname(PQExpBuffer errorMessage);
extern const pg_sasl_mech pg_scram_mech;
extern char *pg_fe_scram_build_secret(const char *password);
+/* Mechanisms in fe-auth-oauth.c */
+extern const pg_sasl_mech pg_oauth_mech;
+
#endif /* FE_AUTH_H */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 10d007582c..1d4bca9194 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -345,6 +345,23 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ /* OAuth v2 */
+ {"oauth_issuer", NULL, NULL, NULL,
+ "OAuth-Issuer", "", 40,
+ offsetof(struct pg_conn, oauth_issuer)},
+
+ {"oauth_client_id", NULL, NULL, NULL,
+ "OAuth-Client-ID", "", 40,
+ offsetof(struct pg_conn, oauth_client_id)},
+
+ {"oauth_client_secret", NULL, NULL, NULL,
+ "OAuth-Client-Secret", "", 40,
+ offsetof(struct pg_conn, oauth_client_secret)},
+
+ {"oauth_scope", NULL, NULL, NULL,
+ "OAuth-Scope", "", 15,
+ offsetof(struct pg_conn, oauth_scope)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
@@ -607,6 +624,7 @@ pqDropServerData(PGconn *conn)
conn->write_err_msg = NULL;
conn->be_pid = 0;
conn->be_key = 0;
+ /* conn->oauth_want_retry = false; TODO */
}
@@ -3356,6 +3374,16 @@ keep_going: /* We will come back to here until there is
/* Check to see if we should mention pgpassfile */
pgpassfileWarning(conn);
+#ifdef USE_OAUTH
+ if (conn->sasl == &pg_oauth_mech
+ && conn->oauth_want_retry)
+ {
+ /* TODO: only allow retry once */
+ need_new_connection = true;
+ goto keep_going;
+ }
+#endif
+
#ifdef ENABLE_GSS
/*
@@ -4130,6 +4158,16 @@ freePGconn(PGconn *conn)
free(conn->rowBuf);
if (conn->target_session_attrs)
free(conn->target_session_attrs);
+ if (conn->oauth_issuer)
+ free(conn->oauth_issuer);
+ if (conn->oauth_discovery_uri)
+ free(conn->oauth_discovery_uri);
+ if (conn->oauth_client_id)
+ free(conn->oauth_client_id);
+ if (conn->oauth_client_secret)
+ free(conn->oauth_client_secret);
+ if (conn->oauth_scope)
+ free(conn->oauth_scope);
termPQExpBuffer(&conn->errorMessage);
termPQExpBuffer(&conn->workBuffer);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 25eaa231c5..b749c6c05d 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -340,7 +340,7 @@ typedef struct pg_conn_host
} pg_conn_host;
typedef void *(*pg_sasl_init_func)(PGconn *, const char *, const char *);
-typedef void (*pg_sasl_exchange_func)(void *, char *, int, char **, int *, bool *, bool *);
+typedef void (*pg_sasl_exchange_func)(void *, bool, char *, int, char **, int *, bool *, bool *);
typedef bool (*pg_sasl_channel_bound_func)(void *);
typedef void (*pg_sasl_free_func)(void *);
@@ -406,6 +406,14 @@ struct pg_conn
char *ssl_max_protocol_version; /* maximum TLS protocol version */
char *target_session_attrs; /* desired session properties */
+ /* OAuth v2 */
+ char *oauth_issuer; /* token issuer URL */
+ char *oauth_discovery_uri; /* URI of the issuer's discovery document */
+ char *oauth_client_id; /* client identifier */
+ char *oauth_client_secret; /* client secret */
+ char *oauth_scope; /* access token scope */
+ bool oauth_want_retry; /* should we retry on failure? */
+
/* Optional file to write trace info to */
FILE *Pfdebug;
int traceFlags;
--
2.25.1
[text/x-patch] 0005-backend-add-OAUTHBEARER-SASL-mechanism.patch (38.5K, ../../[email protected]/6-0005-backend-add-OAUTHBEARER-SASL-mechanism.patch)
download | inline diff:
From ee8e85d3416f381ba9d44f8d4a681e5006bd5b82 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Tue, 4 May 2021 16:21:11 -0700
Subject: [PATCH 5/7] backend: add OAUTHBEARER SASL mechanism
DO NOT USE THIS PROOF OF CONCEPT IN PRODUCTION.
Implement OAUTHBEARER (RFC 7628) on the server side. This adds a new
auth method, oauth, to pg_hba.
Because OAuth implementations vary so wildly, and bearer token
validation is heavily dependent on the issuing party, authn/z is done by
communicating with an external program: the oauth_validator_command.
This command must do the following:
1. Receive the bearer token by reading its contents from a file
descriptor passed from the server. (The numeric value of this
descriptor may be inserted into the oauth_validator_command using the
%f specifier.)
This MUST be the first action the command performs. The server will
not begin reading stdout from the command until the token has been
read in full, so if the command tries to print anything and hits a
buffer limit, the backend will deadlock and time out.
2. Validate the bearer token. The correct way to do this depends on the
issuer, but it generally involves either cryptographic operations to
prove that the token was issued by a trusted party, or the
presentation of the bearer token to some other party so that _it_ can
perform validation.
The command MUST maintain confidentiality of the bearer token, since
in most cases it can be used just like a password. (There are ways to
cryptographically bind tokens to client certificates, but they are
way beyond the scope of this commit message.)
If the token cannot be validated, the command must exit with a
non-zero status. Further authentication/authorization is pointless if
the bearer token wasn't issued by someone you trust.
3. Authenticate the user, authorize the user, or both:
a. To authenticate the user, use the bearer token to retrieve some
trusted identifier string for the end user. The exact process for
this is, again, issuer-dependent. The command should print the
authenticated identity string to stdout, followed by a newline.
If the user cannot be authenticated, the validator should not
print anything to stdout. It should also exit with a non-zero
status, unless the token may be used to authorize the connection
through some other means (see below).
On a success, the command may then exit with a zero success code.
By default, the server will then check to make sure the identity
string matches the role that is being used (or matches a usermap
entry, if one is in use).
b. To optionally authorize the user, in combination with the HBA
option trust_validator_authz=1 (see below), the validator simply
returns a zero exit code if the client should be allowed to
connect with its presented role (which can be passed to the
command using the %r specifier), or a non-zero code otherwise.
The hard part is in determining whether the given token truly
authorizes the client to use the given role, which must
unfortunately be left as an exercise to the reader.
This obviously requires some care, as a poorly implemented token
validator may silently open the entire database to anyone with a
bearer token. But it may be a more portable approach, since OAuth
is designed as an authorization framework, not an authentication
framework. For example, the user's bearer token could carry an
"allow_superuser_access" claim, which would authorize pseudonymous
database access as any role. It's then up to the OAuth system
administrators to ensure that allow_superuser_access is doled out
only to the proper users.
c. It's possible that the user can be successfully authenticated but
isn't authorized to connect. In this case, the command may print
the authenticated ID and then fail with a non-zero exit code.
(This makes it easier to see what's going on in the Postgres
logs.)
4. Token validators may optionally log to stderr. This will be printed
verbatim into the Postgres server logs.
The oauth method supports the following HBA options (but note that two
of them are not optional, since we have no way of choosing sensible
defaults):
issuer: Required. The URL of the OAuth issuing party, which the client
must contact to receive a bearer token.
Some real-world examples as of time of writing:
- https://accounts.google.com
- https://login.microsoft.com/[tenant-id]/v2.0
scope: Required. The OAuth scope(s) required for the server to
authenticate and/or authorize the user. This is heavily
deployment-specific, but a simple example is "openid email".
map: Optional. Specify a standard PostgreSQL user map; this works
the same as with other auth methods such as peer. If a map is
not specified, the user ID returned by the token validator
must exactly match the role that's being requested (but see
trust_validator_authz, below).
trust_validator_authz:
Optional. When set to 1, this allows the token validator to
take full control of the authorization process. Standard user
mapping is skipped: if the validator command succeeds, the
client is allowed to connect under its desired role and no
further checks are done.
Unlike the client, servers support OAuth without needing to be built
against libiddawc (since the responsibility for "speaking" OAuth/OIDC
correctly is delegated entirely to the oauth_validator_command).
Several TODOs:
- port to platforms other than "modern Linux"
- overhaul the communication with oauth_validator_command, which is
currently a bad hack on OpenPipeStream()
- implement more sanity checks on the OAUTHBEARER message format and
tokens sent by the client
- implement more helpful handling of HBA misconfigurations
- properly interpolate JSON when generating error responses
- use logdetail during auth failures
- deal with role names that can't be safely passed to system() without
shell-escaping
- allow passing the configured issuer to the oauth_validator_command, to
deal with multi-issuer setups
- ...and more.
---
src/backend/libpq/Makefile | 1 +
src/backend/libpq/auth-oauth.c | 797 +++++++++++++++++++++++++++++++++
src/backend/libpq/auth-scram.c | 2 +
src/backend/libpq/auth.c | 43 +-
src/backend/libpq/hba.c | 29 +-
src/backend/utils/misc/guc.c | 12 +
src/include/libpq/auth.h | 1 +
src/include/libpq/hba.h | 8 +-
src/include/libpq/oauth.h | 24 +
src/include/libpq/sasl.h | 26 ++
10 files changed, 915 insertions(+), 28 deletions(-)
create mode 100644 src/backend/libpq/auth-oauth.c
create mode 100644 src/include/libpq/oauth.h
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 8d1d16b0fc..40f2c50c3c 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -15,6 +15,7 @@ include $(top_builddir)/src/Makefile.global
# be-fsstubs is here for historical reasons, probably belongs elsewhere
OBJS = \
+ auth-oauth.o \
auth-scram.o \
auth.o \
be-fsstubs.o \
diff --git a/src/backend/libpq/auth-oauth.c b/src/backend/libpq/auth-oauth.c
new file mode 100644
index 0000000000..b2b9d56e7c
--- /dev/null
+++ b/src/backend/libpq/auth-oauth.c
@@ -0,0 +1,797 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-oauth.c
+ * Server-side implementation of the SASL OAUTHBEARER mechanism.
+ *
+ * See the following RFC for more details:
+ * - RFC 7628: https://tools.ietf.org/html/rfc7628
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/libpq/auth-oauth.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "common/oauth-common.h"
+#include "lib/stringinfo.h"
+#include "libpq/auth.h"
+#include "libpq/hba.h"
+#include "libpq/oauth.h"
+#include "libpq/sasl.h"
+#include "storage/fd.h"
+
+/* GUC */
+char *oauth_validator_command;
+
+static void oauth_get_mechanisms(Port *port, StringInfo buf);
+static void *oauth_init(Port *port, const char *selected_mech, const char *shadow_pass);
+static int oauth_exchange(void *opaq, const char *input, int inputlen,
+ char **output, int *outputlen, char **logdetail);
+
+/* Mechanism declaration */
+const pg_be_sasl_mech pg_be_oauth_mech = {
+ oauth_get_mechanisms,
+ oauth_init,
+ oauth_exchange,
+
+ PG_MAX_AUTH_TOKEN_LENGTH,
+};
+
+
+typedef enum
+{
+ OAUTH_STATE_INIT = 0,
+ OAUTH_STATE_ERROR,
+ OAUTH_STATE_FINISHED,
+} oauth_state;
+
+struct oauth_ctx
+{
+ oauth_state state;
+ Port *port;
+ const char *issuer;
+ const char *scope;
+};
+
+static char *sanitize_char(char c);
+static char *parse_kvpairs_for_auth(char **input);
+static void generate_error_response(struct oauth_ctx *ctx, char **output, int *outputlen);
+static bool validate(Port *port, const char *auth, char **logdetail);
+static bool run_validator_command(Port *port, const char *token);
+static bool check_exit(FILE **fh, const char *command);
+static bool unset_cloexec(int fd);
+static bool username_ok_for_shell(const char *username);
+
+#define KVSEP 0x01
+#define AUTH_KEY "auth"
+#define BEARER_SCHEME "Bearer "
+
+static void
+oauth_get_mechanisms(Port *port, StringInfo buf)
+{
+ /* Only OAUTHBEARER is supported. */
+ appendStringInfoString(buf, OAUTHBEARER_NAME);
+ appendStringInfoChar(buf, '\0');
+}
+
+static void *
+oauth_init(Port *port, const char *selected_mech, const char *shadow_pass)
+{
+ struct oauth_ctx *ctx;
+
+ if (strcmp(selected_mech, OAUTHBEARER_NAME))
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("client selected an invalid SASL authentication mechanism")));
+
+ ctx = palloc0(sizeof(*ctx));
+
+ ctx->state = OAUTH_STATE_INIT;
+ ctx->port = port;
+
+ Assert(port->hba);
+ ctx->issuer = port->hba->oauth_issuer;
+ ctx->scope = port->hba->oauth_scope;
+
+ return ctx;
+}
+
+static int
+oauth_exchange(void *opaq, const char *input, int inputlen,
+ char **output, int *outputlen, char **logdetail)
+{
+ char *p;
+ char cbind_flag;
+ char *auth;
+
+ struct oauth_ctx *ctx = opaq;
+
+ *output = NULL;
+ *outputlen = -1;
+
+ /*
+ * If the client didn't include an "Initial Client Response" in the
+ * SASLInitialResponse message, send an empty challenge, to which the
+ * client will respond with the same data that usually comes in the
+ * Initial Client Response.
+ */
+ if (input == NULL)
+ {
+ Assert(ctx->state == OAUTH_STATE_INIT);
+
+ *output = pstrdup("");
+ *outputlen = 0;
+ return SASL_EXCHANGE_CONTINUE;
+ }
+
+ /*
+ * Check that the input length agrees with the string length of the input.
+ */
+ if (inputlen == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("The message is empty.")));
+ if (inputlen != strlen(input))
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message length does not match input length.")));
+
+ switch (ctx->state)
+ {
+ case OAUTH_STATE_INIT:
+ /* Handle this case below. */
+ break;
+
+ case OAUTH_STATE_ERROR:
+ /*
+ * Only one response is valid for the client during authentication
+ * failure: a single kvsep.
+ */
+ if (inputlen != 1 || *input != KVSEP)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Client did not send a kvsep response.")));
+
+ /* The (failed) handshake is now complete. */
+ ctx->state = OAUTH_STATE_FINISHED;
+ return SASL_EXCHANGE_FAILURE;
+
+ default:
+ elog(ERROR, "invalid OAUTHBEARER exchange state");
+ return SASL_EXCHANGE_FAILURE;
+ }
+
+ /* Handle the client's initial message. */
+ p = strdup(input);
+
+ /*
+ * OAUTHBEARER does not currently define a channel binding (so there is no
+ * OAUTHBEARER-PLUS, and we do not accept a 'p' specifier). We accept a 'y'
+ * specifier purely for the remote chance that a future specification could
+ * define one; then future clients can still interoperate with this server
+ * implementation. 'n' is the expected case.
+ */
+ cbind_flag = *p;
+ switch (cbind_flag)
+ {
+ case 'p':
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("The server does not support channel binding for OAuth, but the client message includes channel binding data.")));
+ break;
+
+ case 'y': /* fall through */
+ case 'n':
+ p++;
+ if (*p != ',')
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Comma expected, but found character \"%s\".",
+ sanitize_char(*p))));
+ p++;
+ break;
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Unexpected channel-binding flag \"%s\".",
+ sanitize_char(cbind_flag))));
+ }
+
+ /*
+ * Forbid optional authzid (authorization identity). We don't support it.
+ */
+ if (*p == 'a')
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("client uses authorization identity, but it is not supported")));
+ if (*p != ',')
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Unexpected attribute \"%s\" in client-first-message.",
+ sanitize_char(*p))));
+ p++;
+
+ /* All remaining fields are separated by the RFC's kvsep (\x01). */
+ if (*p != KVSEP)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Key-value separator expected, but found character \"%s\".",
+ sanitize_char(*p))));
+ p++;
+
+ auth = parse_kvpairs_for_auth(&p);
+ if (!auth)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message does not contain an auth value.")));
+
+ /* We should be at the end of our message. */
+ if (*p)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message contains additional data after the final terminator.")));
+
+ if (!validate(ctx->port, auth, logdetail))
+ {
+ generate_error_response(ctx, output, outputlen);
+
+ ctx->state = OAUTH_STATE_ERROR;
+ return SASL_EXCHANGE_CONTINUE;
+ }
+
+ ctx->state = OAUTH_STATE_FINISHED;
+ return SASL_EXCHANGE_SUCCESS;
+}
+
+/*
+ * Convert an arbitrary byte to printable form. For error messages.
+ *
+ * If it's a printable ASCII character, print it as a single character.
+ * otherwise, print it in hex.
+ *
+ * The returned pointer points to a static buffer.
+ */
+static char *
+sanitize_char(char c)
+{
+ static char buf[5];
+
+ if (c >= 0x21 && c <= 0x7E)
+ snprintf(buf, sizeof(buf), "'%c'", c);
+ else
+ snprintf(buf, sizeof(buf), "0x%02x", (unsigned char) c);
+ return buf;
+}
+
+/*
+ * Consumes all kvpairs in an OAUTHBEARER exchange message. If the "auth" key is
+ * found, its value is returned.
+ */
+static char *
+parse_kvpairs_for_auth(char **input)
+{
+ char *pos = *input;
+ char *auth = NULL;
+
+ /*
+ * The relevant ABNF, from Sec. 3.1:
+ *
+ * kvsep = %x01
+ * key = 1*(ALPHA)
+ * value = *(VCHAR / SP / HTAB / CR / LF )
+ * kvpair = key "=" value kvsep
+ * ;;gs2-header = See RFC 5801
+ * client-resp = (gs2-header kvsep *kvpair kvsep) / kvsep
+ *
+ * By the time we reach this code, the gs2-header and initial kvsep have
+ * already been validated. We start at the beginning of the first kvpair.
+ */
+
+ while (*pos)
+ {
+ char *end;
+ char *sep;
+ char *key;
+ char *value;
+
+ /*
+ * Find the end of this kvpair. Note that input is null-terminated by
+ * the SASL code, so the strchr() is bounded.
+ */
+ end = strchr(pos, KVSEP);
+ if (!end)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message contains an unterminated key/value pair.")));
+ *end = '\0';
+
+ if (pos == end)
+ {
+ /* Empty kvpair, signifying the end of the list. */
+ *input = pos + 1;
+ return auth;
+ }
+
+ /*
+ * Find the end of the key name.
+ *
+ * TODO further validate the key/value grammar? empty keys, bad chars...
+ */
+ sep = strchr(pos, '=');
+ if (!sep)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message contains a key without a value.")));
+ *sep = '\0';
+
+ /* Both key and value are now safely terminated. */
+ key = pos;
+ value = sep + 1;
+
+ if (!strcmp(key, AUTH_KEY))
+ {
+ if (auth)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message contains multiple auth values.")));
+
+ auth = value;
+ }
+ else
+ {
+ /*
+ * The RFC also defines the host and port keys, but they are not
+ * required for OAUTHBEARER and we do not use them. Also, per
+ * Sec. 3.1, any key/value pairs we don't recognize must be ignored.
+ */
+ }
+
+ /* Move to the next pair. */
+ pos = end + 1;
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message did not contain a final terminator.")));
+
+ return NULL; /* unreachable */
+}
+
+static void
+generate_error_response(struct oauth_ctx *ctx, char **output, int *outputlen)
+{
+ StringInfoData buf;
+
+ /*
+ * The admin needs to set an issuer and scope for OAuth to work. There's not
+ * really a way to hide this from the user, either, because we can't choose
+ * a "default" issuer, so be honest in the failure message.
+ *
+ * TODO: see if there's a better place to fail, earlier than this.
+ */
+ if (!ctx->issuer || !ctx->scope)
+ ereport(FATAL,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("OAuth is not properly configured for this user"),
+ errdetail_log("The issuer and scope parameters must be set in pg_hba.conf.")));
+
+
+ initStringInfo(&buf);
+
+ /*
+ * TODO: JSON escaping
+ */
+ appendStringInfo(&buf,
+ "{ "
+ "\"status\": \"invalid_token\", "
+ "\"openid-configuration\": \"%s/.well-known/openid-configuration\","
+ "\"scope\": \"%s\" "
+ "}",
+ ctx->issuer, ctx->scope);
+
+ *output = buf.data;
+ *outputlen = buf.len;
+}
+
+static bool
+validate(Port *port, const char *auth, char **logdetail)
+{
+ static const char * const b64_set = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789-._~+/";
+
+ const char *token;
+ size_t span;
+ int ret;
+
+ /* TODO: handle logdetail when the test framework can check it */
+
+ /*
+ * Only Bearer tokens are accepted. The ABNF is defined in RFC 6750, Sec.
+ * 2.1:
+ *
+ * b64token = 1*( ALPHA / DIGIT /
+ * "-" / "." / "_" / "~" / "+" / "/" ) *"="
+ * credentials = "Bearer" 1*SP b64token
+ *
+ * The "credentials" construction is what we receive in our auth value.
+ *
+ * Since that spec is subordinate to HTTP (i.e. the HTTP Authorization
+ * header format; RFC 7235 Sec. 2), the "Bearer" scheme string must be
+ * compared case-insensitively. (This is not mentioned in RFC 6750, but it's
+ * pointed out in RFC 7628 Sec. 4.)
+ *
+ * TODO: handle the Authorization spec, RFC 7235 Sec. 2.1.
+ */
+ if (strncasecmp(auth, BEARER_SCHEME, strlen(BEARER_SCHEME)))
+ return false;
+
+ /* Pull the bearer token out of the auth value. */
+ token = auth + strlen(BEARER_SCHEME);
+
+ /* Swallow any additional spaces. */
+ while (*token == ' ')
+ token++;
+
+ /*
+ * Before invoking the validator command, sanity-check the token format to
+ * avoid any injection attacks later in the chain. Invalid formats are
+ * technically a protocol violation, but don't reflect any information about
+ * the sensitive Bearer token back to the client; log at COMMERROR instead.
+ */
+
+ /* Tokens must not be empty. */
+ if (!*token)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Bearer token is empty.")));
+ return false;
+ }
+
+ /*
+ * Make sure the token contains only allowed characters. Tokens may end with
+ * any number of '=' characters.
+ */
+ span = strspn(token, b64_set);
+ while (token[span] == '=')
+ span++;
+
+ if (token[span] != '\0')
+ {
+ /*
+ * This error message could be more helpful by printing the problematic
+ * character(s), but that'd be a bit like printing a piece of someone's
+ * password into the logs.
+ */
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Bearer token is not in the correct format.")));
+ return false;
+ }
+
+ /* Have the validator check the token. */
+ if (!run_validator_command(port, token))
+ return false;
+
+ if (port->hba->oauth_skip_usermap)
+ {
+ /*
+ * If the validator is our authorization authority, we're done.
+ * Authentication may or may not have been performed depending on the
+ * validator implementation; all that matters is that the validator says
+ * the user can log in with the target role.
+ */
+ return true;
+ }
+
+ /* Make sure the validator authenticated the user. */
+ if (!port->authn_id)
+ {
+ /* TODO: use logdetail; reduce message duplication */
+ ereport(LOG,
+ (errmsg("OAuth bearer authentication failed for user \"%s\": validator provided no identity",
+ port->user_name)));
+ return false;
+ }
+
+ /* Finally, check the user map. */
+ ret = check_usermap(port->hba->usermap, port->user_name, port->authn_id,
+ false);
+ return (ret == STATUS_OK);
+}
+
+static bool
+run_validator_command(Port *port, const char *token)
+{
+ bool success = false;
+ int rc;
+ int pipefd[2];
+ int rfd = -1;
+ int wfd = -1;
+
+ StringInfoData command = { 0 };
+ char *p;
+ FILE *fh = NULL;
+
+ ssize_t written;
+ char *line = NULL;
+ size_t size = 0;
+ ssize_t len;
+
+ Assert(oauth_validator_command);
+
+ if (!oauth_validator_command[0])
+ {
+ ereport(COMMERROR,
+ (errmsg("oauth_validator_command is not set"),
+ errhint("To allow OAuth authenticated connections, set "
+ "oauth_validator_command in postgresql.conf.")));
+ return false;
+ }
+
+ /*
+ * Since popen() is unidirectional, open up a pipe for the other direction.
+ * Use CLOEXEC to ensure that our write end doesn't accidentally get copied
+ * into child processes, which would prevent us from closing it cleanly.
+ *
+ * XXX this is ugly. We should just read from the child process's stdout,
+ * but that's a lot more code.
+ * XXX by bypassing the popen API, we open the potential of process
+ * deadlock. Clearly document child process requirements (i.e. the child
+ * MUST read all data off of the pipe before writing anything).
+ * TODO: port to Windows using _pipe().
+ */
+ rc = pipe2(pipefd, O_CLOEXEC);
+ if (rc < 0)
+ {
+ ereport(COMMERROR,
+ (errcode_for_file_access(),
+ errmsg("could not create child pipe: %m")));
+ return false;
+ }
+
+ rfd = pipefd[0];
+ wfd = pipefd[1];
+
+ /* Allow the read pipe be passed to the child. */
+ if (!unset_cloexec(rfd))
+ {
+ /* error message was already logged */
+ goto cleanup;
+ }
+
+ /*
+ * Construct the command, substituting any recognized %-specifiers:
+ *
+ * %f: the file descriptor of the input pipe
+ * %r: the role that the client wants to assume (port->user_name)
+ * %%: a literal '%'
+ */
+ initStringInfo(&command);
+
+ for (p = oauth_validator_command; *p; p++)
+ {
+ if (p[0] == '%')
+ {
+ switch (p[1])
+ {
+ case 'f':
+ appendStringInfo(&command, "%d", rfd);
+ p++;
+ break;
+ case 'r':
+ /*
+ * TODO: decide how this string should be escaped. The role
+ * is controlled by the client, so if we don't escape it,
+ * command injections are inevitable.
+ *
+ * This is probably an indication that the role name needs
+ * to be communicated to the validator process in some other
+ * way. For this proof of concept, just be incredibly strict
+ * about the characters that are allowed in user names.
+ */
+ if (!username_ok_for_shell(port->user_name))
+ goto cleanup;
+
+ appendStringInfoString(&command, port->user_name);
+ p++;
+ break;
+ case '%':
+ appendStringInfoChar(&command, '%');
+ p++;
+ break;
+ default:
+ appendStringInfoChar(&command, p[0]);
+ }
+ }
+ else
+ appendStringInfoChar(&command, p[0]);
+ }
+
+ /* Execute the command. */
+ fh = OpenPipeStream(command.data, "re");
+ /* TODO: handle failures */
+
+ /* We don't need the read end of the pipe anymore. */
+ close(rfd);
+ rfd = -1;
+
+ /* Give the command the token to validate. */
+ written = write(wfd, token, strlen(token));
+ if (written != strlen(token))
+ {
+ /* TODO must loop for short writes, EINTR et al */
+ ereport(COMMERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write token to child pipe: %m")));
+ goto cleanup;
+ }
+
+ close(wfd);
+ wfd = -1;
+
+ /*
+ * Read the command's response.
+ *
+ * TODO: getline() is probably too new to use, unfortunately.
+ * TODO: loop over all lines
+ */
+ if ((len = getline(&line, &size, fh)) >= 0)
+ {
+ /* TODO: fail if the authn_id doesn't end with a newline */
+ if (len > 0)
+ line[len - 1] = '\0';
+
+ set_authn_id(port, line);
+ }
+ else if (ferror(fh))
+ {
+ ereport(COMMERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read from command \"%s\": %m",
+ command.data)));
+ goto cleanup;
+ }
+
+ /* Make sure the command exits cleanly. */
+ if (!check_exit(&fh, command.data))
+ {
+ /* error message already logged */
+ goto cleanup;
+ }
+
+ /* Done. */
+ success = true;
+
+cleanup:
+ if (line)
+ free(line);
+
+ /*
+ * In the successful case, the pipe fds are already closed. For the error
+ * case, always close out the pipe before waiting for the command, to
+ * prevent deadlock.
+ */
+ if (rfd >= 0)
+ close(rfd);
+ if (wfd >= 0)
+ close(wfd);
+
+ if (fh)
+ {
+ Assert(!success);
+ check_exit(&fh, command.data);
+ }
+
+ if (command.data)
+ pfree(command.data);
+
+ return success;
+}
+
+static bool
+check_exit(FILE **fh, const char *command)
+{
+ int rc;
+
+ rc = ClosePipeStream(*fh);
+ *fh = NULL;
+
+ if (rc == -1)
+ {
+ /* pclose() itself failed. */
+ ereport(COMMERROR,
+ (errcode_for_file_access(),
+ errmsg("could not close pipe to command \"%s\": %m",
+ command)));
+ }
+ else if (rc != 0)
+ {
+ char *reason = wait_result_to_str(rc);
+
+ ereport(COMMERROR,
+ (errmsg("failed to execute command \"%s\": %s",
+ command, reason)));
+
+ pfree(reason);
+ }
+
+ return (rc == 0);
+}
+
+static bool
+unset_cloexec(int fd)
+{
+ int flags;
+ int rc;
+
+ flags = fcntl(fd, F_GETFD);
+ if (flags == -1)
+ {
+ ereport(COMMERROR,
+ (errcode_for_file_access(),
+ errmsg("could not get fd flags for child pipe: %m")));
+ return false;
+ }
+
+ rc = fcntl(fd, F_SETFD, flags & ~FD_CLOEXEC);
+ if (rc < 0)
+ {
+ ereport(COMMERROR,
+ (errcode_for_file_access(),
+ errmsg("could not unset FD_CLOEXEC for child pipe: %m")));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * XXX This should go away eventually and be replaced with either a proper
+ * escape or a different strategy for communication with the validator command.
+ */
+static bool
+username_ok_for_shell(const char *username)
+{
+ /* This set is borrowed from fe_utils' appendShellStringNoError(). */
+ static const char * const allowed = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789-_./:";
+ size_t span;
+
+ Assert(username && username[0]); /* should have already been checked */
+
+ span = strspn(username, allowed);
+ if (username[span] != '\0')
+ {
+ ereport(COMMERROR,
+ (errmsg("PostgreSQL user name contains unsafe characters and cannot be passed to the OAuth validator")));
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index db3ca75a60..9e4482dc27 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -118,6 +118,8 @@ const pg_be_sasl_mech pg_be_scram_mech = {
scram_get_mechanisms,
scram_init,
scram_exchange,
+
+ PG_MAX_SASL_MESSAGE_LENGTH,
};
/*
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index e20740a7c5..354c7b0fc8 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -29,6 +29,7 @@
#include "libpq/auth.h"
#include "libpq/crypt.h"
#include "libpq/libpq.h"
+#include "libpq/oauth.h"
#include "libpq/pqformat.h"
#include "libpq/sasl.h"
#include "libpq/scram.h"
@@ -49,7 +50,6 @@ static void sendAuthRequest(Port *port, AuthRequest areq, const char *extradata,
int extralen);
static void auth_failed(Port *port, int status, char *logdetail);
static char *recv_password_packet(Port *port);
-static void set_authn_id(Port *port, const char *id);
/*----------------------------------------------------------------
* SASL common authentication
@@ -215,29 +215,12 @@ static int CheckRADIUSAuth(Port *port);
static int PerformRadiusTransaction(const char *server, const char *secret, const char *portstr, const char *identifier, const char *user_name, const char *passwd);
-/*
- * Maximum accepted size of GSS and SSPI authentication tokens.
- * We also use this as a limit on ordinary password packet lengths.
- *
- * Kerberos tickets are usually quite small, but the TGTs issued by Windows
- * domain controllers include an authorization field known as the Privilege
- * Attribute Certificate (PAC), which contains the user's Windows permissions
- * (group memberships etc.). The PAC is copied into all tickets obtained on
- * the basis of this TGT (even those issued by Unix realms which the Windows
- * realm trusts), and can be several kB in size. The maximum token size
- * accepted by Windows systems is determined by the MaxAuthToken Windows
- * registry setting. Microsoft recommends that it is not set higher than
- * 65535 bytes, so that seems like a reasonable limit for us as well.
+/*----------------------------------------------------------------
+ * OAuth v2 Bearer Authentication
+ *----------------------------------------------------------------
*/
-#define PG_MAX_AUTH_TOKEN_LENGTH 65535
+static int CheckOAuthBearer(Port *port);
-/*
- * Maximum accepted size of SASL messages.
- *
- * The messages that the server or libpq generate are much smaller than this,
- * but have some headroom.
- */
-#define PG_MAX_SASL_MESSAGE_LENGTH 1024
/*----------------------------------------------------------------
* Global authentication functions
@@ -327,6 +310,9 @@ auth_failed(Port *port, int status, char *logdetail)
case uaRADIUS:
errstr = gettext_noop("RADIUS authentication failed for user \"%s\"");
break;
+ case uaOAuth:
+ errstr = gettext_noop("OAuth bearer authentication failed for user \"%s\"");
+ break;
default:
errstr = gettext_noop("authentication failed for user \"%s\": invalid authentication method");
break;
@@ -361,7 +347,7 @@ auth_failed(Port *port, int status, char *logdetail)
* lifetime of the Port, so it is safe to pass a string that is managed by an
* external library.
*/
-static void
+void
set_authn_id(Port *port, const char *id)
{
Assert(id);
@@ -646,6 +632,9 @@ ClientAuthentication(Port *port)
case uaTrust:
status = STATUS_OK;
break;
+ case uaOAuth:
+ status = CheckOAuthBearer(port);
+ break;
}
if ((status == STATUS_OK && port->hba->clientcert == clientCertFull)
@@ -973,7 +962,7 @@ SASL_exchange(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
/* Get the actual SASL message */
initStringInfo(&buf);
- if (pq_getmessage(&buf, PG_MAX_SASL_MESSAGE_LENGTH))
+ if (pq_getmessage(&buf, mech->max_message_length))
{
/* EOF - pq_getmessage already logged error */
pfree(buf.data);
@@ -3495,3 +3484,9 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por
}
} /* while (true) */
}
+
+static int
+CheckOAuthBearer(Port *port)
+{
+ return SASL_exchange(&pg_be_oauth_mech, port, NULL, NULL);
+}
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 3be8778d21..98147700dd 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -134,7 +134,8 @@ static const char *const UserAuthName[] =
"ldap",
"cert",
"radius",
- "peer"
+ "peer",
+ "oauth",
};
@@ -1399,6 +1400,8 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
#endif
else if (strcmp(token->string, "radius") == 0)
parsedline->auth_method = uaRADIUS;
+ else if (strcmp(token->string, "oauth") == 0)
+ parsedline->auth_method = uaOAuth;
else
{
ereport(elevel,
@@ -1713,8 +1716,9 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
hbaline->auth_method != uaPeer &&
hbaline->auth_method != uaGSS &&
hbaline->auth_method != uaSSPI &&
+ hbaline->auth_method != uaOAuth &&
hbaline->auth_method != uaCert)
- INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, gssapi, sspi, and cert"));
+ INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, gssapi, sspi, oauth, and cert"));
hbaline->usermap = pstrdup(val);
}
else if (strcmp(name, "clientcert") == 0)
@@ -2098,6 +2102,27 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
hbaline->radiusidentifiers = parsed_identifiers;
hbaline->radiusidentifiers_s = pstrdup(val);
}
+ else if (strcmp(name, "issuer") == 0)
+ {
+ if (hbaline->auth_method != uaOAuth)
+ INVALID_AUTH_OPTION("issuer", gettext_noop("oauth"));
+ hbaline->oauth_issuer = pstrdup(val);
+ }
+ else if (strcmp(name, "scope") == 0)
+ {
+ if (hbaline->auth_method != uaOAuth)
+ INVALID_AUTH_OPTION("scope", gettext_noop("oauth"));
+ hbaline->oauth_scope = pstrdup(val);
+ }
+ else if (strcmp(name, "trust_validator_authz") == 0)
+ {
+ if (hbaline->auth_method != uaOAuth)
+ INVALID_AUTH_OPTION("trust_validator_authz", gettext_noop("oauth"));
+ if (strcmp(val, "1") == 0)
+ hbaline->oauth_skip_usermap = true;
+ else
+ hbaline->oauth_skip_usermap = false;
+ }
else
{
ereport(elevel,
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 68b62d523d..1ef6b3c41e 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -56,6 +56,7 @@
#include "libpq/auth.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
+#include "libpq/oauth.h"
#include "miscadmin.h"
#include "optimizer/cost.h"
#include "optimizer/geqo.h"
@@ -4587,6 +4588,17 @@ static struct config_string ConfigureNamesString[] =
check_backtrace_functions, assign_backtrace_functions, NULL
},
+ {
+ {"oauth_validator_command", PGC_SIGHUP, CONN_AUTH_AUTH,
+ gettext_noop("Command to validate OAuth v2 bearer tokens."),
+ NULL,
+ GUC_SUPERUSER_ONLY
+ },
+ &oauth_validator_command,
+ "",
+ NULL, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/include/libpq/auth.h b/src/include/libpq/auth.h
index 3610fae3ff..785cc5d16f 100644
--- a/src/include/libpq/auth.h
+++ b/src/include/libpq/auth.h
@@ -21,6 +21,7 @@ extern bool pg_krb_caseins_users;
extern char *pg_krb_realm;
extern void ClientAuthentication(Port *port);
+extern void set_authn_id(Port *port, const char *id);
/* Hook for plugins to get control in ClientAuthentication() */
typedef void (*ClientAuthentication_hook_type) (Port *, int);
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 8d9f3821b1..441dd5623e 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -38,8 +38,9 @@ typedef enum UserAuth
uaLDAP,
uaCert,
uaRADIUS,
- uaPeer
-#define USER_AUTH_LAST uaPeer /* Must be last value of this enum */
+ uaPeer,
+ uaOAuth
+#define USER_AUTH_LAST uaOAuth /* Must be last value of this enum */
} UserAuth;
/*
@@ -120,6 +121,9 @@ typedef struct HbaLine
char *radiusidentifiers_s;
List *radiusports;
char *radiusports_s;
+ char *oauth_issuer;
+ char *oauth_scope;
+ bool oauth_skip_usermap;
} HbaLine;
typedef struct IdentLine
diff --git a/src/include/libpq/oauth.h b/src/include/libpq/oauth.h
new file mode 100644
index 0000000000..870e426af1
--- /dev/null
+++ b/src/include/libpq/oauth.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth.h
+ * Interface to libpq/auth-oauth.c
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/libpq/oauth.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_OAUTH_H
+#define PG_OAUTH_H
+
+#include "libpq/libpq-be.h"
+#include "libpq/sasl.h"
+
+extern char *oauth_validator_command;
+
+/* Implementation */
+extern const pg_be_sasl_mech pg_be_oauth_mech;
+
+#endif /* PG_OAUTH_H */
diff --git a/src/include/libpq/sasl.h b/src/include/libpq/sasl.h
index 8c9c9983d4..f1341d0c54 100644
--- a/src/include/libpq/sasl.h
+++ b/src/include/libpq/sasl.h
@@ -19,6 +19,30 @@
#define SASL_EXCHANGE_SUCCESS 1
#define SASL_EXCHANGE_FAILURE 2
+/*
+ * Maximum accepted size of GSS and SSPI authentication tokens.
+ * We also use this as a limit on ordinary password packet lengths.
+ *
+ * Kerberos tickets are usually quite small, but the TGTs issued by Windows
+ * domain controllers include an authorization field known as the Privilege
+ * Attribute Certificate (PAC), which contains the user's Windows permissions
+ * (group memberships etc.). The PAC is copied into all tickets obtained on
+ * the basis of this TGT (even those issued by Unix realms which the Windows
+ * realm trusts), and can be several kB in size. The maximum token size
+ * accepted by Windows systems is determined by the MaxAuthToken Windows
+ * registry setting. Microsoft recommends that it is not set higher than
+ * 65535 bytes, so that seems like a reasonable limit for us as well.
+ */
+#define PG_MAX_AUTH_TOKEN_LENGTH 65535
+
+/*
+ * Maximum accepted size of SASL messages.
+ *
+ * The messages that the server or libpq generate are much smaller than this,
+ * but have some headroom.
+ */
+#define PG_MAX_SASL_MESSAGE_LENGTH 1024
+
/* Backend mechanism API */
typedef void (*pg_be_sasl_mechanism_func)(Port *, StringInfo);
typedef void *(*pg_be_sasl_init_func)(Port *, const char *, const char *);
@@ -29,6 +53,8 @@ typedef struct
pg_be_sasl_mechanism_func get_mechanisms;
pg_be_sasl_init_func init;
pg_be_sasl_exchange_func exchange;
+
+ int max_message_length;
} pg_be_sasl_mech;
#endif /* PG_SASL_H */
--
2.25.1
[text/x-patch] 0006-Add-a-very-simple-authn_id-extension.patch (2.8K, ../../[email protected]/7-0006-Add-a-very-simple-authn_id-extension.patch)
download | inline diff:
From e468be7ff7d19645aeb77bef21a383960a47731e Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Tue, 18 May 2021 15:01:29 -0700
Subject: [PATCH 6/7] Add a very simple authn_id extension
...for retrieving the authn_id from the server in tests.
---
contrib/authn_id/Makefile | 19 +++++++++++++++++++
contrib/authn_id/authn_id--1.0.sql | 8 ++++++++
contrib/authn_id/authn_id.c | 28 ++++++++++++++++++++++++++++
contrib/authn_id/authn_id.control | 5 +++++
4 files changed, 60 insertions(+)
create mode 100644 contrib/authn_id/Makefile
create mode 100644 contrib/authn_id/authn_id--1.0.sql
create mode 100644 contrib/authn_id/authn_id.c
create mode 100644 contrib/authn_id/authn_id.control
diff --git a/contrib/authn_id/Makefile b/contrib/authn_id/Makefile
new file mode 100644
index 0000000000..46026358e0
--- /dev/null
+++ b/contrib/authn_id/Makefile
@@ -0,0 +1,19 @@
+# contrib/authn_id/Makefile
+
+MODULE_big = authn_id
+OBJS = authn_id.o
+
+EXTENSION = authn_id
+DATA = authn_id--1.0.sql
+PGFILEDESC = "authn_id - information about the authenticated user"
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/authn_id
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/authn_id/authn_id--1.0.sql b/contrib/authn_id/authn_id--1.0.sql
new file mode 100644
index 0000000000..af2a4d3991
--- /dev/null
+++ b/contrib/authn_id/authn_id--1.0.sql
@@ -0,0 +1,8 @@
+/* contrib/authn_id/authn_id--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION authn_id" to load this file. \quit
+
+CREATE FUNCTION authn_id() RETURNS text
+AS 'MODULE_PATHNAME', 'authn_id'
+LANGUAGE C IMMUTABLE;
diff --git a/contrib/authn_id/authn_id.c b/contrib/authn_id/authn_id.c
new file mode 100644
index 0000000000..0fecac36a8
--- /dev/null
+++ b/contrib/authn_id/authn_id.c
@@ -0,0 +1,28 @@
+/*
+ * Extension to expose the current user's authn_id.
+ *
+ * contrib/authn_id/authn_id.c
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "libpq/libpq-be.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(authn_id);
+
+/*
+ * Returns the current user's authenticated identity.
+ */
+Datum
+authn_id(PG_FUNCTION_ARGS)
+{
+ if (!MyProcPort->authn_id)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(MyProcPort->authn_id));
+}
diff --git a/contrib/authn_id/authn_id.control b/contrib/authn_id/authn_id.control
new file mode 100644
index 0000000000..e0f9e06bed
--- /dev/null
+++ b/contrib/authn_id/authn_id.control
@@ -0,0 +1,5 @@
+# authn_id extension
+comment = 'current user identity'
+default_version = '1.0'
+module_pathname = '$libdir/authn_id'
+relocatable = true
--
2.25.1
[text/x-patch] 0007-Add-pytest-suite-for-OAuth.patch (131.2K, ../../[email protected]/8-0007-Add-pytest-suite-for-OAuth.patch)
download | inline diff:
From 896da918cfcd16bcb119090914f687b3e905d865 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Fri, 4 Jun 2021 09:06:38 -0700
Subject: [PATCH 7/7] Add pytest suite for OAuth
Requires Python 3; on the first run of `make installcheck` the
dependencies will be installed into ./venv for you. See the README for
more details.
---
src/test/python/.gitignore | 2 +
src/test/python/Makefile | 33 +
src/test/python/README | 49 +
src/test/python/client/__init__.py | 0
src/test/python/client/conftest.py | 126 +++
src/test/python/client/test_client.py | 180 ++++
src/test/python/client/test_oauth.py | 936 ++++++++++++++++++
src/test/python/pq3.py | 727 ++++++++++++++
src/test/python/pytest.ini | 4 +
src/test/python/requirements.txt | 7 +
src/test/python/server/__init__.py | 0
src/test/python/server/conftest.py | 45 +
src/test/python/server/test_oauth.py | 1012 ++++++++++++++++++++
src/test/python/server/test_server.py | 21 +
src/test/python/server/validate_bearer.py | 101 ++
src/test/python/server/validate_reflect.py | 34 +
src/test/python/test_internals.py | 138 +++
src/test/python/test_pq3.py | 558 +++++++++++
src/test/python/tls.py | 195 ++++
19 files changed, 4168 insertions(+)
create mode 100644 src/test/python/.gitignore
create mode 100644 src/test/python/Makefile
create mode 100644 src/test/python/README
create mode 100644 src/test/python/client/__init__.py
create mode 100644 src/test/python/client/conftest.py
create mode 100644 src/test/python/client/test_client.py
create mode 100644 src/test/python/client/test_oauth.py
create mode 100644 src/test/python/pq3.py
create mode 100644 src/test/python/pytest.ini
create mode 100644 src/test/python/requirements.txt
create mode 100644 src/test/python/server/__init__.py
create mode 100644 src/test/python/server/conftest.py
create mode 100644 src/test/python/server/test_oauth.py
create mode 100644 src/test/python/server/test_server.py
create mode 100755 src/test/python/server/validate_bearer.py
create mode 100755 src/test/python/server/validate_reflect.py
create mode 100644 src/test/python/test_internals.py
create mode 100644 src/test/python/test_pq3.py
create mode 100644 src/test/python/tls.py
diff --git a/src/test/python/.gitignore b/src/test/python/.gitignore
new file mode 100644
index 0000000000..0e8f027b2e
--- /dev/null
+++ b/src/test/python/.gitignore
@@ -0,0 +1,2 @@
+__pycache__/
+/venv/
diff --git a/src/test/python/Makefile b/src/test/python/Makefile
new file mode 100644
index 0000000000..515a995106
--- /dev/null
+++ b/src/test/python/Makefile
@@ -0,0 +1,33 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+# Only Python 3 is supported, but if it's named something different on your
+# system you can override it with the PYTHON3 variable.
+PYTHON3 := python3
+
+# All dependencies are placed into this directory. The default is .gitignored
+# for you, but you can override it if you'd like.
+VENV := ./venv
+
+override VBIN := $(VENV)/bin
+override PIP := $(VBIN)/pip
+override PYTEST := $(VBIN)/py.test
+override ISORT := $(VBIN)/isort
+override BLACK := $(VBIN)/black
+
+.PHONY: installcheck indent
+
+installcheck: $(PYTEST)
+ $(PYTEST) -v -rs
+
+indent: $(ISORT) $(BLACK)
+ $(ISORT) --profile black *.py client/*.py server/*.py
+ $(BLACK) *.py client/*.py server/*.py
+
+$(PYTEST) $(ISORT) $(BLACK): requirements.txt | $(PIP)
+ $(PIP) install -r $<
+
+$(PIP):
+ $(PYTHON3) -m venv $(VENV)
diff --git a/src/test/python/README b/src/test/python/README
new file mode 100644
index 0000000000..ceae364e81
--- /dev/null
+++ b/src/test/python/README
@@ -0,0 +1,49 @@
+A test suite for exercising both the libpq client and the server backend at the
+protocol level, based on pytest and Construct.
+
+The test suite currently assumes that the standard PG* environment variables
+point to the database under test and are sufficient to log in a superuser on
+that system. In other words, a bare `psql` needs to Just Work before the test
+suite can do its thing. For a newly built dev cluster, typically all that I need
+to do is a
+
+ export PGDATABASE=postgres
+
+but you can adjust as needed for your setup.
+
+## Requirements
+
+A supported version (3.6+) of Python.
+
+The first run of
+
+ make installcheck
+
+will install a local virtual environment and all needed dependencies.
+
+## Hacking
+
+The code style is enforced by a _very_ opinionated autoformatter. Running the
+
+ make indent
+
+recipe will invoke it for you automatically. Don't fight the tool; part of the
+zen is in knowing that if the formatter makes your code ugly, there's probably a
+cleaner way to write your code.
+
+## Advanced Usage
+
+The Makefile is there for convenience, but you don't have to use it. Activate
+the virtualenv to be able to use pytest directly:
+
+ $ source venv/bin/activate
+ $ py.test -k oauth
+ ...
+ $ py.test ./server/test_server.py
+ ...
+ $ deactivate # puts the PATH et al back the way it was before
+
+To make quick smoke tests possible, slow tests have been marked explicitly. You
+can skip them by saying e.g.
+
+ $ py.test -m 'not slow'
diff --git a/src/test/python/client/__init__.py b/src/test/python/client/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/test/python/client/conftest.py b/src/test/python/client/conftest.py
new file mode 100644
index 0000000000..f38da7a138
--- /dev/null
+++ b/src/test/python/client/conftest.py
@@ -0,0 +1,126 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import socket
+import sys
+import threading
+
+import psycopg2
+import pytest
+
+import pq3
+
+BLOCKING_TIMEOUT = 2 # the number of seconds to wait for blocking calls
+
+
[email protected]
+def server_socket(unused_tcp_port_factory):
+ """
+ Returns a listening socket bound to an ephemeral port.
+ """
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
+ s.bind(("127.0.0.1", unused_tcp_port_factory()))
+ s.listen(1)
+ s.settimeout(BLOCKING_TIMEOUT)
+ yield s
+
+
+class ClientHandshake(threading.Thread):
+ """
+ A thread that connects to a local Postgres server using psycopg2. Once the
+ opening handshake completes, the connection will be immediately closed.
+ """
+
+ def __init__(self, *, port, **kwargs):
+ super().__init__()
+
+ kwargs["port"] = port
+ self._kwargs = kwargs
+
+ self.exception = None
+
+ def run(self):
+ try:
+ conn = psycopg2.connect(host="127.0.0.1", **self._kwargs)
+ conn.close()
+ except Exception as e:
+ self.exception = e
+
+ def check_completed(self, timeout=BLOCKING_TIMEOUT):
+ """
+ Joins the client thread. Raises an exception if the thread could not be
+ joined, or if it threw an exception itself. (The exception will be
+ cleared, so future calls to check_completed will succeed.)
+ """
+ self.join(timeout)
+
+ if self.is_alive():
+ raise TimeoutError("client thread did not handshake within the timeout")
+ elif self.exception:
+ e = self.exception
+ self.exception = None
+ raise e
+
+
[email protected]
+def accept(server_socket):
+ """
+ Returns a factory function that, when called, returns a pair (sock, client)
+ where sock is a server socket that has accepted a connection from client,
+ and client is an instance of ClientHandshake. Clients will complete their
+ handshakes and cleanly disconnect.
+
+ The default connstring options may be extended or overridden by passing
+ arbitrary keyword arguments. Keep in mind that you generally should not
+ override the host or port, since they point to the local test server.
+
+ For situations where a client needs to connect more than once to complete a
+ handshake, the accept function may be called more than once. (The client
+ returned for subsequent calls will always be the same client that was
+ returned for the first call.)
+
+ Tests must either complete the handshake so that the client thread can be
+ automatically joined during teardown, or else call client.check_completed()
+ and manually handle any expected errors.
+ """
+ _, port = server_socket.getsockname()
+
+ client = None
+ default_opts = dict(
+ port=port,
+ user=pq3.pguser(),
+ sslmode="disable",
+ )
+
+ def factory(**kwargs):
+ nonlocal client
+
+ if client is None:
+ opts = dict(default_opts)
+ opts.update(kwargs)
+
+ # The server_socket is already listening, so the client thread can
+ # be safely started; it'll block on the connection until we accept.
+ client = ClientHandshake(**opts)
+ client.start()
+
+ sock, _ = server_socket.accept()
+ return sock, client
+
+ yield factory
+ client.check_completed()
+
+
[email protected]
+def conn(accept):
+ """
+ Returns an accepted, wrapped pq3 connection to a psycopg2 client. The socket
+ will be closed when the test finishes, and the client will be checked for a
+ cleanly completed handshake.
+ """
+ sock, client = accept()
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ yield conn
diff --git a/src/test/python/client/test_client.py b/src/test/python/client/test_client.py
new file mode 100644
index 0000000000..c4c946fda4
--- /dev/null
+++ b/src/test/python/client/test_client.py
@@ -0,0 +1,180 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import base64
+import sys
+
+import psycopg2
+import pytest
+from cryptography.hazmat.primitives import hashes, hmac
+
+import pq3
+
+
+def finish_handshake(conn):
+ """
+ Sends the AuthenticationOK message and the standard opening salvo of server
+ messages, then asserts that the client immediately sends a Terminate message
+ to close the connection cleanly.
+ """
+ pq3.send(conn, pq3.types.AuthnRequest, type=pq3.authn.OK)
+ pq3.send(conn, pq3.types.ParameterStatus, name=b"client_encoding", value=b"UTF-8")
+ pq3.send(conn, pq3.types.ParameterStatus, name=b"DateStyle", value=b"ISO, MDY")
+ pq3.send(conn, pq3.types.ReadyForQuery, status=b"I")
+
+ pkt = pq3.recv1(conn)
+ assert pkt.type == pq3.types.Terminate
+
+
+def test_handshake(conn):
+ startup = pq3.recv1(conn, cls=pq3.Startup)
+ assert startup.proto == pq3.protocol(3, 0)
+
+ finish_handshake(conn)
+
+
+def test_aborted_connection(accept):
+ """
+ Make sure the client correctly reports an early close during handshakes.
+ """
+ sock, client = accept()
+ sock.close()
+
+ expected = "server closed the connection unexpectedly"
+ with pytest.raises(psycopg2.OperationalError, match=expected):
+ client.check_completed()
+
+
+#
+# SCRAM-SHA-256 (see RFC 5802: https://tools.ietf.org/html/rfc5802)
+#
+
+
[email protected]
+def password():
+ """
+ Returns a password for use by both client and server.
+ """
+ # TODO: parameterize this with passwords that require SASLprep.
+ return "secret"
+
+
[email protected]
+def pwconn(accept, password):
+ """
+ Like the conn fixture, but uses a password in the connection.
+ """
+ sock, client = accept(password=password)
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ yield conn
+
+
+def sha256(data):
+ """The H(str) function from Section 2.2."""
+ digest = hashes.Hash(hashes.SHA256())
+ digest.update(data)
+ return digest.finalize()
+
+
+def hmac_256(key, data):
+ """The HMAC(key, str) function from Section 2.2."""
+ h = hmac.HMAC(key, hashes.SHA256())
+ h.update(data)
+ return h.finalize()
+
+
+def xor(a, b):
+ """The XOR operation from Section 2.2."""
+ res = bytearray(a)
+ for i, byte in enumerate(b):
+ res[i] ^= byte
+ return bytes(res)
+
+
+def h_i(data, salt, i):
+ """The Hi(str, salt, i) function from Section 2.2."""
+ assert i > 0
+
+ acc = hmac_256(data, salt + b"\x00\x00\x00\x01")
+ last = acc
+ i -= 1
+
+ while i:
+ u = hmac_256(data, last)
+ acc = xor(acc, u)
+
+ last = u
+ i -= 1
+
+ return acc
+
+
+def test_scram(pwconn, password):
+ startup = pq3.recv1(pwconn, cls=pq3.Startup)
+ assert startup.proto == pq3.protocol(3, 0)
+
+ pq3.send(
+ pwconn,
+ pq3.types.AuthnRequest,
+ type=pq3.authn.SASL,
+ body=[b"SCRAM-SHA-256", b""],
+ )
+
+ # Get the client-first-message.
+ pkt = pq3.recv1(pwconn)
+ assert pkt.type == pq3.types.PasswordMessage
+
+ initial = pq3.SASLInitialResponse.parse(pkt.payload)
+ assert initial.name == b"SCRAM-SHA-256"
+
+ c_bind, authzid, c_name, c_nonce = initial.data.split(b",")
+ assert c_bind == b"n" # no channel bindings on a plaintext connection
+ assert authzid == b"" # we don't support authzid currently
+ assert c_name == b"n=" # libpq doesn't honor the GS2 username
+ assert c_nonce.startswith(b"r=")
+
+ # Send the server-first-message.
+ salt = b"12345"
+ iterations = 2
+
+ s_nonce = c_nonce + b"somenonce"
+ s_salt = b"s=" + base64.b64encode(salt)
+ s_iterations = b"i=%d" % iterations
+
+ msg = b",".join([s_nonce, s_salt, s_iterations])
+ pq3.send(pwconn, pq3.types.AuthnRequest, type=pq3.authn.SASLContinue, body=msg)
+
+ # Get the client-final-message.
+ pkt = pq3.recv1(pwconn)
+ assert pkt.type == pq3.types.PasswordMessage
+
+ c_bind_final, c_nonce_final, c_proof = pkt.payload.split(b",")
+ assert c_bind_final == b"c=" + base64.b64encode(c_bind + b"," + authzid + b",")
+ assert c_nonce_final == s_nonce
+
+ # Calculate what the client proof should be.
+ salted_password = h_i(password.encode("ascii"), salt, iterations)
+ client_key = hmac_256(salted_password, b"Client Key")
+ stored_key = sha256(client_key)
+
+ auth_message = b",".join(
+ [c_name, c_nonce, s_nonce, s_salt, s_iterations, c_bind_final, c_nonce_final]
+ )
+ client_signature = hmac_256(stored_key, auth_message)
+ client_proof = xor(client_key, client_signature)
+
+ expected = b"p=" + base64.b64encode(client_proof)
+ assert c_proof == expected
+
+ # Send the correct server signature.
+ server_key = hmac_256(salted_password, b"Server Key")
+ server_signature = hmac_256(server_key, auth_message)
+
+ s_verify = b"v=" + base64.b64encode(server_signature)
+ pq3.send(pwconn, pq3.types.AuthnRequest, type=pq3.authn.SASLFinal, body=s_verify)
+
+ # Done!
+ finish_handshake(pwconn)
diff --git a/src/test/python/client/test_oauth.py b/src/test/python/client/test_oauth.py
new file mode 100644
index 0000000000..a754a9c0b6
--- /dev/null
+++ b/src/test/python/client/test_oauth.py
@@ -0,0 +1,936 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import base64
+import http.server
+import json
+import secrets
+import sys
+import threading
+import time
+import urllib.parse
+
+import psycopg2
+import pytest
+
+import pq3
+
+from .conftest import BLOCKING_TIMEOUT
+
+
+def finish_handshake(conn):
+ """
+ Sends the AuthenticationOK message and the standard opening salvo of server
+ messages, then asserts that the client immediately sends a Terminate message
+ to close the connection cleanly.
+ """
+ pq3.send(conn, pq3.types.AuthnRequest, type=pq3.authn.OK)
+ pq3.send(conn, pq3.types.ParameterStatus, name=b"client_encoding", value=b"UTF-8")
+ pq3.send(conn, pq3.types.ParameterStatus, name=b"DateStyle", value=b"ISO, MDY")
+ pq3.send(conn, pq3.types.ReadyForQuery, status=b"I")
+
+ pkt = pq3.recv1(conn)
+ assert pkt.type == pq3.types.Terminate
+
+
+#
+# OAUTHBEARER (see RFC 7628: https://tools.ietf.org/html/rfc7628)
+#
+
+
+def start_oauth_handshake(conn):
+ """
+ Negotiates an OAUTHBEARER SASL challenge. Returns the client's initial
+ response data.
+ """
+ startup = pq3.recv1(conn, cls=pq3.Startup)
+ assert startup.proto == pq3.protocol(3, 0)
+
+ pq3.send(
+ conn, pq3.types.AuthnRequest, type=pq3.authn.SASL, body=[b"OAUTHBEARER", b""]
+ )
+
+ pkt = pq3.recv1(conn)
+ assert pkt.type == pq3.types.PasswordMessage
+
+ initial = pq3.SASLInitialResponse.parse(pkt.payload)
+ assert initial.name == b"OAUTHBEARER"
+
+ return initial.data
+
+
+def get_auth_value(initial):
+ """
+ Finds the auth value (e.g. "Bearer somedata..." in the client's initial SASL
+ response.
+ """
+ kvpairs = initial.split(b"\x01")
+ assert kvpairs[0] == b"n,," # no channel binding or authzid
+ assert kvpairs[2] == b"" # ends with an empty kvpair
+ assert kvpairs[3] == b"" # ...and there's nothing after it
+ assert len(kvpairs) == 4
+
+ key, value = kvpairs[1].split(b"=", 2)
+ assert key == b"auth"
+
+ return value
+
+
+def xtest_oauth_success(conn): # TODO
+ initial = start_oauth_handshake(conn)
+
+ auth = get_auth_value(initial)
+ assert auth.startswith(b"Bearer ")
+
+ # Accept the token. TODO actually validate
+ pq3.send(conn, pq3.types.AuthnRequest, type=pq3.authn.SASLFinal)
+ finish_handshake(conn)
+
+
+class OpenIDProvider(threading.Thread):
+ """
+ A thread that runs a mock OpenID provider server.
+ """
+
+ def __init__(self, *, port):
+ super().__init__()
+
+ self.exception = None
+
+ addr = ("", port)
+ self.server = self._Server(addr, self._Handler)
+
+ # TODO: allow HTTPS only, somehow
+ oauth = self._OAuthState()
+ oauth.host = f"localhost:{port}"
+ oauth.issuer = f"http://localhost:{port}"
+
+ # The following endpoints are required to be advertised by providers,
+ # even though our chosen client implementation does not actually make
+ # use of them.
+ oauth.register_endpoint(
+ "authorization_endpoint", "POST", "/authorize", self._authorization_handler
+ )
+ oauth.register_endpoint("jwks_uri", "GET", "/keys", self._jwks_handler)
+
+ self.server.oauth = oauth
+
+ def run(self):
+ try:
+ self.server.serve_forever()
+ except Exception as e:
+ self.exception = e
+
+ def stop(self, timeout=BLOCKING_TIMEOUT):
+ """
+ Shuts down the server and joins its thread. Raises an exception if the
+ thread could not be joined, or if it threw an exception itself. Must
+ only be called once, after start().
+ """
+ self.server.shutdown()
+ self.join(timeout)
+
+ if self.is_alive():
+ raise TimeoutError("client thread did not handshake within the timeout")
+ elif self.exception:
+ e = self.exception
+ raise e
+
+ class _OAuthState(object):
+ def __init__(self):
+ self.endpoint_paths = {}
+ self._endpoints = {}
+
+ def register_endpoint(self, name, method, path, func):
+ if method not in self._endpoints:
+ self._endpoints[method] = {}
+
+ self._endpoints[method][path] = func
+ self.endpoint_paths[name] = path
+
+ def endpoint(self, method, path):
+ if method not in self._endpoints:
+ return None
+
+ return self._endpoints[method].get(path)
+
+ class _Server(http.server.HTTPServer):
+ def handle_error(self, request, addr):
+ self.shutdown_request(request)
+ raise
+
+ @staticmethod
+ def _jwks_handler(headers, params):
+ return 200, {"keys": []}
+
+ @staticmethod
+ def _authorization_handler(headers, params):
+ # We don't actually want this to be called during these tests -- we
+ # should be using the device authorization endpoint instead.
+ assert (
+ False
+ ), "authorization handler called instead of device authorization handler"
+
+ class _Handler(http.server.BaseHTTPRequestHandler):
+ timeout = BLOCKING_TIMEOUT
+
+ def _discovery_handler(self, headers, params):
+ oauth = self.server.oauth
+
+ doc = {
+ "issuer": oauth.issuer,
+ "response_types_supported": ["token"],
+ "subject_types_supported": ["public"],
+ "id_token_signing_alg_values_supported": ["RS256"],
+ }
+
+ for name, path in oauth.endpoint_paths.items():
+ doc[name] = oauth.issuer + path
+
+ return 200, doc
+
+ def _handle(self, *, params=None, handler=None):
+ oauth = self.server.oauth
+ assert self.headers["Host"] == oauth.host
+
+ if handler is None:
+ handler = oauth.endpoint(self.command, self.path)
+ assert (
+ handler is not None
+ ), f"no registered endpoint for {self.command} {self.path}"
+
+ code, resp = handler(self.headers, params)
+
+ self.send_response(code)
+ self.send_header("Content-Type", "application/json")
+ self.end_headers()
+
+ resp = json.dumps(resp)
+ resp = resp.encode("utf-8")
+ self.wfile.write(resp)
+
+ self.close_connection = True
+
+ def do_GET(self):
+ if self.path == "/.well-known/openid-configuration":
+ self._handle(handler=self._discovery_handler)
+ return
+
+ self._handle()
+
+ def _request_body(self):
+ length = self.headers["Content-Length"]
+
+ # Handle only an explicit content-length.
+ assert length is not None
+ length = int(length)
+
+ return self.rfile.read(length).decode("utf-8")
+
+ def do_POST(self):
+ assert self.headers["Content-Type"] == "application/x-www-form-urlencoded"
+
+ body = self._request_body()
+ params = urllib.parse.parse_qs(body)
+
+ self._handle(params=params)
+
+
[email protected]
+def openid_provider(unused_tcp_port_factory):
+ """
+ A fixture that returns the OAuth state of a running OpenID provider server. The
+ server will be stopped when the fixture is torn down.
+ """
+ thread = OpenIDProvider(port=unused_tcp_port_factory())
+ thread.start()
+
+ try:
+ yield thread.server.oauth
+ finally:
+ thread.stop()
+
+
[email protected]("secret", [None, "", "hunter2"])
[email protected]("scope", [None, "", "openid email"])
[email protected]("retries", [0, 1])
+def test_oauth_with_explicit_issuer(
+ capfd, accept, openid_provider, retries, scope, secret
+):
+ client_id = secrets.token_hex()
+
+ sock, client = accept(
+ oauth_issuer=openid_provider.issuer,
+ oauth_client_id=client_id,
+ oauth_client_secret=secret,
+ oauth_scope=scope,
+ )
+
+ device_code = secrets.token_hex()
+ user_code = f"{secrets.token_hex(2)}-{secrets.token_hex(2)}"
+ verification_url = "https://example.com/device"
+
+ access_token = secrets.token_urlsafe()
+
+ def check_client_authn(headers, params):
+ if not secret:
+ assert params["client_id"] == [client_id]
+ return
+
+ # Require the client to use Basic authn; request-body credentials are
+ # NOT RECOMMENDED (RFC 6749, Sec. 2.3.1).
+ assert "Authorization" in headers
+
+ method, creds = headers["Authorization"].split()
+ assert method == "Basic"
+
+ expected = f"{client_id}:{secret}"
+ assert base64.b64decode(creds) == expected.encode("ascii")
+
+ # Set up our provider callbacks.
+ # NOTE that these callbacks will be called on a background thread. Don't do
+ # any unprotected state mutation here.
+
+ def authorization_endpoint(headers, params):
+ check_client_authn(headers, params)
+
+ if scope:
+ assert params["scope"] == [scope]
+ else:
+ assert "scope" not in params
+
+ resp = {
+ "device_code": device_code,
+ "user_code": user_code,
+ "interval": 0,
+ "verification_uri": verification_url,
+ "expires_in": 5,
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "device_authorization_endpoint", "POST", "/device", authorization_endpoint
+ )
+
+ attempts = 0
+ retry_lock = threading.Lock()
+
+ def token_endpoint(headers, params):
+ check_client_authn(headers, params)
+
+ assert params["grant_type"] == ["urn:ietf:params:oauth:grant-type:device_code"]
+ assert params["device_code"] == [device_code]
+
+ now = time.monotonic()
+
+ with retry_lock:
+ nonlocal attempts
+
+ # If the test wants to force the client to retry, return an
+ # authorization_pending response and decrement the retry count.
+ if attempts < retries:
+ attempts += 1
+ return 400, {"error": "authorization_pending"}
+
+ # Successfully finish the request by sending the access bearer token.
+ resp = {
+ "access_token": access_token,
+ "token_type": "bearer",
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "token_endpoint", "POST", "/token", token_endpoint
+ )
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ # Initiate a handshake, which should result in the above endpoints
+ # being called.
+ initial = start_oauth_handshake(conn)
+
+ # Validate and accept the token.
+ auth = get_auth_value(initial)
+ assert auth == f"Bearer {access_token}".encode("ascii")
+
+ pq3.send(conn, pq3.types.AuthnRequest, type=pq3.authn.SASLFinal)
+ finish_handshake(conn)
+
+ if retries:
+ # Finally, make sure that the client prompted the user with the expected
+ # authorization URL and user code.
+ expected = f"Visit {verification_url} and enter the code: {user_code}"
+ _, stderr = capfd.readouterr()
+ assert expected in stderr
+
+
+def test_oauth_requires_client_id(accept, openid_provider):
+ sock, client = accept(
+ oauth_issuer=openid_provider.issuer,
+ # Do not set a client ID; this should cause a client error after the
+ # server asks for OAUTHBEARER and the client tries to contact the
+ # issuer.
+ )
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ # Initiate a handshake.
+ startup = pq3.recv1(conn, cls=pq3.Startup)
+ assert startup.proto == pq3.protocol(3, 0)
+
+ pq3.send(
+ conn,
+ pq3.types.AuthnRequest,
+ type=pq3.authn.SASL,
+ body=[b"OAUTHBEARER", b""],
+ )
+
+ # The client should disconnect at this point.
+ assert not conn.read()
+
+ expected_error = "no oauth_client_id is set"
+ with pytest.raises(psycopg2.OperationalError, match=expected_error):
+ client.check_completed()
+
+
[email protected]
[email protected]("error_code", ["authorization_pending", "slow_down"])
[email protected]("retries", [1, 2])
+def test_oauth_retry_interval(accept, openid_provider, retries, error_code):
+ sock, client = accept(
+ oauth_issuer=openid_provider.issuer,
+ oauth_client_id="some-id",
+ )
+
+ expected_retry_interval = 1
+ access_token = secrets.token_urlsafe()
+
+ # Set up our provider callbacks.
+ # NOTE that these callbacks will be called on a background thread. Don't do
+ # any unprotected state mutation here.
+
+ def authorization_endpoint(headers, params):
+ resp = {
+ "device_code": "my-device-code",
+ "user_code": "my-user-code",
+ "interval": expected_retry_interval,
+ "verification_uri": "https://example.com",
+ "expires_in": 5,
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "device_authorization_endpoint", "POST", "/device", authorization_endpoint
+ )
+
+ attempts = 0
+ last_retry = None
+ retry_lock = threading.Lock()
+
+ def token_endpoint(headers, params):
+ now = time.monotonic()
+
+ with retry_lock:
+ nonlocal attempts, last_retry, expected_retry_interval
+
+ # Make sure the retry interval is being respected by the client.
+ if last_retry is not None:
+ interval = now - last_retry
+ assert interval >= expected_retry_interval
+
+ last_retry = now
+
+ # If the test wants to force the client to retry, return the desired
+ # error response and decrement the retry count.
+ if attempts < retries:
+ attempts += 1
+
+ # A slow_down code requires the client to additionally increase
+ # its interval by five seconds.
+ if error_code == "slow_down":
+ expected_retry_interval += 5
+
+ return 400, {"error": error_code}
+
+ # Successfully finish the request by sending the access bearer token.
+ resp = {
+ "access_token": access_token,
+ "token_type": "bearer",
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "token_endpoint", "POST", "/token", token_endpoint
+ )
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ # Initiate a handshake, which should result in the above endpoints
+ # being called.
+ initial = start_oauth_handshake(conn)
+
+ # Validate and accept the token.
+ auth = get_auth_value(initial)
+ assert auth == f"Bearer {access_token}".encode("ascii")
+
+ pq3.send(conn, pq3.types.AuthnRequest, type=pq3.authn.SASLFinal)
+ finish_handshake(conn)
+
+
[email protected](
+ "failure_mode, error_pattern",
+ [
+ pytest.param(
+ {
+ "error": "invalid_client",
+ "error_description": "client authentication failed",
+ },
+ r"client authentication failed \(invalid_client\)",
+ id="authentication failure with description",
+ ),
+ pytest.param(
+ {"error": "invalid_request"},
+ r"\(invalid_request\)",
+ id="invalid request without description",
+ ),
+ pytest.param(
+ {},
+ r"failed to obtain device authorization",
+ id="broken error response",
+ ),
+ ],
+)
+def test_oauth_device_authorization_failures(
+ accept, openid_provider, failure_mode, error_pattern
+):
+ client_id = secrets.token_hex()
+
+ sock, client = accept(
+ oauth_issuer=openid_provider.issuer,
+ oauth_client_id=client_id,
+ )
+
+ # Set up our provider callbacks.
+ # NOTE that these callbacks will be called on a background thread. Don't do
+ # any unprotected state mutation here.
+
+ def authorization_endpoint(headers, params):
+ return 400, failure_mode
+
+ openid_provider.register_endpoint(
+ "device_authorization_endpoint", "POST", "/device", authorization_endpoint
+ )
+
+ def token_endpoint(headers, params):
+ assert False, "token endpoint was invoked unexpectedly"
+
+ openid_provider.register_endpoint(
+ "token_endpoint", "POST", "/token", token_endpoint
+ )
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ # Initiate a handshake, which should result in the above endpoints
+ # being called.
+ startup = pq3.recv1(conn, cls=pq3.Startup)
+ assert startup.proto == pq3.protocol(3, 0)
+
+ pq3.send(
+ conn,
+ pq3.types.AuthnRequest,
+ type=pq3.authn.SASL,
+ body=[b"OAUTHBEARER", b""],
+ )
+
+ # The client should not continue the connection due to the hardcoded
+ # provider failure; we disconnect here.
+
+ # Now make sure the client correctly failed.
+ with pytest.raises(psycopg2.OperationalError, match=error_pattern):
+ client.check_completed()
+
+
[email protected](
+ "failure_mode, error_pattern",
+ [
+ pytest.param(
+ {
+ "error": "expired_token",
+ "error_description": "the device code has expired",
+ },
+ r"the device code has expired \(expired_token\)",
+ id="expired token with description",
+ ),
+ pytest.param(
+ {"error": "access_denied"},
+ r"\(access_denied\)",
+ id="access denied without description",
+ ),
+ pytest.param(
+ {},
+ r"OAuth token retrieval failed",
+ id="broken error response",
+ ),
+ ],
+)
[email protected]("retries", [0, 1])
+def test_oauth_token_failures(
+ accept, openid_provider, retries, failure_mode, error_pattern
+):
+ client_id = secrets.token_hex()
+
+ sock, client = accept(
+ oauth_issuer=openid_provider.issuer,
+ oauth_client_id=client_id,
+ )
+
+ device_code = secrets.token_hex()
+ user_code = f"{secrets.token_hex(2)}-{secrets.token_hex(2)}"
+
+ # Set up our provider callbacks.
+ # NOTE that these callbacks will be called on a background thread. Don't do
+ # any unprotected state mutation here.
+
+ def authorization_endpoint(headers, params):
+ assert params["client_id"] == [client_id]
+
+ resp = {
+ "device_code": device_code,
+ "user_code": user_code,
+ "interval": 0,
+ "verification_uri": "https://example.com/device",
+ "expires_in": 5,
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "device_authorization_endpoint", "POST", "/device", authorization_endpoint
+ )
+
+ retry_lock = threading.Lock()
+
+ def token_endpoint(headers, params):
+ with retry_lock:
+ nonlocal retries
+
+ # If the test wants to force the client to retry, return an
+ # authorization_pending response and decrement the retry count.
+ if retries > 0:
+ retries -= 1
+ return 400, {"error": "authorization_pending"}
+
+ return 400, failure_mode
+
+ openid_provider.register_endpoint(
+ "token_endpoint", "POST", "/token", token_endpoint
+ )
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ # Initiate a handshake, which should result in the above endpoints
+ # being called.
+ startup = pq3.recv1(conn, cls=pq3.Startup)
+ assert startup.proto == pq3.protocol(3, 0)
+
+ pq3.send(
+ conn,
+ pq3.types.AuthnRequest,
+ type=pq3.authn.SASL,
+ body=[b"OAUTHBEARER", b""],
+ )
+
+ # The client should not continue the connection due to the hardcoded
+ # provider failure; we disconnect here.
+
+ # Now make sure the client correctly failed.
+ with pytest.raises(psycopg2.OperationalError, match=error_pattern):
+ client.check_completed()
+
+
[email protected]("scope", [None, "openid email"])
[email protected](
+ "base_response",
+ [
+ {"status": "invalid_token"},
+ {"extra_object": {"key": "value"}, "status": "invalid_token"},
+ {"extra_object": {"status": 1}, "status": "invalid_token"},
+ ],
+)
+def test_oauth_discovery(accept, openid_provider, base_response, scope):
+ sock, client = accept(oauth_client_id=secrets.token_hex())
+
+ device_code = secrets.token_hex()
+ user_code = f"{secrets.token_hex(2)}-{secrets.token_hex(2)}"
+ verification_url = "https://example.com/device"
+
+ access_token = secrets.token_urlsafe()
+
+ # Set up our provider callbacks.
+ # NOTE that these callbacks will be called on a background thread. Don't do
+ # any unprotected state mutation here.
+
+ def authorization_endpoint(headers, params):
+ if scope:
+ assert params["scope"] == [scope]
+ else:
+ assert "scope" not in params
+
+ resp = {
+ "device_code": device_code,
+ "user_code": user_code,
+ "interval": 0,
+ "verification_uri": verification_url,
+ "expires_in": 5,
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "device_authorization_endpoint", "POST", "/device", authorization_endpoint
+ )
+
+ def token_endpoint(headers, params):
+ assert params["grant_type"] == ["urn:ietf:params:oauth:grant-type:device_code"]
+ assert params["device_code"] == [device_code]
+
+ # Successfully finish the request by sending the access bearer token.
+ resp = {
+ "access_token": access_token,
+ "token_type": "bearer",
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "token_endpoint", "POST", "/token", token_endpoint
+ )
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ initial = start_oauth_handshake(conn)
+
+ # For discovery, the client should send an empty auth header. See
+ # RFC 7628, Sec. 4.3.
+ auth = get_auth_value(initial)
+ assert auth == b""
+
+ # We will fail the first SASL exchange. First return a link to the
+ # discovery document, pointing to the test provider server.
+ resp = dict(base_response)
+
+ discovery_uri = f"{openid_provider.issuer}/.well-known/openid-configuration"
+ resp["openid-configuration"] = discovery_uri
+
+ if scope:
+ resp["scope"] = scope
+
+ resp = json.dumps(resp)
+
+ pq3.send(
+ conn,
+ pq3.types.AuthnRequest,
+ type=pq3.authn.SASLContinue,
+ body=resp.encode("ascii"),
+ )
+
+ # Per RFC, the client is required to send a dummy ^A response.
+ pkt = pq3.recv1(conn)
+ assert pkt.type == pq3.types.PasswordMessage
+ assert pkt.payload == b"\x01"
+
+ # Now fail the SASL exchange.
+ pq3.send(
+ conn,
+ pq3.types.ErrorResponse,
+ fields=[
+ b"SFATAL",
+ b"C28000",
+ b"Mdoesn't matter",
+ b"",
+ ],
+ )
+
+ # The client will connect to us a second time, using the parameters we sent
+ # it.
+ sock, _ = accept()
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ initial = start_oauth_handshake(conn)
+
+ # Validate and accept the token.
+ auth = get_auth_value(initial)
+ assert auth == f"Bearer {access_token}".encode("ascii")
+
+ pq3.send(conn, pq3.types.AuthnRequest, type=pq3.authn.SASLFinal)
+ finish_handshake(conn)
+
+
[email protected](
+ "response,expected_error",
+ [
+ pytest.param(
+ "abcde",
+ 'Token "abcde" is invalid',
+ id="bad JSON: invalid syntax",
+ ),
+ pytest.param(
+ '"abcde"',
+ "top-level element must be an object",
+ id="bad JSON: top-level element is a string",
+ ),
+ pytest.param(
+ "[]",
+ "top-level element must be an object",
+ id="bad JSON: top-level element is an array",
+ ),
+ pytest.param(
+ "{}",
+ "server sent error response without a status",
+ id="bad JSON: no status member",
+ ),
+ pytest.param(
+ '{ "status": null }',
+ 'field "status" must be a string',
+ id="bad JSON: null status member",
+ ),
+ pytest.param(
+ '{ "status": 0 }',
+ 'field "status" must be a string',
+ id="bad JSON: int status member",
+ ),
+ pytest.param(
+ '{ "status": [ "bad" ] }',
+ 'field "status" must be a string',
+ id="bad JSON: array status member",
+ ),
+ pytest.param(
+ '{ "status": { "bad": "bad" } }',
+ 'field "status" must be a string',
+ id="bad JSON: object status member",
+ ),
+ pytest.param(
+ '{ "nested": { "status": "bad" } }',
+ "server sent error response without a status",
+ id="bad JSON: nested status",
+ ),
+ pytest.param(
+ '{ "status": "invalid_token" ',
+ "The input string ended unexpectedly",
+ id="bad JSON: unterminated object",
+ ),
+ pytest.param(
+ '{ "status": "invalid_token" } { }',
+ 'Expected end of input, but found "{"',
+ id="bad JSON: trailing data",
+ ),
+ pytest.param(
+ '{ "status": "invalid_token", "openid-configuration": 1 }',
+ 'field "openid-configuration" must be a string',
+ id="bad JSON: int openid-configuration member",
+ ),
+ pytest.param(
+ '{ "status": "invalid_token", "openid-configuration": 1 }',
+ 'field "openid-configuration" must be a string',
+ id="bad JSON: int openid-configuration member",
+ ),
+ pytest.param(
+ '{ "status": "invalid_token", "scope": 1 }',
+ 'field "scope" must be a string',
+ id="bad JSON: int scope member",
+ ),
+ ],
+)
+def test_oauth_discovery_server_error(accept, response, expected_error):
+ sock, client = accept(oauth_client_id=secrets.token_hex())
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ initial = start_oauth_handshake(conn)
+
+ # Fail the SASL exchange with an invalid JSON response.
+ pq3.send(
+ conn,
+ pq3.types.AuthnRequest,
+ type=pq3.authn.SASLContinue,
+ body=response.encode("utf-8"),
+ )
+
+ # The client should disconnect, so the socket is closed here. (If
+ # the client doesn't disconnect, it will report a different error
+ # below and the test will fail.)
+
+ with pytest.raises(psycopg2.OperationalError, match=expected_error):
+ client.check_completed()
+
+
[email protected](
+ "sasl_err,resp_type,resp_payload,expected_error",
+ [
+ pytest.param(
+ {"status": "invalid_request"},
+ pq3.types.ErrorResponse,
+ dict(
+ fields=[b"SFATAL", b"C28000", b"Mexpected error message", b""],
+ ),
+ "expected error message",
+ id="standard server error: invalid_request",
+ ),
+ pytest.param(
+ {"status": "invalid_token"},
+ pq3.types.ErrorResponse,
+ dict(
+ fields=[b"SFATAL", b"C28000", b"Mexpected error message", b""],
+ ),
+ "expected error message",
+ id="standard server error: invalid_token without discovery URI",
+ ),
+ pytest.param(
+ {"status": "invalid_request"},
+ pq3.types.AuthnRequest,
+ dict(type=pq3.authn.SASLContinue, body=b""),
+ "server sent additional OAuth data",
+ id="broken server: additional challenge after error",
+ ),
+ pytest.param(
+ {"status": "invalid_request"},
+ pq3.types.AuthnRequest,
+ dict(type=pq3.authn.SASLFinal),
+ "server sent additional OAuth data",
+ id="broken server: SASL success after error",
+ ),
+ ],
+)
+def test_oauth_server_error(accept, sasl_err, resp_type, resp_payload, expected_error):
+ sock, client = accept()
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ start_oauth_handshake(conn)
+
+ # Ignore the client data. Return an error "challenge".
+ resp = json.dumps(sasl_err)
+ resp = resp.encode("utf-8")
+
+ pq3.send(
+ conn, pq3.types.AuthnRequest, type=pq3.authn.SASLContinue, body=resp
+ )
+
+ # Per RFC, the client is required to send a dummy ^A response.
+ pkt = pq3.recv1(conn)
+ assert pkt.type == pq3.types.PasswordMessage
+ assert pkt.payload == b"\x01"
+
+ # Now fail the SASL exchange (in either a valid way, or an invalid
+ # one, depending on the test).
+ pq3.send(conn, resp_type, **resp_payload)
+
+ with pytest.raises(psycopg2.OperationalError, match=expected_error):
+ client.check_completed()
diff --git a/src/test/python/pq3.py b/src/test/python/pq3.py
new file mode 100644
index 0000000000..3a22dad0b6
--- /dev/null
+++ b/src/test/python/pq3.py
@@ -0,0 +1,727 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import contextlib
+import getpass
+import io
+import os
+import ssl
+import sys
+import textwrap
+
+from construct import *
+
+import tls
+
+
+def protocol(major, minor):
+ """
+ Returns the protocol version, in integer format, corresponding to the given
+ major and minor version numbers.
+ """
+ return (major << 16) | minor
+
+
+# Startup
+
+StringList = GreedyRange(NullTerminated(GreedyBytes))
+
+
+class KeyValueAdapter(Adapter):
+ """
+ Turns a key-value store into a null-terminated list of null-terminated
+ strings, as presented on the wire in the startup packet.
+ """
+
+ def _encode(self, obj, context, path):
+ if isinstance(obj, list):
+ return obj
+
+ l = []
+
+ for k, v in obj.items():
+ if isinstance(k, str):
+ k = k.encode("utf-8")
+ l.append(k)
+
+ if isinstance(v, str):
+ v = v.encode("utf-8")
+ l.append(v)
+
+ l.append(b"")
+ return l
+
+ def _decode(self, obj, context, path):
+ # TODO: turn a list back into a dict
+ return obj
+
+
+KeyValues = KeyValueAdapter(StringList)
+
+_startup_payload = Switch(
+ this.proto,
+ {
+ protocol(3, 0): KeyValues,
+ },
+ default=GreedyBytes,
+)
+
+
+def _default_protocol(this):
+ try:
+ if isinstance(this.payload, (list, dict)):
+ return protocol(3, 0)
+ except AttributeError:
+ pass # no payload passed during build
+
+ return 0
+
+
+def _startup_payload_len(this):
+ """
+ The payload field has a fixed size based on the length of the packet. But
+ if the caller hasn't supplied an explicit length at build time, we have to
+ build the payload to figure out how long it is, which requires us to know
+ the length first... This function exists solely to break the cycle.
+ """
+ assert this._building, "_startup_payload_len() cannot be called during parsing"
+
+ try:
+ payload = this.payload
+ except AttributeError:
+ return 0 # no payload
+
+ if isinstance(payload, bytes):
+ # already serialized; just use the given length
+ return len(payload)
+
+ try:
+ proto = this.proto
+ except AttributeError:
+ proto = _default_protocol(this)
+
+ data = _startup_payload.build(payload, proto=proto)
+ return len(data)
+
+
+Startup = Struct(
+ "len" / Default(Int32sb, lambda this: _startup_payload_len(this) + 8),
+ "proto" / Default(Hex(Int32sb), _default_protocol),
+ "payload" / FixedSized(this.len - 8, Default(_startup_payload, b"")),
+)
+
+# Pq3
+
+# Adapted from construct.core.EnumIntegerString
+class EnumNamedByte:
+ def __init__(self, val, name):
+ self._val = val
+ self._name = name
+
+ def __int__(self):
+ return ord(self._val)
+
+ def __str__(self):
+ return "(enum) %s %r" % (self._name, self._val)
+
+ def __repr__(self):
+ return "EnumNamedByte(%r)" % self._val
+
+ def __eq__(self, other):
+ if isinstance(other, EnumNamedByte):
+ other = other._val
+ if not isinstance(other, bytes):
+ return NotImplemented
+
+ return self._val == other
+
+ def __hash__(self):
+ return hash(self._val)
+
+
+# Adapted from construct.core.Enum
+class ByteEnum(Adapter):
+ def __init__(self, **mapping):
+ super(ByteEnum, self).__init__(Byte)
+ self.namemapping = {k: EnumNamedByte(v, k) for k, v in mapping.items()}
+ self.decmapping = {v: EnumNamedByte(v, k) for k, v in mapping.items()}
+
+ def __getattr__(self, name):
+ if name in self.namemapping:
+ return self.decmapping[self.namemapping[name]]
+ raise AttributeError
+
+ def _decode(self, obj, context, path):
+ b = bytes([obj])
+ try:
+ return self.decmapping[b]
+ except KeyError:
+ return EnumNamedByte(b, "(unknown)")
+
+ def _encode(self, obj, context, path):
+ if isinstance(obj, int):
+ return obj
+ elif isinstance(obj, bytes):
+ return ord(obj)
+ return int(obj)
+
+
+types = ByteEnum(
+ ErrorResponse=b"E",
+ ReadyForQuery=b"Z",
+ Query=b"Q",
+ EmptyQueryResponse=b"I",
+ AuthnRequest=b"R",
+ PasswordMessage=b"p",
+ BackendKeyData=b"K",
+ CommandComplete=b"C",
+ ParameterStatus=b"S",
+ DataRow=b"D",
+ Terminate=b"X",
+)
+
+
+authn = Enum(
+ Int32ub,
+ OK=0,
+ SASL=10,
+ SASLContinue=11,
+ SASLFinal=12,
+)
+
+
+_authn_body = Switch(
+ this.type,
+ {
+ authn.OK: Terminated,
+ authn.SASL: StringList,
+ },
+ default=GreedyBytes,
+)
+
+
+def _data_len(this):
+ assert this._building, "_data_len() cannot be called during parsing"
+
+ if not hasattr(this, "data") or this.data is None:
+ return -1
+
+ return len(this.data)
+
+
+# The protocol reuses the PasswordMessage for several authentication response
+# types, and there's no good way to figure out which is which without keeping
+# state for the entire stream. So this is a separate Construct that can be
+# explicitly parsed/built by code that knows it's needed.
+SASLInitialResponse = Struct(
+ "name" / NullTerminated(GreedyBytes),
+ "len" / Default(Int32sb, lambda this: _data_len(this)),
+ "data"
+ / IfThenElse(
+ # Allow tests to explicitly pass an incorrect length during testing, by
+ # not enforcing a FixedSized during build. (The len calculation above
+ # defaults to the correct size.)
+ this._building,
+ Optional(GreedyBytes),
+ If(this.len != -1, Default(FixedSized(this.len, GreedyBytes), b"")),
+ ),
+ Terminated, # make sure the entire response is consumed
+)
+
+
+_column = FocusedSeq(
+ "data",
+ "len" / Default(Int32sb, lambda this: _data_len(this)),
+ "data" / If(this.len != -1, FixedSized(this.len, GreedyBytes)),
+)
+
+
+_payload_map = {
+ types.ErrorResponse: Struct("fields" / StringList),
+ types.ReadyForQuery: Struct("status" / Bytes(1)),
+ types.Query: Struct("query" / NullTerminated(GreedyBytes)),
+ types.EmptyQueryResponse: Terminated,
+ types.AuthnRequest: Struct("type" / authn, "body" / Default(_authn_body, b"")),
+ types.BackendKeyData: Struct("pid" / Int32ub, "key" / Hex(Int32ub)),
+ types.CommandComplete: Struct("tag" / NullTerminated(GreedyBytes)),
+ types.ParameterStatus: Struct(
+ "name" / NullTerminated(GreedyBytes), "value" / NullTerminated(GreedyBytes)
+ ),
+ types.DataRow: Struct("columns" / Default(PrefixedArray(Int16sb, _column), b"")),
+ types.Terminate: Terminated,
+}
+
+
+_payload = FocusedSeq(
+ "_payload",
+ "_payload"
+ / Switch(
+ this._.type,
+ _payload_map,
+ default=GreedyBytes,
+ ),
+ Terminated, # make sure every payload consumes the entire packet
+)
+
+
+def _payload_len(this):
+ """
+ See _startup_payload_len() for an explanation.
+ """
+ assert this._building, "_payload_len() cannot be called during parsing"
+
+ try:
+ payload = this.payload
+ except AttributeError:
+ return 0 # no payload
+
+ if isinstance(payload, bytes):
+ # already serialized; just use the given length
+ return len(payload)
+
+ data = _payload.build(payload, type=this.type)
+ return len(data)
+
+
+Pq3 = Struct(
+ "type" / types,
+ "len" / Default(Int32ub, lambda this: _payload_len(this) + 4),
+ "payload" / FixedSized(this.len - 4, Default(_payload, b"")),
+)
+
+
+# Environment
+
+
+def pghost():
+ return os.environ.get("PGHOST", default="localhost")
+
+
+def pgport():
+ return int(os.environ.get("PGPORT", default=5432))
+
+
+def pguser():
+ try:
+ return os.environ["PGUSER"]
+ except KeyError:
+ return getpass.getuser()
+
+
+def pgdatabase():
+ return os.environ.get("PGDATABASE", default="postgres")
+
+
+# Connections
+
+
+def _hexdump_translation_map():
+ """
+ For hexdumps. Translates any unprintable or non-ASCII bytes into '.'.
+ """
+ input = bytearray()
+
+ for i in range(128):
+ c = chr(i)
+
+ if not c.isprintable():
+ input += bytes([i])
+
+ input += bytes(range(128, 256))
+
+ return bytes.maketrans(input, b"." * len(input))
+
+
+class _DebugStream(object):
+ """
+ Wraps a file-like object and adds hexdumps of the read and write data. Call
+ end_packet() on a _DebugStream to write the accumulated hexdumps to the
+ output stream, along with the packet that was sent.
+ """
+
+ _translation_map = _hexdump_translation_map()
+
+ def __init__(self, stream, out=sys.stdout):
+ """
+ Creates a new _DebugStream wrapping the given stream (which must have
+ been created by wrap()). All attributes not provided by the _DebugStream
+ are delegated to the wrapped stream. out is the text stream to which
+ hexdumps are written.
+ """
+ self.raw = stream
+ self._out = out
+ self._rbuf = io.BytesIO()
+ self._wbuf = io.BytesIO()
+
+ def __getattr__(self, name):
+ return getattr(self.raw, name)
+
+ def __setattr__(self, name, value):
+ if name in ("raw", "_out", "_rbuf", "_wbuf"):
+ return object.__setattr__(self, name, value)
+
+ setattr(self.raw, name, value)
+
+ def read(self, *args, **kwargs):
+ buf = self.raw.read(*args, **kwargs)
+
+ self._rbuf.write(buf)
+ return buf
+
+ def write(self, b):
+ self._wbuf.write(b)
+ return self.raw.write(b)
+
+ def recv(self, *args):
+ buf = self.raw.recv(*args)
+
+ self._rbuf.write(buf)
+ return buf
+
+ def _flush(self, buf, prefix):
+ width = 16
+ hexwidth = width * 3 - 1
+
+ count = 0
+ buf.seek(0)
+
+ while True:
+ line = buf.read(16)
+
+ if not line:
+ if count:
+ self._out.write("\n") # separate the output block with a newline
+ return
+
+ self._out.write("%s %04X:\t" % (prefix, count))
+ self._out.write("%*s\t" % (-hexwidth, line.hex(" ")))
+ self._out.write(line.translate(self._translation_map).decode("ascii"))
+ self._out.write("\n")
+
+ count += 16
+
+ def print_debug(self, obj, *, prefix=""):
+ contents = ""
+ if obj is not None:
+ contents = str(obj)
+
+ for line in contents.splitlines():
+ self._out.write("%s%s\n" % (prefix, line))
+
+ self._out.write("\n")
+
+ def flush_debug(self, *, prefix=""):
+ self._flush(self._rbuf, prefix + "<")
+ self._rbuf = io.BytesIO()
+
+ self._flush(self._wbuf, prefix + ">")
+ self._wbuf = io.BytesIO()
+
+ def end_packet(self, pkt, *, read=False, prefix="", indent=" "):
+ """
+ Marks the end of a logical "packet" of data. A string representation of
+ pkt will be printed, and the debug buffers will be flushed with an
+ indent. All lines can be optionally prefixed.
+
+ If read is True, the packet representation is written after the debug
+ buffers; otherwise the default of False (meaning write) causes the
+ packet representation to be dumped first. This is meant to capture the
+ logical flow of layer translation.
+ """
+ write = not read
+
+ if write:
+ self.print_debug(pkt, prefix=prefix + "> ")
+
+ self.flush_debug(prefix=prefix + indent)
+
+ if read:
+ self.print_debug(pkt, prefix=prefix + "< ")
+
+
[email protected]
+def wrap(socket, *, debug_stream=None):
+ """
+ Transforms a raw socket into a connection that can be used for Construct
+ building and parsing. The return value is a context manager and can be used
+ in a with statement.
+ """
+ # It is critical that buffering be disabled here, so that we can still
+ # manipulate the raw socket without desyncing the stream.
+ with socket.makefile("rwb", buffering=0) as sfile:
+ # Expose the original socket's recv() on the SocketIO object we return.
+ def recv(self, *args):
+ return socket.recv(*args)
+
+ sfile.recv = recv.__get__(sfile)
+
+ conn = sfile
+ if debug_stream:
+ conn = _DebugStream(conn, debug_stream)
+
+ try:
+ yield conn
+ finally:
+ if debug_stream:
+ conn.flush_debug(prefix="? ")
+
+
+def _send(stream, cls, obj):
+ debugging = hasattr(stream, "flush_debug")
+ out = io.BytesIO()
+
+ # Ideally we would build directly to the passed stream, but because we need
+ # to reparse the generated output for the debugging case, build to an
+ # intermediate BytesIO and send it instead.
+ cls.build_stream(obj, out)
+ buf = out.getvalue()
+
+ stream.write(buf)
+ if debugging:
+ pkt = cls.parse(buf)
+ stream.end_packet(pkt)
+
+ stream.flush()
+
+
+def send(stream, packet_type, payload_data=None, **payloadkw):
+ """
+ Sends a packet on the given pq3 connection. type is the pq3.types member
+ that should be assigned to the packet. If payload_data is given, it will be
+ used as the packet payload; otherwise the key/value pairs in payloadkw will
+ be the payload contents.
+ """
+ data = payloadkw
+
+ if payload_data is not None:
+ if payloadkw:
+ raise ValueError(
+ "payload_data and payload keywords may not be used simultaneously"
+ )
+
+ data = payload_data
+
+ _send(stream, Pq3, dict(type=packet_type, payload=data))
+
+
+def send_startup(stream, proto=None, **kwargs):
+ """
+ Sends a startup packet on the given pq3 connection. In most cases you should
+ use the handshake functions instead, which will do this for you.
+
+ By default, a protocol version 3 packet will be sent. This can be overridden
+ with the proto parameter.
+ """
+ pkt = {}
+
+ if proto is not None:
+ pkt["proto"] = proto
+ if kwargs:
+ pkt["payload"] = kwargs
+
+ _send(stream, Startup, pkt)
+
+
+def recv1(stream, *, cls=Pq3):
+ """
+ Receives a single pq3 packet from the given stream and returns it.
+ """
+ resp = cls.parse_stream(stream)
+
+ debugging = hasattr(stream, "flush_debug")
+ if debugging:
+ stream.end_packet(resp, read=True)
+
+ return resp
+
+
+def handshake(stream, **kwargs):
+ """
+ Performs a libpq v3 startup handshake. kwargs should contain the key/value
+ parameters to send to the server in the startup packet.
+ """
+ # Send our startup parameters.
+ send_startup(stream, **kwargs)
+
+ # Receive and dump packets until the server indicates it's ready for our
+ # first query.
+ while True:
+ resp = recv1(stream)
+ if resp is None:
+ raise RuntimeError("server closed connection during handshake")
+
+ if resp.type == types.ReadyForQuery:
+ return
+ elif resp.type == types.ErrorResponse:
+ raise RuntimeError(
+ f"received error response from peer: {resp.payload.fields!r}"
+ )
+
+
+# TLS
+
+
+class _TLSStream(object):
+ """
+ A file-like object that performs TLS encryption/decryption on a wrapped
+ stream. Differs from ssl.SSLSocket in that we have full visibility and
+ control over the TLS layer.
+ """
+
+ def __init__(self, stream, context):
+ self._stream = stream
+ self._debugging = hasattr(stream, "flush_debug")
+
+ self._in = ssl.MemoryBIO()
+ self._out = ssl.MemoryBIO()
+ self._ssl = context.wrap_bio(self._in, self._out)
+
+ def handshake(self):
+ try:
+ self._pump(lambda: self._ssl.do_handshake())
+ finally:
+ self._flush_debug(prefix="? ")
+
+ def read(self, *args):
+ return self._pump(lambda: self._ssl.read(*args))
+
+ def write(self, *args):
+ return self._pump(lambda: self._ssl.write(*args))
+
+ def _decode(self, buf):
+ """
+ Attempts to decode a buffer of TLS data into a packet representation
+ that can be printed.
+
+ TODO: handle buffers (and record fragments) that don't align with packet
+ boundaries.
+ """
+ end = len(buf)
+ bio = io.BytesIO(buf)
+
+ ret = io.StringIO()
+
+ while bio.tell() < end:
+ record = tls.Plaintext.parse_stream(bio)
+
+ if ret.tell() > 0:
+ ret.write("\n")
+ ret.write("[Record] ")
+ ret.write(str(record))
+ ret.write("\n")
+
+ if record.type == tls.ContentType.handshake:
+ record_cls = tls.Handshake
+ else:
+ continue
+
+ innerlen = len(record.fragment)
+ inner = io.BytesIO(record.fragment)
+
+ while inner.tell() < innerlen:
+ msg = record_cls.parse_stream(inner)
+
+ indented = "[Message] " + str(msg)
+ indented = textwrap.indent(indented, " ")
+
+ ret.write("\n")
+ ret.write(indented)
+ ret.write("\n")
+
+ return ret.getvalue()
+
+ def flush(self):
+ if not self._out.pending:
+ self._stream.flush()
+ return
+
+ buf = self._out.read()
+ self._stream.write(buf)
+
+ if self._debugging:
+ pkt = self._decode(buf)
+ self._stream.end_packet(pkt, prefix=" ")
+
+ self._stream.flush()
+
+ def _pump(self, operation):
+ while True:
+ try:
+ return operation()
+ except (ssl.SSLWantReadError, ssl.SSLWantWriteError) as e:
+ want = e
+ self._read_write(want)
+
+ def _recv(self, maxsize):
+ buf = self._stream.recv(4096)
+ if not buf:
+ self._in.write_eof()
+ return
+
+ self._in.write(buf)
+
+ if not self._debugging:
+ return
+
+ pkt = self._decode(buf)
+ self._stream.end_packet(pkt, read=True, prefix=" ")
+
+ def _read_write(self, want):
+ # XXX This needs work. So many corner cases yet to handle. For one,
+ # doing blocking writes in flush may lead to distributed deadlock if the
+ # peer is already blocking on its writes.
+
+ if isinstance(want, ssl.SSLWantWriteError):
+ assert self._out.pending, "SSL backend wants write without data"
+
+ self.flush()
+
+ if isinstance(want, ssl.SSLWantReadError):
+ self._recv(4096)
+
+ def _flush_debug(self, prefix):
+ if not self._debugging:
+ return
+
+ self._stream.flush_debug(prefix=prefix)
+
+
[email protected]
+def tls_handshake(stream, context):
+ """
+ Performs a TLS handshake over the given stream (which must have been created
+ via a call to wrap()), and returns a new stream which transparently tunnels
+ data over the TLS connection.
+
+ If the passed stream has debugging enabled, the returned stream will also
+ have debugging, using the same output IO.
+ """
+ debugging = hasattr(stream, "flush_debug")
+
+ # Send our startup parameters.
+ send_startup(stream, proto=protocol(1234, 5679))
+
+ # Look at the SSL response.
+ resp = stream.read(1)
+ if debugging:
+ stream.flush_debug(prefix=" ")
+
+ if resp == b"N":
+ raise RuntimeError("server does not support SSLRequest")
+ if resp != b"S":
+ raise RuntimeError(f"unexpected response of type {resp!r} during TLS startup")
+
+ tls = _TLSStream(stream, context)
+ tls.handshake()
+
+ if debugging:
+ tls = _DebugStream(tls, stream._out)
+
+ try:
+ yield tls
+ # TODO: teardown/unwrap the connection?
+ finally:
+ if debugging:
+ tls.flush_debug(prefix="? ")
diff --git a/src/test/python/pytest.ini b/src/test/python/pytest.ini
new file mode 100644
index 0000000000..ab7a6e7fb9
--- /dev/null
+++ b/src/test/python/pytest.ini
@@ -0,0 +1,4 @@
+[pytest]
+
+markers =
+ slow: mark test as slow
diff --git a/src/test/python/requirements.txt b/src/test/python/requirements.txt
new file mode 100644
index 0000000000..32f105ea84
--- /dev/null
+++ b/src/test/python/requirements.txt
@@ -0,0 +1,7 @@
+black
+cryptography~=3.4.6
+construct~=2.10.61
+isort~=5.6
+psycopg2~=2.8.6
+pytest~=6.1
+pytest-asyncio~=0.14.0
diff --git a/src/test/python/server/__init__.py b/src/test/python/server/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/test/python/server/conftest.py b/src/test/python/server/conftest.py
new file mode 100644
index 0000000000..ba7342a453
--- /dev/null
+++ b/src/test/python/server/conftest.py
@@ -0,0 +1,45 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import contextlib
+import socket
+import sys
+
+import pytest
+
+import pq3
+
+
[email protected]
+def connect():
+ """
+ A factory fixture that, when called, returns a socket connected to a
+ Postgres server, wrapped in a pq3 connection. The calling test will be
+ skipped automatically if a server is not running at PGHOST:PGPORT, so it's
+ best to connect as soon as possible after the test case begins, to avoid
+ doing unnecessary work.
+ """
+ # Set up an ExitStack to handle safe cleanup of all of the moving pieces.
+ with contextlib.ExitStack() as stack:
+
+ def conn_factory():
+ addr = (pq3.pghost(), pq3.pgport())
+
+ try:
+ sock = socket.create_connection(addr, timeout=2)
+ except ConnectionError as e:
+ pytest.skip(f"unable to connect to {addr}: {e}")
+
+ # Have ExitStack close our socket.
+ stack.enter_context(sock)
+
+ # Wrap the connection in a pq3 layer and have ExitStack clean it up
+ # too.
+ wrap_ctx = pq3.wrap(sock, debug_stream=sys.stdout)
+ conn = stack.enter_context(wrap_ctx)
+
+ return conn
+
+ yield conn_factory
diff --git a/src/test/python/server/test_oauth.py b/src/test/python/server/test_oauth.py
new file mode 100644
index 0000000000..355ef8e4bd
--- /dev/null
+++ b/src/test/python/server/test_oauth.py
@@ -0,0 +1,1012 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import base64
+import contextlib
+import json
+import os
+import pathlib
+import secrets
+import shlex
+import shutil
+import socket
+import struct
+from multiprocessing import shared_memory
+
+import psycopg2
+import pytest
+from psycopg2 import sql
+
+import pq3
+
+MAX_SASL_MESSAGE_LENGTH = 65535
+
+INVALID_AUTHORIZATION_ERRCODE = b"28000"
+PROTOCOL_VIOLATION_ERRCODE = b"08P01"
+FEATURE_NOT_SUPPORTED_ERRCODE = b"0A000"
+
+SHARED_MEM_NAME = "oauth-pytest"
+MAX_TOKEN_SIZE = 4096
+MAX_UINT16 = 2 ** 16 - 1
+
+
+def skip_if_no_postgres():
+ """
+ Used by the oauth_ctx fixture to skip this test module if no Postgres server
+ is running.
+
+ This logic is nearly duplicated with the conn fixture. Ideally oauth_ctx
+ would depend on that, but a module-scope fixture can't depend on a
+ test-scope fixture, and we haven't reached the rule of three yet.
+ """
+ addr = (pq3.pghost(), pq3.pgport())
+
+ try:
+ with socket.create_connection(addr, timeout=2):
+ pass
+ except ConnectionError as e:
+ pytest.skip(f"unable to connect to {addr}: {e}")
+
+
[email protected]
+def prepend_file(path, lines):
+ """
+ A context manager that prepends a file on disk with the desired lines of
+ text. When the context manager is exited, the file will be restored to its
+ original contents.
+ """
+ # First make a backup of the original file.
+ bak = path + ".bak"
+ shutil.copy2(path, bak)
+
+ try:
+ # Write the new lines, followed by the original file content.
+ with open(path, "w") as new, open(bak, "r") as orig:
+ new.writelines(lines)
+ shutil.copyfileobj(orig, new)
+
+ # Return control to the calling code.
+ yield
+
+ finally:
+ # Put the backup back into place.
+ os.replace(bak, path)
+
+
[email protected](scope="module")
+def oauth_ctx():
+ """
+ Creates a database and user that use the oauth auth method. The context
+ object contains the dbname and user attributes as strings to be used during
+ connection, as well as the issuer and scope that have been set in the HBA
+ configuration.
+
+ This fixture assumes that the standard PG* environment variables point to a
+ server running on a local machine, and that the PGUSER has rights to create
+ databases and roles.
+ """
+ skip_if_no_postgres() # don't bother running these tests without a server
+
+ id = secrets.token_hex(4)
+
+ class Context:
+ dbname = "oauth_test_" + id
+
+ user = "oauth_user_" + id
+ map_user = "oauth_map_user_" + id
+ authz_user = "oauth_authz_user_" + id
+
+ issuer = "https://example.com/" + id
+ scope = "openid " + id
+
+ ctx = Context()
+ hba_lines = (
+ f'host {ctx.dbname} {ctx.map_user} samehost oauth issuer="{ctx.issuer}" scope="{ctx.scope}" map=oauth\n',
+ f'host {ctx.dbname} {ctx.authz_user} samehost oauth issuer="{ctx.issuer}" scope="{ctx.scope}" trust_validator_authz=1\n',
+ f'host {ctx.dbname} all samehost oauth issuer="{ctx.issuer}" scope="{ctx.scope}"\n',
+ )
+ ident_lines = (r"oauth /^(.*)@example\.com$ \1",)
+
+ conn = psycopg2.connect("")
+ conn.autocommit = True
+
+ with contextlib.closing(conn):
+ c = conn.cursor()
+
+ # Create our roles and database.
+ user = sql.Identifier(ctx.user)
+ map_user = sql.Identifier(ctx.map_user)
+ authz_user = sql.Identifier(ctx.authz_user)
+ dbname = sql.Identifier(ctx.dbname)
+
+ c.execute(sql.SQL("CREATE ROLE {} LOGIN;").format(user))
+ c.execute(sql.SQL("CREATE ROLE {} LOGIN;").format(map_user))
+ c.execute(sql.SQL("CREATE ROLE {} LOGIN;").format(authz_user))
+ c.execute(sql.SQL("CREATE DATABASE {};").format(dbname))
+
+ # Make this test script the server's oauth_validator.
+ path = pathlib.Path(__file__).parent / "validate_bearer.py"
+ path = str(path.absolute())
+
+ cmd = f"{shlex.quote(path)} {SHARED_MEM_NAME} <&%f"
+ c.execute("ALTER SYSTEM SET oauth_validator_command TO %s;", (cmd,))
+
+ # Replace pg_hba and pg_ident.
+ c.execute("SHOW hba_file;")
+ hba = c.fetchone()[0]
+
+ c.execute("SHOW ident_file;")
+ ident = c.fetchone()[0]
+
+ with prepend_file(hba, hba_lines), prepend_file(ident, ident_lines):
+ c.execute("SELECT pg_reload_conf();")
+
+ # Use the new database and user.
+ yield ctx
+
+ # Put things back the way they were.
+ c.execute("SELECT pg_reload_conf();")
+
+ c.execute("ALTER SYSTEM RESET oauth_validator_command;")
+ c.execute(sql.SQL("DROP DATABASE {};").format(dbname))
+ c.execute(sql.SQL("DROP ROLE {};").format(authz_user))
+ c.execute(sql.SQL("DROP ROLE {};").format(map_user))
+ c.execute(sql.SQL("DROP ROLE {};").format(user))
+
+
[email protected]()
+def conn(oauth_ctx, connect):
+ """
+ A convenience wrapper for connect(). The main purpose of this fixture is to
+ make sure oauth_ctx runs its setup code before the connection is made.
+ """
+ return connect()
+
+
[email protected](scope="module", autouse=True)
+def authn_id_extension(oauth_ctx):
+ """
+ Performs a `CREATE EXTENSION authn_id` in the test database. This fixture is
+ autoused, so tests don't need to rely on it.
+ """
+ conn = psycopg2.connect(database=oauth_ctx.dbname)
+ conn.autocommit = True
+
+ with contextlib.closing(conn):
+ c = conn.cursor()
+ c.execute("CREATE EXTENSION authn_id;")
+
+
[email protected](scope="session")
+def shared_mem():
+ """
+ Yields a shared memory segment that can be used for communication between
+ the bearer_token fixture and ./validate_bearer.py.
+ """
+ size = MAX_TOKEN_SIZE + 2 # two byte length prefix
+ mem = shared_memory.SharedMemory(SHARED_MEM_NAME, create=True, size=size)
+
+ try:
+ with contextlib.closing(mem):
+ yield mem
+ finally:
+ mem.unlink()
+
+
[email protected]()
+def bearer_token(shared_mem):
+ """
+ Returns a factory function that, when called, will store a Bearer token in
+ shared_mem. If token is None (the default), a new token will be generated
+ using secrets.token_urlsafe() and returned; otherwise the passed token will
+ be used as-is.
+
+ When token is None, the generated token size in bytes may be specified as an
+ argument; if unset, a small 16-byte token will be generated. The token size
+ may not exceed MAX_TOKEN_SIZE in any case.
+
+ The return value is the token, converted to a bytes object.
+
+ As a special case for testing failure modes, accept_any may be set to True.
+ This signals to the validator command that any bearer token should be
+ accepted. The returned token in this case may be used or discarded as needed
+ by the test.
+ """
+
+ def set_token(token=None, *, size=16, accept_any=False):
+ if token is not None:
+ size = len(token)
+
+ if size > MAX_TOKEN_SIZE:
+ raise ValueError(f"token size {size} exceeds maximum size {MAX_TOKEN_SIZE}")
+
+ if token is None:
+ if size % 4:
+ raise ValueError(f"requested token size {size} is not a multiple of 4")
+
+ token = secrets.token_urlsafe(size // 4 * 3)
+ assert len(token) == size
+
+ try:
+ token = token.encode("ascii")
+ except AttributeError:
+ pass # already encoded
+
+ if accept_any:
+ # Two-byte magic value.
+ shared_mem.buf[:2] = struct.pack("H", MAX_UINT16)
+ else:
+ # Two-byte length prefix, then the token data.
+ shared_mem.buf[:2] = struct.pack("H", len(token))
+ shared_mem.buf[2 : size + 2] = token
+
+ return token
+
+ return set_token
+
+
+def begin_oauth_handshake(conn, oauth_ctx, *, user=None):
+ if user is None:
+ user = oauth_ctx.authz_user
+
+ pq3.send_startup(conn, user=user, database=oauth_ctx.dbname)
+
+ resp = pq3.recv1(conn)
+ assert resp.type == pq3.types.AuthnRequest
+
+ # The server should advertise exactly one mechanism.
+ assert resp.payload.type == pq3.authn.SASL
+ assert resp.payload.body == [b"OAUTHBEARER", b""]
+
+
+def send_initial_response(conn, *, auth=None, bearer=None):
+ """
+ Sends the OAUTHBEARER initial response on the connection, using the given
+ bearer token. Alternatively to a bearer token, the initial response's auth
+ field may be explicitly specified to test corner cases.
+ """
+ if bearer is not None and auth is not None:
+ raise ValueError("exactly one of the auth and bearer kwargs must be set")
+
+ if bearer is not None:
+ auth = b"Bearer " + bearer
+
+ if auth is None:
+ raise ValueError("exactly one of the auth and bearer kwargs must be set")
+
+ initial = pq3.SASLInitialResponse.build(
+ dict(
+ name=b"OAUTHBEARER",
+ data=b"n,,\x01auth=" + auth + b"\x01\x01",
+ )
+ )
+ pq3.send(conn, pq3.types.PasswordMessage, initial)
+
+
+def expect_handshake_success(conn):
+ """
+ Validates that the server responds with an AuthnOK message, and then drains
+ the connection until a ReadyForQuery message is received.
+ """
+ resp = pq3.recv1(conn)
+
+ assert resp.type == pq3.types.AuthnRequest
+ assert resp.payload.type == pq3.authn.OK
+ assert not resp.payload.body
+
+ receive_until(conn, pq3.types.ReadyForQuery)
+
+
+def expect_handshake_failure(conn, oauth_ctx):
+ """
+ Performs the OAUTHBEARER SASL failure "handshake" and validates the server's
+ side of the conversation, including the final ErrorResponse.
+ """
+
+ # We expect a discovery "challenge" back from the server before the authn
+ # failure message.
+ resp = pq3.recv1(conn)
+ assert resp.type == pq3.types.AuthnRequest
+
+ req = resp.payload
+ assert req.type == pq3.authn.SASLContinue
+
+ body = json.loads(req.body)
+ assert body["status"] == "invalid_token"
+ assert body["scope"] == oauth_ctx.scope
+
+ expected_config = oauth_ctx.issuer + "/.well-known/openid-configuration"
+ assert body["openid-configuration"] == expected_config
+
+ # Send the dummy response to complete the failed handshake.
+ pq3.send(conn, pq3.types.PasswordMessage, b"\x01")
+ resp = pq3.recv1(conn)
+
+ err = ExpectedError(INVALID_AUTHORIZATION_ERRCODE, "bearer authentication failed")
+ err.match(resp)
+
+
+def receive_until(conn, type):
+ """
+ receive_until pulls packets off the pq3 connection until a packet with the
+ desired type is found, or an error response is received.
+ """
+ while True:
+ pkt = pq3.recv1(conn)
+
+ if pkt.type == type:
+ return pkt
+ elif pkt.type == pq3.types.ErrorResponse:
+ raise RuntimeError(
+ f"received error response from peer: {pkt.payload.fields!r}"
+ )
+
+
[email protected]("token_len", [16, 1024, 4096])
[email protected](
+ "auth_prefix",
+ [
+ b"Bearer ",
+ b"bearer ",
+ b"Bearer ",
+ ],
+)
+def test_oauth(conn, oauth_ctx, bearer_token, auth_prefix, token_len):
+ begin_oauth_handshake(conn, oauth_ctx)
+
+ # Generate our bearer token with the desired length.
+ token = bearer_token(size=token_len)
+ auth = auth_prefix + token
+
+ send_initial_response(conn, auth=auth)
+ expect_handshake_success(conn)
+
+ # Make sure that the server has not set an authenticated ID.
+ pq3.send(conn, pq3.types.Query, query=b"SELECT authn_id();")
+ resp = receive_until(conn, pq3.types.DataRow)
+
+ row = resp.payload
+ assert row.columns == [None]
+
+
[email protected](
+ "token_value",
+ [
+ "abcdzA==",
+ "123456M=",
+ "x-._~+/x",
+ ],
+)
+def test_oauth_bearer_corner_cases(conn, oauth_ctx, bearer_token, token_value):
+ begin_oauth_handshake(conn, oauth_ctx)
+
+ send_initial_response(conn, bearer=bearer_token(token_value))
+
+ expect_handshake_success(conn)
+
+
[email protected](
+ "user,authn_id,should_succeed",
+ [
+ pytest.param(
+ lambda ctx: ctx.user,
+ lambda ctx: ctx.user,
+ True,
+ id="validator authn: succeeds when authn_id == username",
+ ),
+ pytest.param(
+ lambda ctx: ctx.user,
+ lambda ctx: None,
+ False,
+ id="validator authn: fails when authn_id is not set",
+ ),
+ pytest.param(
+ lambda ctx: ctx.user,
+ lambda ctx: ctx.authz_user,
+ False,
+ id="validator authn: fails when authn_id != username",
+ ),
+ pytest.param(
+ lambda ctx: ctx.map_user,
+ lambda ctx: ctx.map_user + "@example.com",
+ True,
+ id="validator with map: succeeds when authn_id matches map",
+ ),
+ pytest.param(
+ lambda ctx: ctx.map_user,
+ lambda ctx: None,
+ False,
+ id="validator with map: fails when authn_id is not set",
+ ),
+ pytest.param(
+ lambda ctx: ctx.map_user,
+ lambda ctx: ctx.map_user + "@example.net",
+ False,
+ id="validator with map: fails when authn_id doesn't match map",
+ ),
+ pytest.param(
+ lambda ctx: ctx.authz_user,
+ lambda ctx: None,
+ True,
+ id="validator authz: succeeds with no authn_id",
+ ),
+ pytest.param(
+ lambda ctx: ctx.authz_user,
+ lambda ctx: "",
+ True,
+ id="validator authz: succeeds with empty authn_id",
+ ),
+ pytest.param(
+ lambda ctx: ctx.authz_user,
+ lambda ctx: "postgres",
+ True,
+ id="validator authz: succeeds with basic username",
+ ),
+ pytest.param(
+ lambda ctx: ctx.authz_user,
+ lambda ctx: "[email protected]",
+ True,
+ id="validator authz: succeeds with email address",
+ ),
+ ],
+)
+def test_oauth_authn_id(conn, oauth_ctx, bearer_token, user, authn_id, should_succeed):
+ token = None
+
+ authn_id = authn_id(oauth_ctx)
+ if authn_id is not None:
+ authn_id = authn_id.encode("ascii")
+
+ # As a hack to get the validator to reflect arbitrary output from this
+ # test, encode the desired output as a base64 token. The validator will
+ # key on the leading "output=" to differentiate this from the random
+ # tokens generated by secrets.token_urlsafe().
+ output = b"output=" + authn_id + b"\n"
+ token = base64.urlsafe_b64encode(output)
+
+ token = bearer_token(token)
+ username = user(oauth_ctx)
+
+ begin_oauth_handshake(conn, oauth_ctx, user=username)
+ send_initial_response(conn, bearer=token)
+
+ if not should_succeed:
+ expect_handshake_failure(conn, oauth_ctx)
+ return
+
+ expect_handshake_success(conn)
+
+ # Check the reported authn_id.
+ pq3.send(conn, pq3.types.Query, query=b"SELECT authn_id();")
+ resp = receive_until(conn, pq3.types.DataRow)
+
+ row = resp.payload
+ assert row.columns == [authn_id]
+
+
+class ExpectedError(object):
+ def __init__(self, code, msg=None, detail=None):
+ self.code = code
+ self.msg = msg
+ self.detail = detail
+
+ # Protect against the footgun of an accidental empty string, which will
+ # "match" anything. If you don't want to match message or detail, just
+ # don't pass them.
+ if self.msg == "":
+ raise ValueError("msg must be non-empty or None")
+ if self.detail == "":
+ raise ValueError("detail must be non-empty or None")
+
+ def _getfield(self, resp, type):
+ """
+ Searches an ErrorResponse for a single field of the given type (e.g.
+ "M", "C", "D") and returns its value. Asserts if it doesn't find exactly
+ one field.
+ """
+ prefix = type.encode("ascii")
+ fields = [f for f in resp.payload.fields if f.startswith(prefix)]
+
+ assert len(fields) == 1
+ return fields[0][1:] # strip off the type byte
+
+ def match(self, resp):
+ """
+ Checks that the given response matches the expected code, message, and
+ detail (if given). The error code must match exactly. The expected
+ message and detail must be contained within the actual strings.
+ """
+ assert resp.type == pq3.types.ErrorResponse
+
+ code = self._getfield(resp, "C")
+ assert code == self.code
+
+ if self.msg:
+ msg = self._getfield(resp, "M")
+ expected = self.msg.encode("utf-8")
+ assert expected in msg
+
+ if self.detail:
+ detail = self._getfield(resp, "D")
+ expected = self.detail.encode("utf-8")
+ assert expected in detail
+
+
+def test_oauth_rejected_bearer(conn, oauth_ctx, bearer_token):
+ # Generate a new bearer token, which we will proceed not to use.
+ _ = bearer_token()
+
+ begin_oauth_handshake(conn, oauth_ctx)
+
+ # Send a bearer token that doesn't match what the validator expects. It
+ # should fail the connection.
+ send_initial_response(conn, bearer=b"xxxxxx")
+
+ expect_handshake_failure(conn, oauth_ctx)
+
+
[email protected](
+ "bad_bearer",
+ [
+ b"Bearer ",
+ b"Bearer a===b",
+ b"Bearer hello!",
+ b"Bearer [email protected]",
+ b'OAuth realm="Example"',
+ b"",
+ ],
+)
+def test_oauth_invalid_bearer(conn, oauth_ctx, bearer_token, bad_bearer):
+ # Tell the validator to accept any token. This ensures that the invalid
+ # bearer tokens are rejected before the validation step.
+ _ = bearer_token(accept_any=True)
+
+ begin_oauth_handshake(conn, oauth_ctx)
+ send_initial_response(conn, auth=bad_bearer)
+
+ expect_handshake_failure(conn, oauth_ctx)
+
+
[email protected]
[email protected](
+ "resp_type,resp,err",
+ [
+ pytest.param(
+ None,
+ None,
+ None,
+ marks=pytest.mark.slow,
+ id="no response (expect timeout)",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ b"hello",
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "did not send a kvsep response",
+ ),
+ id="bad dummy response",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ b"\x01\x01",
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "did not send a kvsep response",
+ ),
+ id="multiple kvseps",
+ ),
+ pytest.param(
+ pq3.types.Query,
+ dict(query=b""),
+ ExpectedError(PROTOCOL_VIOLATION_ERRCODE, "expected SASL response"),
+ id="bad response message type",
+ ),
+ ],
+)
+def test_oauth_bad_response_to_error_challenge(conn, oauth_ctx, resp_type, resp, err):
+ begin_oauth_handshake(conn, oauth_ctx)
+
+ # Send an empty auth initial response, which will force an authn failure.
+ send_initial_response(conn, auth=b"")
+
+ # We expect a discovery "challenge" back from the server before the authn
+ # failure message.
+ pkt = pq3.recv1(conn)
+ assert pkt.type == pq3.types.AuthnRequest
+
+ req = pkt.payload
+ assert req.type == pq3.authn.SASLContinue
+
+ body = json.loads(req.body)
+ assert body["status"] == "invalid_token"
+
+ if resp_type is None:
+ # Do not send the dummy response. We should time out and not get a
+ # response from the server.
+ with pytest.raises(socket.timeout):
+ conn.read(1)
+
+ # Done with the test.
+ return
+
+ # Send the bad response.
+ pq3.send(conn, resp_type, resp)
+
+ # Make sure the server fails the connection correctly.
+ pkt = pq3.recv1(conn)
+ err.match(pkt)
+
+
[email protected](
+ "type,payload,err",
+ [
+ pytest.param(
+ pq3.types.ErrorResponse,
+ dict(fields=[b""]),
+ ExpectedError(PROTOCOL_VIOLATION_ERRCODE, "expected SASL response"),
+ id="error response in initial message",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ b"x" * (MAX_SASL_MESSAGE_LENGTH + 1),
+ ExpectedError(
+ INVALID_AUTHORIZATION_ERRCODE, "bearer authentication failed"
+ ),
+ id="overlong initial response data",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"SCRAM-SHA-256")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE, "invalid SASL authentication mechanism"
+ ),
+ id="bad SASL mechanism selection",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", len=2, data=b"x")),
+ ExpectedError(PROTOCOL_VIOLATION_ERRCODE, "insufficient data"),
+ id="SASL data underflow",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", len=0, data=b"x")),
+ ExpectedError(PROTOCOL_VIOLATION_ERRCODE, "invalid message format"),
+ id="SASL data overflow",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "message is empty",
+ ),
+ id="empty",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"n,,\x01auth=\x01\x01\0")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "length does not match input length",
+ ),
+ id="contains null byte",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"\x01")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Unexpected channel-binding flag", # XXX this is a bit strange
+ ),
+ id="initial error response",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"p=tls-server-end-point,,\x01")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "server does not support channel binding",
+ ),
+ id="uses channel binding",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"x,,\x01")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Unexpected channel-binding flag",
+ ),
+ id="invalid channel binding specifier",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Comma expected",
+ ),
+ id="bad GS2 header: missing channel binding terminator",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y,a")),
+ ExpectedError(
+ FEATURE_NOT_SUPPORTED_ERRCODE,
+ "client uses authorization identity",
+ ),
+ id="bad GS2 header: authzid in use",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y,b,")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Unexpected attribute",
+ ),
+ id="bad GS2 header: extra attribute",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y,")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ 'Unexpected attribute "0x00"', # XXX this is a bit strange
+ ),
+ id="bad GS2 header: missing authzid terminator",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y,,")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Key-value separator expected",
+ ),
+ id="missing initial kvsep",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y,,")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Key-value separator expected",
+ ),
+ id="missing initial kvsep",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"y,,\x01\x01")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "does not contain an auth value",
+ ),
+ id="missing auth value: empty key-value list",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"y,,\x01host=example.com\x01\x01")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "does not contain an auth value",
+ ),
+ id="missing auth value: other keys present",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"y,,\x01host=example.com")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "unterminated key/value pair",
+ ),
+ id="missing value terminator",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y,,\x01")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "did not contain a final terminator",
+ ),
+ id="missing list terminator: empty list",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"y,,\x01auth=Bearer 0\x01")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "did not contain a final terminator",
+ ),
+ id="missing list terminator: with auth value",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"y,,\x01auth=Bearer 0\x01\x01blah")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "additional data after the final terminator",
+ ),
+ id="additional key after terminator",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"y,,\x01key\x01\x01")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "key without a value",
+ ),
+ id="key without value",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(
+ name=b"OAUTHBEARER",
+ data=b"y,,\x01auth=Bearer 0\x01auth=Bearer 1\x01\x01",
+ )
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "contains multiple auth values",
+ ),
+ id="multiple auth values",
+ ),
+ ],
+)
+def test_oauth_bad_initial_response(conn, oauth_ctx, type, payload, err):
+ begin_oauth_handshake(conn, oauth_ctx)
+
+ # The server expects a SASL response; give it something else instead.
+ if not isinstance(payload, dict):
+ payload = dict(payload_data=payload)
+ pq3.send(conn, type, **payload)
+
+ resp = pq3.recv1(conn)
+ err.match(resp)
+
+
+def test_oauth_empty_initial_response(conn, oauth_ctx, bearer_token):
+ begin_oauth_handshake(conn, oauth_ctx)
+
+ # Send an initial response without data.
+ initial = pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER"))
+ pq3.send(conn, pq3.types.PasswordMessage, initial)
+
+ # The server should respond with an empty challenge so we can send the data
+ # it wants.
+ pkt = pq3.recv1(conn)
+
+ assert pkt.type == pq3.types.AuthnRequest
+ assert pkt.payload.type == pq3.authn.SASLContinue
+ assert not pkt.payload.body
+
+ # Now send the initial data.
+ data = b"n,,\x01auth=Bearer " + bearer_token() + b"\x01\x01"
+ pq3.send(conn, pq3.types.PasswordMessage, data)
+
+ # Server should now complete the handshake.
+ expect_handshake_success(conn)
+
+
[email protected]()
+def set_validator():
+ """
+ A per-test fixture that allows a test to override the setting of
+ oauth_validator_command for the cluster. The setting will be reverted during
+ teardown.
+
+ Passing None will perform an ALTER SYSTEM RESET.
+ """
+ conn = psycopg2.connect("")
+ conn.autocommit = True
+
+ with contextlib.closing(conn):
+ c = conn.cursor()
+
+ # Save the previous value.
+ c.execute("SHOW oauth_validator_command;")
+ prev_cmd = c.fetchone()[0]
+
+ def setter(cmd):
+ c.execute("ALTER SYSTEM SET oauth_validator_command TO %s;", (cmd,))
+ c.execute("SELECT pg_reload_conf();")
+
+ yield setter
+
+ # Restore the previous value.
+ c.execute("ALTER SYSTEM SET oauth_validator_command TO %s;", (prev_cmd,))
+ c.execute("SELECT pg_reload_conf();")
+
+
+def test_oauth_no_validator(oauth_ctx, set_validator, connect, bearer_token):
+ # Clear out our validator command, then establish a new connection.
+ set_validator("")
+ conn = connect()
+
+ begin_oauth_handshake(conn, oauth_ctx)
+ send_initial_response(conn, bearer=bearer_token())
+
+ # The server should fail the connection.
+ expect_handshake_failure(conn, oauth_ctx)
+
+
+def test_oauth_validator_role(oauth_ctx, set_validator, connect):
+ # Switch the validator implementation. This validator will reflect the
+ # PGUSER as the authenticated identity.
+ path = pathlib.Path(__file__).parent / "validate_reflect.py"
+ path = str(path.absolute())
+
+ set_validator(f"{shlex.quote(path)} '%r' <&%f")
+ conn = connect()
+
+ # Log in. Note that the reflection validator ignores the bearer token.
+ begin_oauth_handshake(conn, oauth_ctx, user=oauth_ctx.user)
+ send_initial_response(conn, bearer=b"dontcare")
+ expect_handshake_success(conn)
+
+ # Check the user identity.
+ pq3.send(conn, pq3.types.Query, query=b"SELECT authn_id();")
+ resp = receive_until(conn, pq3.types.DataRow)
+
+ row = resp.payload
+ expected = oauth_ctx.user.encode("utf-8")
+ assert row.columns == [expected]
+
+
+def test_oauth_role_with_shell_unsafe_characters(oauth_ctx, set_validator, connect):
+ """
+ XXX This test pins undesirable behavior. We should be able to handle any
+ valid Postgres role name.
+ """
+ # Switch the validator implementation. This validator will reflect the
+ # PGUSER as the authenticated identity.
+ path = pathlib.Path(__file__).parent / "validate_reflect.py"
+ path = str(path.absolute())
+
+ set_validator(f"{shlex.quote(path)} '%r' <&%f")
+ conn = connect()
+
+ unsafe_username = "hello'there"
+ begin_oauth_handshake(conn, oauth_ctx, user=unsafe_username)
+
+ # The server should reject the handshake.
+ send_initial_response(conn, bearer=b"dontcare")
+ expect_handshake_failure(conn, oauth_ctx)
diff --git a/src/test/python/server/test_server.py b/src/test/python/server/test_server.py
new file mode 100644
index 0000000000..02126dba79
--- /dev/null
+++ b/src/test/python/server/test_server.py
@@ -0,0 +1,21 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import pq3
+
+
+def test_handshake(connect):
+ """Basic sanity check."""
+ conn = connect()
+
+ pq3.handshake(conn, user=pq3.pguser(), database=pq3.pgdatabase())
+
+ pq3.send(conn, pq3.types.Query, query=b"")
+
+ resp = pq3.recv1(conn)
+ assert resp.type == pq3.types.EmptyQueryResponse
+
+ resp = pq3.recv1(conn)
+ assert resp.type == pq3.types.ReadyForQuery
diff --git a/src/test/python/server/validate_bearer.py b/src/test/python/server/validate_bearer.py
new file mode 100755
index 0000000000..2cc73ff154
--- /dev/null
+++ b/src/test/python/server/validate_bearer.py
@@ -0,0 +1,101 @@
+#! /usr/bin/env python3
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+# DO NOT USE THIS OAUTH VALIDATOR IN PRODUCTION. It doesn't actually validate
+# anything, and it logs the bearer token data, which is sensitive.
+#
+# This executable is used as an oauth_validator_command in concert with
+# test_oauth.py. Memory is shared and communicated from that test module's
+# bearer_token() fixture.
+#
+# This script must run under the Postgres server environment; keep the
+# dependency list fairly standard.
+
+import base64
+import binascii
+import contextlib
+import struct
+import sys
+from multiprocessing import shared_memory
+
+MAX_UINT16 = 2 ** 16 - 1
+
+
+def remove_shm_from_resource_tracker():
+ """
+ Monkey-patch multiprocessing.resource_tracker so SharedMemory won't be
+ tracked. Pulled from this thread, where there are more details:
+
+ https://bugs.python.org/issue38119
+
+ TL;DR: all clients of shared memory segments automatically destroy them on
+ process exit, which makes shared memory segments much less useful. This
+ monkeypatch removes that behavior so that we can defer to the test to manage
+ the segment lifetime.
+
+ Ideally a future Python patch will pull in this fix and then the entire
+ function can go away.
+ """
+ from multiprocessing import resource_tracker
+
+ def fix_register(name, rtype):
+ if rtype == "shared_memory":
+ return
+ return resource_tracker._resource_tracker.register(self, name, rtype)
+
+ resource_tracker.register = fix_register
+
+ def fix_unregister(name, rtype):
+ if rtype == "shared_memory":
+ return
+ return resource_tracker._resource_tracker.unregister(self, name, rtype)
+
+ resource_tracker.unregister = fix_unregister
+
+ if "shared_memory" in resource_tracker._CLEANUP_FUNCS:
+ del resource_tracker._CLEANUP_FUNCS["shared_memory"]
+
+
+def main(args):
+ remove_shm_from_resource_tracker() # XXX remove some day
+
+ # Get the expected token from the currently running test.
+ shared_mem_name = args[0]
+
+ mem = shared_memory.SharedMemory(shared_mem_name)
+ with contextlib.closing(mem):
+ # First two bytes are the token length.
+ size = struct.unpack("H", mem.buf[:2])[0]
+
+ if size == MAX_UINT16:
+ # Special case: the test wants us to accept any token.
+ sys.stderr.write("accepting token without validation\n")
+ return
+
+ # The remainder of the buffer contains the expected token.
+ assert size <= (mem.size - 2)
+ expected_token = mem.buf[2 : size + 2].tobytes()
+
+ mem.buf[:] = b"\0" * mem.size # scribble over the token
+
+ token = sys.stdin.buffer.read()
+ if token != expected_token:
+ sys.exit(f"failed to match Bearer token ({token!r} != {expected_token!r})")
+
+ # See if the test wants us to print anything. If so, it will have encoded
+ # the desired output in the token with an "output=" prefix.
+ try:
+ # altchars="-_" corresponds to the urlsafe alphabet.
+ data = base64.b64decode(token, altchars="-_", validate=True)
+
+ if data.startswith(b"output="):
+ sys.stdout.buffer.write(data[7:])
+
+ except binascii.Error:
+ pass
+
+
+if __name__ == "__main__":
+ main(sys.argv[1:])
diff --git a/src/test/python/server/validate_reflect.py b/src/test/python/server/validate_reflect.py
new file mode 100755
index 0000000000..24c3a7e715
--- /dev/null
+++ b/src/test/python/server/validate_reflect.py
@@ -0,0 +1,34 @@
+#! /usr/bin/env python3
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+# DO NOT USE THIS OAUTH VALIDATOR IN PRODUCTION. It ignores the bearer token
+# entirely and automatically logs the user in.
+#
+# This executable is used as an oauth_validator_command in concert with
+# test_oauth.py. It expects the user's desired role name as an argument; the
+# actual token will be discarded and the user will be logged in with the role
+# name as the authenticated identity.
+#
+# This script must run under the Postgres server environment; keep the
+# dependency list fairly standard.
+
+import sys
+
+
+def main(args):
+ # We have to read the entire token as our first action to unblock the
+ # server, but we won't actually use it.
+ _ = sys.stdin.buffer.read()
+
+ if len(args) != 1:
+ sys.exit("usage: ./validate_reflect.py ROLE")
+
+ # Log the user in as the provided role.
+ role = args[0]
+ print(role)
+
+
+if __name__ == "__main__":
+ main(sys.argv[1:])
diff --git a/src/test/python/test_internals.py b/src/test/python/test_internals.py
new file mode 100644
index 0000000000..dee4855fc0
--- /dev/null
+++ b/src/test/python/test_internals.py
@@ -0,0 +1,138 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import io
+
+from pq3 import _DebugStream
+
+
+def test_DebugStream_read():
+ under = io.BytesIO(b"abcdefghijklmnopqrstuvwxyz")
+ out = io.StringIO()
+
+ stream = _DebugStream(under, out)
+
+ res = stream.read(5)
+ assert res == b"abcde"
+
+ res = stream.read(16)
+ assert res == b"fghijklmnopqrstu"
+
+ stream.flush_debug()
+
+ res = stream.read()
+ assert res == b"vwxyz"
+
+ stream.flush_debug()
+
+ expected = (
+ "< 0000:\t61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70\tabcdefghijklmnop\n"
+ "< 0010:\t71 72 73 74 75 \tqrstu\n"
+ "\n"
+ "< 0000:\t76 77 78 79 7a \tvwxyz\n"
+ "\n"
+ )
+ assert out.getvalue() == expected
+
+
+def test_DebugStream_write():
+ under = io.BytesIO()
+ out = io.StringIO()
+
+ stream = _DebugStream(under, out)
+
+ stream.write(b"\x00\x01\x02")
+ stream.flush()
+
+ assert under.getvalue() == b"\x00\x01\x02"
+
+ stream.write(b"\xc0\xc1\xc2")
+ stream.flush()
+
+ assert under.getvalue() == b"\x00\x01\x02\xc0\xc1\xc2"
+
+ stream.flush_debug()
+
+ expected = "> 0000:\t00 01 02 c0 c1 c2 \t......\n\n"
+ assert out.getvalue() == expected
+
+
+def test_DebugStream_read_write():
+ under = io.BytesIO(b"abcdefghijklmnopqrstuvwxyz")
+ out = io.StringIO()
+ stream = _DebugStream(under, out)
+
+ res = stream.read(5)
+ assert res == b"abcde"
+
+ stream.write(b"xxxxx")
+ stream.flush()
+
+ assert under.getvalue() == b"abcdexxxxxklmnopqrstuvwxyz"
+
+ res = stream.read(5)
+ assert res == b"klmno"
+
+ stream.write(b"xxxxx")
+ stream.flush()
+
+ assert under.getvalue() == b"abcdexxxxxklmnoxxxxxuvwxyz"
+
+ stream.flush_debug()
+
+ expected = (
+ "< 0000:\t61 62 63 64 65 6b 6c 6d 6e 6f \tabcdeklmno\n"
+ "\n"
+ "> 0000:\t78 78 78 78 78 78 78 78 78 78 \txxxxxxxxxx\n"
+ "\n"
+ )
+ assert out.getvalue() == expected
+
+
+def test_DebugStream_end_packet():
+ under = io.BytesIO(b"abcdefghijklmnopqrstuvwxyz")
+ out = io.StringIO()
+ stream = _DebugStream(under, out)
+
+ stream.read(5)
+ stream.end_packet("read description", read=True, indent=" ")
+
+ stream.write(b"xxxxx")
+ stream.flush()
+ stream.end_packet("write description", indent=" ")
+
+ stream.read(5)
+ stream.write(b"xxxxx")
+ stream.flush()
+ stream.end_packet("read/write combo for read", read=True, indent=" ")
+
+ stream.read(5)
+ stream.write(b"xxxxx")
+ stream.flush()
+ stream.end_packet("read/write combo for write", indent=" ")
+
+ expected = (
+ " < 0000:\t61 62 63 64 65 \tabcde\n"
+ "\n"
+ "< read description\n"
+ "\n"
+ "> write description\n"
+ "\n"
+ " > 0000:\t78 78 78 78 78 \txxxxx\n"
+ "\n"
+ " < 0000:\t6b 6c 6d 6e 6f \tklmno\n"
+ "\n"
+ " > 0000:\t78 78 78 78 78 \txxxxx\n"
+ "\n"
+ "< read/write combo for read\n"
+ "\n"
+ "> read/write combo for write\n"
+ "\n"
+ " < 0000:\t75 76 77 78 79 \tuvwxy\n"
+ "\n"
+ " > 0000:\t78 78 78 78 78 \txxxxx\n"
+ "\n"
+ )
+ assert out.getvalue() == expected
diff --git a/src/test/python/test_pq3.py b/src/test/python/test_pq3.py
new file mode 100644
index 0000000000..e0c0e0568d
--- /dev/null
+++ b/src/test/python/test_pq3.py
@@ -0,0 +1,558 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import contextlib
+import getpass
+import io
+import struct
+import sys
+
+import pytest
+from construct import Container, PaddingError, StreamError, TerminatedError
+
+import pq3
+
+
[email protected](
+ "raw,expected,extra",
+ [
+ pytest.param(
+ b"\x00\x00\x00\x10\x00\x04\x00\x00abcdefgh",
+ Container(len=16, proto=0x40000, payload=b"abcdefgh"),
+ b"",
+ id="8-byte payload",
+ ),
+ pytest.param(
+ b"\x00\x00\x00\x08\x00\x04\x00\x00",
+ Container(len=8, proto=0x40000, payload=b""),
+ b"",
+ id="no payload",
+ ),
+ pytest.param(
+ b"\x00\x00\x00\x09\x00\x04\x00\x00abcde",
+ Container(len=9, proto=0x40000, payload=b"a"),
+ b"bcde",
+ id="1-byte payload and extra padding",
+ ),
+ pytest.param(
+ b"\x00\x00\x00\x0B\x00\x03\x00\x00hi\x00",
+ Container(len=11, proto=pq3.protocol(3, 0), payload=[b"hi"]),
+ b"",
+ id="implied parameter list when using proto version 3.0",
+ ),
+ ],
+)
+def test_Startup_parse(raw, expected, extra):
+ with io.BytesIO(raw) as stream:
+ actual = pq3.Startup.parse_stream(stream)
+
+ assert actual == expected
+ assert stream.read() == extra
+
+
[email protected](
+ "packet,expected_bytes",
+ [
+ pytest.param(
+ dict(),
+ b"\x00\x00\x00\x08\x00\x00\x00\x00",
+ id="nothing set",
+ ),
+ pytest.param(
+ dict(len=10, proto=0x12345678),
+ b"\x00\x00\x00\x0A\x12\x34\x56\x78\x00\x00",
+ id="len and proto set explicitly",
+ ),
+ pytest.param(
+ dict(proto=0x12345678),
+ b"\x00\x00\x00\x08\x12\x34\x56\x78",
+ id="implied len with no payload",
+ ),
+ pytest.param(
+ dict(proto=0x12345678, payload=b"abcd"),
+ b"\x00\x00\x00\x0C\x12\x34\x56\x78abcd",
+ id="implied len with payload",
+ ),
+ pytest.param(
+ dict(payload=[b""]),
+ b"\x00\x00\x00\x09\x00\x03\x00\x00\x00",
+ id="implied proto version 3 when sending parameters",
+ ),
+ pytest.param(
+ dict(payload=[b"hi", b""]),
+ b"\x00\x00\x00\x0C\x00\x03\x00\x00hi\x00\x00",
+ id="implied proto version 3 and len when sending more than one parameter",
+ ),
+ pytest.param(
+ dict(payload=dict(user="jsmith", database="postgres")),
+ b"\x00\x00\x00\x27\x00\x03\x00\x00user\x00jsmith\x00database\x00postgres\x00\x00",
+ id="auto-serialization of dict parameters",
+ ),
+ ],
+)
+def test_Startup_build(packet, expected_bytes):
+ actual = pq3.Startup.build(packet)
+ assert actual == expected_bytes
+
+
[email protected](
+ "raw,expected,extra",
+ [
+ pytest.param(
+ b"*\x00\x00\x00\x08abcd",
+ dict(type=b"*", len=8, payload=b"abcd"),
+ b"",
+ id="4-byte payload",
+ ),
+ pytest.param(
+ b"*\x00\x00\x00\x04",
+ dict(type=b"*", len=4, payload=b""),
+ b"",
+ id="no payload",
+ ),
+ pytest.param(
+ b"*\x00\x00\x00\x05xabcd",
+ dict(type=b"*", len=5, payload=b"x"),
+ b"abcd",
+ id="1-byte payload with extra padding",
+ ),
+ pytest.param(
+ b"R\x00\x00\x00\x08\x00\x00\x00\x00",
+ dict(
+ type=pq3.types.AuthnRequest,
+ len=8,
+ payload=dict(type=pq3.authn.OK, body=None),
+ ),
+ b"",
+ id="AuthenticationOk",
+ ),
+ pytest.param(
+ b"R\x00\x00\x00\x12\x00\x00\x00\x0AEXTERNAL\x00\x00",
+ dict(
+ type=pq3.types.AuthnRequest,
+ len=18,
+ payload=dict(type=pq3.authn.SASL, body=[b"EXTERNAL", b""]),
+ ),
+ b"",
+ id="AuthenticationSASL",
+ ),
+ pytest.param(
+ b"R\x00\x00\x00\x0D\x00\x00\x00\x0B12345",
+ dict(
+ type=pq3.types.AuthnRequest,
+ len=13,
+ payload=dict(type=pq3.authn.SASLContinue, body=b"12345"),
+ ),
+ b"",
+ id="AuthenticationSASLContinue",
+ ),
+ pytest.param(
+ b"R\x00\x00\x00\x0D\x00\x00\x00\x0C12345",
+ dict(
+ type=pq3.types.AuthnRequest,
+ len=13,
+ payload=dict(type=pq3.authn.SASLFinal, body=b"12345"),
+ ),
+ b"",
+ id="AuthenticationSASLFinal",
+ ),
+ pytest.param(
+ b"p\x00\x00\x00\x0Bhunter2",
+ dict(
+ type=pq3.types.PasswordMessage,
+ len=11,
+ payload=b"hunter2",
+ ),
+ b"",
+ id="PasswordMessage",
+ ),
+ pytest.param(
+ b"K\x00\x00\x00\x0C\x00\x00\x00\x00\x12\x34\x56\x78",
+ dict(
+ type=pq3.types.BackendKeyData,
+ len=12,
+ payload=dict(pid=0, key=0x12345678),
+ ),
+ b"",
+ id="BackendKeyData",
+ ),
+ pytest.param(
+ b"C\x00\x00\x00\x08SET\x00",
+ dict(
+ type=pq3.types.CommandComplete,
+ len=8,
+ payload=dict(tag=b"SET"),
+ ),
+ b"",
+ id="CommandComplete",
+ ),
+ pytest.param(
+ b"E\x00\x00\x00\x11Mbad!\x00Mdog!\x00\x00",
+ dict(type=b"E", len=17, payload=dict(fields=[b"Mbad!", b"Mdog!", b""])),
+ b"",
+ id="ErrorResponse",
+ ),
+ pytest.param(
+ b"S\x00\x00\x00\x08a\x00b\x00",
+ dict(
+ type=pq3.types.ParameterStatus,
+ len=8,
+ payload=dict(name=b"a", value=b"b"),
+ ),
+ b"",
+ id="ParameterStatus",
+ ),
+ pytest.param(
+ b"Z\x00\x00\x00\x05x",
+ dict(type=b"Z", len=5, payload=dict(status=b"x")),
+ b"",
+ id="ReadyForQuery",
+ ),
+ pytest.param(
+ b"Q\x00\x00\x00\x06!\x00",
+ dict(type=pq3.types.Query, len=6, payload=dict(query=b"!")),
+ b"",
+ id="Query",
+ ),
+ pytest.param(
+ b"D\x00\x00\x00\x0B\x00\x01\x00\x00\x00\x01!",
+ dict(type=pq3.types.DataRow, len=11, payload=dict(columns=[b"!"])),
+ b"",
+ id="DataRow",
+ ),
+ pytest.param(
+ b"D\x00\x00\x00\x06\x00\x00extra",
+ dict(type=pq3.types.DataRow, len=6, payload=dict(columns=[])),
+ b"extra",
+ id="DataRow with extra data",
+ ),
+ pytest.param(
+ b"I\x00\x00\x00\x04",
+ dict(type=pq3.types.EmptyQueryResponse, len=4, payload=None),
+ b"",
+ id="EmptyQueryResponse",
+ ),
+ pytest.param(
+ b"I\x00\x00\x00\x04\xFF",
+ dict(type=b"I", len=4, payload=None),
+ b"\xFF",
+ id="EmptyQueryResponse with extra bytes",
+ ),
+ pytest.param(
+ b"X\x00\x00\x00\x04",
+ dict(type=pq3.types.Terminate, len=4, payload=None),
+ b"",
+ id="Terminate",
+ ),
+ ],
+)
+def test_Pq3_parse(raw, expected, extra):
+ with io.BytesIO(raw) as stream:
+ actual = pq3.Pq3.parse_stream(stream)
+
+ assert actual == expected
+ assert stream.read() == extra
+
+
[email protected](
+ "fields,expected",
+ [
+ pytest.param(
+ dict(type=b"*", len=5),
+ b"*\x00\x00\x00\x05\x00",
+ id="type and len set explicitly",
+ ),
+ pytest.param(
+ dict(type=b"*"),
+ b"*\x00\x00\x00\x04",
+ id="implied len with no payload",
+ ),
+ pytest.param(
+ dict(type=b"*", payload=b"1234"),
+ b"*\x00\x00\x00\x081234",
+ id="implied len with payload",
+ ),
+ pytest.param(
+ dict(type=pq3.types.AuthnRequest, payload=dict(type=pq3.authn.OK)),
+ b"R\x00\x00\x00\x08\x00\x00\x00\x00",
+ id="implied len/type for AuthenticationOK",
+ ),
+ pytest.param(
+ dict(
+ type=pq3.types.AuthnRequest,
+ payload=dict(
+ type=pq3.authn.SASL,
+ body=[b"SCRAM-SHA-256-PLUS", b"SCRAM-SHA-256", b""],
+ ),
+ ),
+ b"R\x00\x00\x00\x2A\x00\x00\x00\x0ASCRAM-SHA-256-PLUS\x00SCRAM-SHA-256\x00\x00",
+ id="implied len/type for AuthenticationSASL",
+ ),
+ pytest.param(
+ dict(
+ type=pq3.types.AuthnRequest,
+ payload=dict(type=pq3.authn.SASLContinue, body=b"12345"),
+ ),
+ b"R\x00\x00\x00\x0D\x00\x00\x00\x0B12345",
+ id="implied len/type for AuthenticationSASLContinue",
+ ),
+ pytest.param(
+ dict(
+ type=pq3.types.AuthnRequest,
+ payload=dict(type=pq3.authn.SASLFinal, body=b"12345"),
+ ),
+ b"R\x00\x00\x00\x0D\x00\x00\x00\x0C12345",
+ id="implied len/type for AuthenticationSASLFinal",
+ ),
+ pytest.param(
+ dict(
+ type=pq3.types.PasswordMessage,
+ payload=b"hunter2",
+ ),
+ b"p\x00\x00\x00\x0Bhunter2",
+ id="implied len/type for PasswordMessage",
+ ),
+ pytest.param(
+ dict(type=pq3.types.BackendKeyData, payload=dict(pid=1, key=7)),
+ b"K\x00\x00\x00\x0C\x00\x00\x00\x01\x00\x00\x00\x07",
+ id="implied len/type for BackendKeyData",
+ ),
+ pytest.param(
+ dict(type=pq3.types.CommandComplete, payload=dict(tag=b"SET")),
+ b"C\x00\x00\x00\x08SET\x00",
+ id="implied len/type for CommandComplete",
+ ),
+ pytest.param(
+ dict(type=pq3.types.ErrorResponse, payload=dict(fields=[b"error", b""])),
+ b"E\x00\x00\x00\x0Berror\x00\x00",
+ id="implied len/type for ErrorResponse",
+ ),
+ pytest.param(
+ dict(type=pq3.types.ParameterStatus, payload=dict(name=b"a", value=b"b")),
+ b"S\x00\x00\x00\x08a\x00b\x00",
+ id="implied len/type for ParameterStatus",
+ ),
+ pytest.param(
+ dict(type=pq3.types.ReadyForQuery, payload=dict(status=b"I")),
+ b"Z\x00\x00\x00\x05I",
+ id="implied len/type for ReadyForQuery",
+ ),
+ pytest.param(
+ dict(type=pq3.types.Query, payload=dict(query=b"SELECT 1;")),
+ b"Q\x00\x00\x00\x0eSELECT 1;\x00",
+ id="implied len/type for Query",
+ ),
+ pytest.param(
+ dict(type=pq3.types.DataRow, payload=dict(columns=[b"abcd"])),
+ b"D\x00\x00\x00\x0E\x00\x01\x00\x00\x00\x04abcd",
+ id="implied len/type for DataRow",
+ ),
+ pytest.param(
+ dict(type=pq3.types.EmptyQueryResponse),
+ b"I\x00\x00\x00\x04",
+ id="implied len for EmptyQueryResponse",
+ ),
+ pytest.param(
+ dict(type=pq3.types.Terminate),
+ b"X\x00\x00\x00\x04",
+ id="implied len for Terminate",
+ ),
+ ],
+)
+def test_Pq3_build(fields, expected):
+ actual = pq3.Pq3.build(fields)
+ assert actual == expected
+
+
[email protected](
+ "raw,expected,extra",
+ [
+ pytest.param(
+ b"\x00\x00",
+ dict(columns=[]),
+ b"",
+ id="no columns",
+ ),
+ pytest.param(
+ b"\x00\x01\x00\x00\x00\x04abcd",
+ dict(columns=[b"abcd"]),
+ b"",
+ id="one column",
+ ),
+ pytest.param(
+ b"\x00\x02\x00\x00\x00\x04abcd\x00\x00\x00\x01x",
+ dict(columns=[b"abcd", b"x"]),
+ b"",
+ id="multiple columns",
+ ),
+ pytest.param(
+ b"\x00\x02\x00\x00\x00\x00\x00\x00\x00\x01x",
+ dict(columns=[b"", b"x"]),
+ b"",
+ id="empty column value",
+ ),
+ pytest.param(
+ b"\x00\x02\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
+ dict(columns=[None, None]),
+ b"",
+ id="null columns",
+ ),
+ ],
+)
+def test_DataRow_parse(raw, expected, extra):
+ pkt = b"D" + struct.pack("!i", len(raw) + 4) + raw
+ with io.BytesIO(pkt) as stream:
+ actual = pq3.Pq3.parse_stream(stream)
+
+ assert actual.type == pq3.types.DataRow
+ assert actual.payload == expected
+ assert stream.read() == extra
+
+
[email protected](
+ "fields,expected",
+ [
+ pytest.param(
+ dict(),
+ b"\x00\x00",
+ id="no columns",
+ ),
+ pytest.param(
+ dict(columns=[None, None]),
+ b"\x00\x02\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
+ id="null columns",
+ ),
+ ],
+)
+def test_DataRow_build(fields, expected):
+ actual = pq3.Pq3.build(dict(type=pq3.types.DataRow, payload=fields))
+
+ expected = b"D" + struct.pack("!i", len(expected) + 4) + expected
+ assert actual == expected
+
+
[email protected](
+ "raw,expected,exception",
+ [
+ pytest.param(
+ b"EXTERNAL\x00\xFF\xFF\xFF\xFF",
+ dict(name=b"EXTERNAL", len=-1, data=None),
+ None,
+ id="no initial response",
+ ),
+ pytest.param(
+ b"EXTERNAL\x00\x00\x00\x00\x02me",
+ dict(name=b"EXTERNAL", len=2, data=b"me"),
+ None,
+ id="initial response",
+ ),
+ pytest.param(
+ b"EXTERNAL\x00\x00\x00\x00\x02meextra",
+ None,
+ TerminatedError,
+ id="extra data",
+ ),
+ pytest.param(
+ b"EXTERNAL\x00\x00\x00\x00\xFFme",
+ None,
+ StreamError,
+ id="underflow",
+ ),
+ ],
+)
+def test_SASLInitialResponse_parse(raw, expected, exception):
+ ctx = contextlib.nullcontext()
+ if exception:
+ ctx = pytest.raises(exception)
+
+ with ctx:
+ actual = pq3.SASLInitialResponse.parse(raw)
+ assert actual == expected
+
+
[email protected](
+ "fields,expected",
+ [
+ pytest.param(
+ dict(name=b"EXTERNAL"),
+ b"EXTERNAL\x00\xFF\xFF\xFF\xFF",
+ id="no initial response",
+ ),
+ pytest.param(
+ dict(name=b"EXTERNAL", data=None),
+ b"EXTERNAL\x00\xFF\xFF\xFF\xFF",
+ id="no initial response (explicit None)",
+ ),
+ pytest.param(
+ dict(name=b"EXTERNAL", data=b""),
+ b"EXTERNAL\x00\x00\x00\x00\x00",
+ id="empty response",
+ ),
+ pytest.param(
+ dict(name=b"EXTERNAL", data=b"[email protected]"),
+ b"EXTERNAL\x00\x00\x00\x00\[email protected]",
+ id="initial response",
+ ),
+ pytest.param(
+ dict(name=b"EXTERNAL", len=2, data=b"[email protected]"),
+ b"EXTERNAL\x00\x00\x00\x00\[email protected]",
+ id="data overflow",
+ ),
+ pytest.param(
+ dict(name=b"EXTERNAL", len=14, data=b"me"),
+ b"EXTERNAL\x00\x00\x00\x00\x0Eme",
+ id="data underflow",
+ ),
+ ],
+)
+def test_SASLInitialResponse_build(fields, expected):
+ actual = pq3.SASLInitialResponse.build(fields)
+ assert actual == expected
+
+
[email protected](
+ "version,expected_bytes",
+ [
+ pytest.param((3, 0), b"\x00\x03\x00\x00", id="version 3"),
+ pytest.param((1234, 5679), b"\x04\xd2\x16\x2f", id="SSLRequest"),
+ ],
+)
+def test_protocol(version, expected_bytes):
+ # Make sure the integer returned by protocol is correctly serialized on the
+ # wire.
+ assert struct.pack("!i", pq3.protocol(*version)) == expected_bytes
+
+
[email protected](
+ "envvar,func,expected",
+ [
+ ("PGHOST", pq3.pghost, "localhost"),
+ ("PGPORT", pq3.pgport, 5432),
+ ("PGUSER", pq3.pguser, getpass.getuser()),
+ ("PGDATABASE", pq3.pgdatabase, "postgres"),
+ ],
+)
+def test_env_defaults(monkeypatch, envvar, func, expected):
+ monkeypatch.delenv(envvar, raising=False)
+
+ actual = func()
+ assert actual == expected
+
+
[email protected](
+ "envvars,func,expected",
+ [
+ (dict(PGHOST="otherhost"), pq3.pghost, "otherhost"),
+ (dict(PGPORT="6789"), pq3.pgport, 6789),
+ (dict(PGUSER="postgres"), pq3.pguser, "postgres"),
+ (dict(PGDATABASE="template1"), pq3.pgdatabase, "template1"),
+ ],
+)
+def test_env(monkeypatch, envvars, func, expected):
+ for k, v in envvars.items():
+ monkeypatch.setenv(k, v)
+
+ actual = func()
+ assert actual == expected
diff --git a/src/test/python/tls.py b/src/test/python/tls.py
new file mode 100644
index 0000000000..075c02c1ca
--- /dev/null
+++ b/src/test/python/tls.py
@@ -0,0 +1,195 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+from construct import *
+
+#
+# TLS 1.3
+#
+# Most of the types below are transcribed from RFC 8446:
+#
+# https://tools.ietf.org/html/rfc8446
+#
+
+
+def _Vector(size_field, element):
+ return Prefixed(size_field, GreedyRange(element))
+
+
+# Alerts
+
+AlertLevel = Enum(
+ Byte,
+ warning=1,
+ fatal=2,
+)
+
+AlertDescription = Enum(
+ Byte,
+ close_notify=0,
+ unexpected_message=10,
+ bad_record_mac=20,
+ decryption_failed_RESERVED=21,
+ record_overflow=22,
+ decompression_failure=30,
+ handshake_failure=40,
+ no_certificate_RESERVED=41,
+ bad_certificate=42,
+ unsupported_certificate=43,
+ certificate_revoked=44,
+ certificate_expired=45,
+ certificate_unknown=46,
+ illegal_parameter=47,
+ unknown_ca=48,
+ access_denied=49,
+ decode_error=50,
+ decrypt_error=51,
+ export_restriction_RESERVED=60,
+ protocol_version=70,
+ insufficient_security=71,
+ internal_error=80,
+ user_canceled=90,
+ no_renegotiation=100,
+ unsupported_extension=110,
+)
+
+Alert = Struct(
+ "level" / AlertLevel,
+ "description" / AlertDescription,
+)
+
+
+# Extensions
+
+ExtensionType = Enum(
+ Int16ub,
+ server_name=0,
+ max_fragment_length=1,
+ status_request=5,
+ supported_groups=10,
+ signature_algorithms=13,
+ use_srtp=14,
+ heartbeat=15,
+ application_layer_protocol_negotiation=16,
+ signed_certificate_timestamp=18,
+ client_certificate_type=19,
+ server_certificate_type=20,
+ padding=21,
+ pre_shared_key=41,
+ early_data=42,
+ supported_versions=43,
+ cookie=44,
+ psk_key_exchange_modes=45,
+ certificate_authorities=47,
+ oid_filters=48,
+ post_handshake_auth=49,
+ signature_algorithms_cert=50,
+ key_share=51,
+)
+
+Extension = Struct(
+ "extension_type" / ExtensionType,
+ "extension_data" / Prefixed(Int16ub, GreedyBytes),
+)
+
+
+# ClientHello
+
+
+class _CipherSuiteAdapter(Adapter):
+ class _hextuple(tuple):
+ def __repr__(self):
+ return f"(0x{self[0]:02X}, 0x{self[1]:02X})"
+
+ def _encode(self, obj, context, path):
+ return bytes(obj)
+
+ def _decode(self, obj, context, path):
+ assert len(obj) == 2
+ return self._hextuple(obj)
+
+
+ProtocolVersion = Hex(Int16ub)
+
+Random = Hex(Bytes(32))
+
+CipherSuite = _CipherSuiteAdapter(Byte[2])
+
+ClientHello = Struct(
+ "legacy_version" / ProtocolVersion,
+ "random" / Random,
+ "legacy_session_id" / Prefixed(Byte, Hex(GreedyBytes)),
+ "cipher_suites" / _Vector(Int16ub, CipherSuite),
+ "legacy_compression_methods" / Prefixed(Byte, GreedyBytes),
+ "extensions" / _Vector(Int16ub, Extension),
+)
+
+# ServerHello
+
+ServerHello = Struct(
+ "legacy_version" / ProtocolVersion,
+ "random" / Random,
+ "legacy_session_id_echo" / Prefixed(Byte, Hex(GreedyBytes)),
+ "cipher_suite" / CipherSuite,
+ "legacy_compression_method" / Hex(Byte),
+ "extensions" / _Vector(Int16ub, Extension),
+)
+
+# Handshake
+
+HandshakeType = Enum(
+ Byte,
+ client_hello=1,
+ server_hello=2,
+ new_session_ticket=4,
+ end_of_early_data=5,
+ encrypted_extensions=8,
+ certificate=11,
+ certificate_request=13,
+ certificate_verify=15,
+ finished=20,
+ key_update=24,
+ message_hash=254,
+)
+
+Handshake = Struct(
+ "msg_type" / HandshakeType,
+ "length" / Int24ub,
+ "payload"
+ / Switch(
+ this.msg_type,
+ {
+ HandshakeType.client_hello: ClientHello,
+ HandshakeType.server_hello: ServerHello,
+ # HandshakeType.end_of_early_data: EndOfEarlyData,
+ # HandshakeType.encrypted_extensions: EncryptedExtensions,
+ # HandshakeType.certificate_request: CertificateRequest,
+ # HandshakeType.certificate: Certificate,
+ # HandshakeType.certificate_verify: CertificateVerify,
+ # HandshakeType.finished: Finished,
+ # HandshakeType.new_session_ticket: NewSessionTicket,
+ # HandshakeType.key_update: KeyUpdate,
+ },
+ default=FixedSized(this.length, GreedyBytes),
+ ),
+)
+
+# Records
+
+ContentType = Enum(
+ Byte,
+ invalid=0,
+ change_cipher_spec=20,
+ alert=21,
+ handshake=22,
+ application_data=23,
+)
+
+Plaintext = Struct(
+ "type" / ContentType,
+ "legacy_record_version" / ProtocolVersion,
+ "length" / Int16ub,
+ "fragment" / FixedSized(this.length, GreedyBytes),
+)
--
2.25.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2021-06-18 04:07 ` Michael Paquier <[email protected]>
2021-06-22 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Michael Paquier @ 2021-06-18 04:07 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: pgsql-hackers
On Tue, Jun 08, 2021 at 04:37:46PM +0000, Jacob Champion wrote:
> 1. Prep
>
> 0001 decouples the SASL code from the SCRAM implementation.
> 0002 makes it possible to use common/jsonapi from the frontend.
> 0003 lets the json_errdetail() result be freed, to avoid leaks.
>
> The first three patches are, hopefully, generally useful outside of
> this implementation, and I'll plan to register them in the next
> commitfest. The middle two patches are the "interesting" pieces, and
> I've split them into client and server for ease of understanding,
> though neither is particularly useful without the other.
Beginning with the beginning, could you spawn two threads for the
jsonapi rework and the SASL/SCRAM business? I agree that these look
independently useful. Glad to see someone improving the code with
SASL and SCRAM which are too inter-dependent now. I saw in the RFCs
dedicated to OAUTH the need for the JSON part as well.
+# define check_stack_depth()
+# ifdef JSONAPI_NO_LOG
+# define json_log_and_abort(...) \
+ do { fprintf(stderr, __VA_ARGS__); exit(1); } while(0)
+# else
In patch 0002, this is the wrong approach. libpq will not be able to
feed on such reports, and you cannot use any of the APIs from the
palloc() family either as these just fail on OOM. libpq should be
able to know about the error, and would fill in the error back to the
application. This abstraction is not necessary on HEAD as
pg_verifybackup is fine with this level of reporting. My rough guess
is that we will need to split the existing jsonapi.c into two files,
one that can be used in shared libraries and a second that handles the
errors.
+ /* TODO: SASL_EXCHANGE_FAILURE with output is forbidden in SASL */
if (result == SASL_EXCHANGE_SUCCESS)
sendAuthRequest(port,
AUTH_REQ_SASL_FIN,
output,
outputlen);
Perhaps that's an issue we need to worry on its own? I didn't recall
this part..
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 04:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Michael Paquier <[email protected]>
@ 2021-06-22 23:26 ` Jacob Champion <[email protected]>
2021-06-23 06:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Michael Paquier <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2021-06-22 23:26 UTC (permalink / raw)
To: [email protected] <[email protected]>; +Cc: pgsql-hackers
On Fri, 2021-06-18 at 13:07 +0900, Michael Paquier wrote:
> On Tue, Jun 08, 2021 at 04:37:46PM +0000, Jacob Champion wrote:
> > 1. Prep
> >
> > 0001 decouples the SASL code from the SCRAM implementation.
> > 0002 makes it possible to use common/jsonapi from the frontend.
> > 0003 lets the json_errdetail() result be freed, to avoid leaks.
> >
> > The first three patches are, hopefully, generally useful outside of
> > this implementation, and I'll plan to register them in the next
> > commitfest. The middle two patches are the "interesting" pieces, and
> > I've split them into client and server for ease of understanding,
> > though neither is particularly useful without the other.
>
> Beginning with the beginning, could you spawn two threads for the
> jsonapi rework and the SASL/SCRAM business?
Done [1, 2]. I've copied your comments into those threads with my
responses, and I'll have them registered in commitfest shortly.
Thanks!
--Jacob
[1] https://www.postgresql.org/message-id/3d2a6f5d50e741117d6baf83eb67ebf1a8a35a11.camel%40vmware.com
[2] https://www.postgresql.org/message-id/a250d475ba1c0cc0efb7dfec8e538fcc77cdcb8e.camel%40vmware.com
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 04:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Michael Paquier <[email protected]>
2021-06-22 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2021-06-23 06:10 ` Michael Paquier <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Michael Paquier @ 2021-06-23 06:10 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: pgsql-hackers
On Tue, Jun 22, 2021 at 11:26:03PM +0000, Jacob Champion wrote:
> Done [1, 2]. I've copied your comments into those threads with my
> responses, and I'll have them registered in commitfest shortly.
Thanks!
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2021-06-18 08:31 ` Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Heikki Linnakangas @ 2021-06-18 08:31 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; pgsql-hackers
On 08/06/2021 19:37, Jacob Champion wrote:
> We've been working on ways to expand the list of third-party auth
> methods that Postgres provides. Some example use cases might be "I want
> to let anyone with a Google account read this table" or "let anyone who
> belongs to this GitHub organization connect as a superuser".
Cool!
> The iddawc dependency for client-side OAuth was extremely helpful to
> develop this proof of concept quickly, but I don't think it would be an
> appropriate component to build a real feature on. It's extremely
> heavyweight -- it incorporates a huge stack of dependencies, including
> a logging framework and a web server, to implement features we would
> probably never use -- and it's fairly difficult to debug in practice.
> If a device authorization flow were the only thing that libpq needed to
> support natively, I think we should just depend on a widely used HTTP
> client, like libcurl or neon, and implement the minimum spec directly
> against the existing test suite.
You could punt and let the application implement that stuff. I'm
imagining that the application code would look something like this:
conn = PQconnectStartParams(...);
for (;;)
{
status = PQconnectPoll(conn)
switch (status)
{
case CONNECTION_SASL_TOKEN_REQUIRED:
/* open a browser for the user, get token */
token = open_browser()
PQauthResponse(token);
break;
...
}
}
It would be nice to have a simple default implementation, though, for
psql and all the other client applications that come with PostgreSQL itself.
> If you've read this far, thank you for your interest, and I hope you
> enjoy playing with it!
A few small things caught my eye in the backend oauth_exchange function:
> + /* Handle the client's initial message. */
> + p = strdup(input);
this strdup() should be pstrdup().
In the same function, there are a bunch of reports like this:
> ereport(ERROR,
> + (errcode(ERRCODE_PROTOCOL_VIOLATION),
> + errmsg("malformed OAUTHBEARER message"),
> + errdetail("Comma expected, but found character \"%s\".",
> + sanitize_char(*p))));
I don't think the double quotes are needed here, because sanitize_char
will return quotes if it's a single character. So it would end up
looking like this: ... found character "'x'".
- Heikki
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
@ 2021-06-22 23:22 ` Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2021-06-22 23:22 UTC (permalink / raw)
To: [email protected] <[email protected]>; pgsql-hackers
On Fri, 2021-06-18 at 11:31 +0300, Heikki Linnakangas wrote:
> On 08/06/2021 19:37, Jacob Champion wrote:
> > We've been working on ways to expand the list of third-party auth
> > methods that Postgres provides. Some example use cases might be "I want
> > to let anyone with a Google account read this table" or "let anyone who
> > belongs to this GitHub organization connect as a superuser".
>
> Cool!
Glad you think so! :D
> > The iddawc dependency for client-side OAuth was extremely helpful to
> > develop this proof of concept quickly, but I don't think it would be an
> > appropriate component to build a real feature on. It's extremely
> > heavyweight -- it incorporates a huge stack of dependencies, including
> > a logging framework and a web server, to implement features we would
> > probably never use -- and it's fairly difficult to debug in practice.
> > If a device authorization flow were the only thing that libpq needed to
> > support natively, I think we should just depend on a widely used HTTP
> > client, like libcurl or neon, and implement the minimum spec directly
> > against the existing test suite.
>
> You could punt and let the application implement that stuff. I'm
> imagining that the application code would look something like this:
>
> conn = PQconnectStartParams(...);
> for (;;)
> {
> status = PQconnectPoll(conn)
> switch (status)
> {
> case CONNECTION_SASL_TOKEN_REQUIRED:
> /* open a browser for the user, get token */
> token = open_browser()
> PQauthResponse(token);
> break;
> ...
> }
> }
I was toying with the idea of having a callback for libpq clients,
where they could take full control of the OAuth flow if they wanted to.
Doing it inline with PQconnectPoll seems like it would work too. It has
a couple of drawbacks that I can see:
- If a client isn't currently using a poll loop, they'd have to switch
to one to be able to use OAuth connections. Not a difficult change, but
considering all the other hurdles to making this work, I'm hoping to
minimize the hoop-jumping.
- A client would still have to receive a bunch of OAuth parameters from
some new libpq API in order to construct the correct URL to visit, so
the overall complexity for implementers might be higher than if we just
passed those params directly in a callback.
> It would be nice to have a simple default implementation, though, for
> psql and all the other client applications that come with PostgreSQL itself.
I agree. I think having a bare-bones implementation in libpq itself
would make initial adoption *much* easier, and then if specific
applications wanted to have richer control over an authorization flow,
then they could implement that themselves with the aforementioned
callback.
The Device Authorization flow was the most minimal working
implementation I could find, since it doesn't require a web browser on
the system, just the ability to print a prompt to the console. But if
anyone knows of a better flow for this use case, I'm all ears.
> > If you've read this far, thank you for your interest, and I hope you
> > enjoy playing with it!
>
> A few small things caught my eye in the backend oauth_exchange function:
>
> > + /* Handle the client's initial message. */
> > + p = strdup(input);
>
> this strdup() should be pstrdup().
Thanks, I'll fix that in the next re-roll.
> In the same function, there are a bunch of reports like this:
>
> > ereport(ERROR,
> > + (errcode(ERRCODE_PROTOCOL_VIOLATION),
> > + errmsg("malformed OAUTHBEARER message"),
> > + errdetail("Comma expected, but found character \"%s\".",
> > + sanitize_char(*p))));
>
> I don't think the double quotes are needed here, because sanitize_char
> will return quotes if it's a single character. So it would end up
> looking like this: ... found character "'x'".
I'll fix this too. Thanks!
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2021-08-25 18:41 ` Jacob Champion <[email protected]>
2021-08-25 22:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Zhihong Yu <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-20 05:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
0 siblings, 3 replies; 194+ messages in thread
From: Jacob Champion @ 2021-08-25 18:41 UTC (permalink / raw)
To: [email protected] <[email protected]>; pgsql-hackers; +Cc: [email protected] <[email protected]>
On Tue, 2021-06-22 at 23:22 +0000, Jacob Champion wrote:
> On Fri, 2021-06-18 at 11:31 +0300, Heikki Linnakangas wrote:
> >
> > A few small things caught my eye in the backend oauth_exchange function:
> >
> > > + /* Handle the client's initial message. */
> > > + p = strdup(input);
> >
> > this strdup() should be pstrdup().
>
> Thanks, I'll fix that in the next re-roll.
>
> > In the same function, there are a bunch of reports like this:
> >
> > > ereport(ERROR,
> > > + (errcode(ERRCODE_PROTOCOL_VIOLATION),
> > > + errmsg("malformed OAUTHBEARER message"),
> > > + errdetail("Comma expected, but found character \"%s\".",
> > > + sanitize_char(*p))));
> >
> > I don't think the double quotes are needed here, because sanitize_char
> > will return quotes if it's a single character. So it would end up
> > looking like this: ... found character "'x'".
>
> I'll fix this too. Thanks!
v2, attached, incorporates Heikki's suggested fixes and also rebases on
top of latest HEAD, which had the SASL refactoring changes committed
last month.
The biggest change from the last patchset is 0001, an attempt at
enabling jsonapi in the frontend without the use of palloc(), based on
suggestions by Michael and Tom from last commitfest. I've also made
some improvements to the pytest suite. No major changes to the OAuth
implementation yet.
--Jacob
Attachments:
[text/x-patch] v2-0001-common-jsonapi-support-FRONTEND-clients.patch (20.4K, ../../[email protected]/2-v2-0001-common-jsonapi-support-FRONTEND-clients.patch)
download | inline diff:
From 8c4b82940efb7e0f0f33ac915d5f7969a36e3644 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Mon, 3 May 2021 15:38:26 -0700
Subject: [PATCH v2 1/5] common/jsonapi: support FRONTEND clients
Based on a patch by Michael Paquier.
For frontend code, use PQExpBuffer instead of StringInfo. This requires
us to track allocation failures so that we can return JSON_OUT_OF_MEMORY
as needed. json_errdetail() now allocates its error message inside
memory owned by the JsonLexContext, so clients don't need to worry about
freeing it.
For convenience, the backend now has destroyJsonLexContext() to mirror
other create/destroy APIs. The frontend has init/term versions of the
API to handle stack-allocated JsonLexContexts.
We can now partially revert b44669b2ca, now that json_errdetail() works
correctly.
---
src/backend/utils/adt/jsonfuncs.c | 4 +-
src/bin/pg_verifybackup/parse_manifest.c | 13 +-
src/bin/pg_verifybackup/t/005_bad_manifest.pl | 2 +-
src/common/Makefile | 2 +-
src/common/jsonapi.c | 290 +++++++++++++-----
src/include/common/jsonapi.h | 47 ++-
6 files changed, 270 insertions(+), 88 deletions(-)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 5fd54b64b5..fa39751188 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -723,9 +723,7 @@ json_object_keys(PG_FUNCTION_ARGS)
pg_parse_json_or_ereport(lex, sem);
/* keys are now in state->result */
- pfree(lex->strval->data);
- pfree(lex->strval);
- pfree(lex);
+ destroyJsonLexContext(lex);
pfree(sem);
MemoryContextSwitchTo(oldcontext);
diff --git a/src/bin/pg_verifybackup/parse_manifest.c b/src/bin/pg_verifybackup/parse_manifest.c
index c7ccc78c70..6cedb7435f 100644
--- a/src/bin/pg_verifybackup/parse_manifest.c
+++ b/src/bin/pg_verifybackup/parse_manifest.c
@@ -119,7 +119,7 @@ void
json_parse_manifest(JsonManifestParseContext *context, char *buffer,
size_t size)
{
- JsonLexContext *lex;
+ JsonLexContext lex = {0};
JsonParseErrorType json_error;
JsonSemAction sem;
JsonManifestParseState parse;
@@ -129,8 +129,8 @@ json_parse_manifest(JsonManifestParseContext *context, char *buffer,
parse.state = JM_EXPECT_TOPLEVEL_START;
parse.saw_version_field = false;
- /* Create a JSON lexing context. */
- lex = makeJsonLexContextCstringLen(buffer, size, PG_UTF8, true);
+ /* Initialize a JSON lexing context. */
+ initJsonLexContextCstringLen(&lex, buffer, size, PG_UTF8, true);
/* Set up semantic actions. */
sem.semstate = &parse;
@@ -145,14 +145,17 @@ json_parse_manifest(JsonManifestParseContext *context, char *buffer,
sem.scalar = json_manifest_scalar;
/* Run the actual JSON parser. */
- json_error = pg_parse_json(lex, &sem);
+ json_error = pg_parse_json(&lex, &sem);
if (json_error != JSON_SUCCESS)
- json_manifest_parse_failure(context, "parsing failed");
+ json_manifest_parse_failure(context, json_errdetail(json_error, &lex));
if (parse.state != JM_EXPECT_EOF)
json_manifest_parse_failure(context, "manifest ended unexpectedly");
/* Verify the manifest checksum. */
verify_manifest_checksum(&parse, buffer, size);
+
+ /* Clean up. */
+ termJsonLexContext(&lex);
}
/*
diff --git a/src/bin/pg_verifybackup/t/005_bad_manifest.pl b/src/bin/pg_verifybackup/t/005_bad_manifest.pl
index 4f5b8f5a49..9f8a100a71 100644
--- a/src/bin/pg_verifybackup/t/005_bad_manifest.pl
+++ b/src/bin/pg_verifybackup/t/005_bad_manifest.pl
@@ -16,7 +16,7 @@ my $tempdir = TestLib::tempdir;
test_bad_manifest(
'input string ended unexpectedly',
- qr/could not parse backup manifest: parsing failed/,
+ qr/could not parse backup manifest: The input string ended unexpectedly/,
<<EOM);
{
EOM
diff --git a/src/common/Makefile b/src/common/Makefile
index 880722fcf5..5ecb09a8c4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -40,7 +40,7 @@ override CPPFLAGS += -DVAL_LDFLAGS_EX="\"$(LDFLAGS_EX)\""
override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
-override CPPFLAGS := -DFRONTEND -I. -I$(top_srcdir)/src/common $(CPPFLAGS)
+override CPPFLAGS := -DFRONTEND -I. -I$(top_srcdir)/src/common -I$(libpq_srcdir) $(CPPFLAGS)
LIBS += $(PTHREAD_LIBS)
# If you add objects here, see also src/tools/msvc/Mkvcbuild.pm
diff --git a/src/common/jsonapi.c b/src/common/jsonapi.c
index 5504072b4f..3a9620f739 100644
--- a/src/common/jsonapi.c
+++ b/src/common/jsonapi.c
@@ -20,10 +20,39 @@
#include "common/jsonapi.h"
#include "mb/pg_wchar.h"
-#ifndef FRONTEND
+#ifdef FRONTEND
+#include "pqexpbuffer.h"
+#else
+#include "lib/stringinfo.h"
#include "miscadmin.h"
#endif
+/*
+ * In backend, we will use palloc/pfree along with StringInfo. In frontend, use
+ * malloc and PQExpBuffer, and return JSON_OUT_OF_MEMORY on out-of-memory.
+ */
+#ifdef FRONTEND
+
+#define STRDUP(s) strdup(s)
+#define ALLOC(size) malloc(size)
+
+#define appendStrVal appendPQExpBuffer
+#define appendStrValChar appendPQExpBufferChar
+#define createStrVal createPQExpBuffer
+#define resetStrVal resetPQExpBuffer
+
+#else /* !FRONTEND */
+
+#define STRDUP(s) pstrdup(s)
+#define ALLOC(size) palloc(size)
+
+#define appendStrVal appendStringInfo
+#define appendStrValChar appendStringInfoChar
+#define createStrVal makeStringInfo
+#define resetStrVal resetStringInfo
+
+#endif
+
/*
* The context of the parser is maintained by the recursive descent
* mechanism, but is passed explicitly to the error reporting routine
@@ -132,10 +161,12 @@ IsValidJsonNumber(const char *str, int len)
return (!numeric_error) && (total_len == dummy_lex.input_length);
}
+#ifndef FRONTEND
+
/*
* makeJsonLexContextCstringLen
*
- * lex constructor, with or without StringInfo object for de-escaped lexemes.
+ * lex constructor, with or without a string object for de-escaped lexemes.
*
* Without is better as it makes the processing faster, so only make one
* if really required.
@@ -145,13 +176,66 @@ makeJsonLexContextCstringLen(char *json, int len, int encoding, bool need_escape
{
JsonLexContext *lex = palloc0(sizeof(JsonLexContext));
+ initJsonLexContextCstringLen(lex, json, len, encoding, need_escapes);
+
+ return lex;
+}
+
+void
+destroyJsonLexContext(JsonLexContext *lex)
+{
+ termJsonLexContext(lex);
+ pfree(lex);
+}
+
+#endif /* !FRONTEND */
+
+void
+initJsonLexContextCstringLen(JsonLexContext *lex, char *json, int len, int encoding, bool need_escapes)
+{
lex->input = lex->token_terminator = lex->line_start = json;
lex->line_number = 1;
lex->input_length = len;
lex->input_encoding = encoding;
- if (need_escapes)
- lex->strval = makeStringInfo();
- return lex;
+ lex->parse_strval = need_escapes;
+ if (lex->parse_strval)
+ {
+ /*
+ * This call can fail in FRONTEND code. We defer error handling to time
+ * of use (json_lex_string()) since there's no way to signal failure
+ * here, and we might not need to parse any strings anyway.
+ */
+ lex->strval = createStrVal();
+ }
+ lex->errormsg = NULL;
+}
+
+void
+termJsonLexContext(JsonLexContext *lex)
+{
+ static const JsonLexContext empty = {0};
+
+ if (lex->strval)
+ {
+#ifdef FRONTEND
+ destroyPQExpBuffer(lex->strval);
+#else
+ pfree(lex->strval->data);
+ pfree(lex->strval);
+#endif
+ }
+
+ if (lex->errormsg)
+ {
+#ifdef FRONTEND
+ destroyPQExpBuffer(lex->errormsg);
+#else
+ pfree(lex->errormsg->data);
+ pfree(lex->errormsg);
+#endif
+ }
+
+ *lex = empty;
}
/*
@@ -217,7 +301,7 @@ json_count_array_elements(JsonLexContext *lex, int *elements)
* etc, so doing this with a copy makes that safe.
*/
memcpy(©lex, lex, sizeof(JsonLexContext));
- copylex.strval = NULL; /* not interested in values here */
+ copylex.parse_strval = false; /* not interested in values here */
copylex.lex_level++;
count = 0;
@@ -279,14 +363,21 @@ parse_scalar(JsonLexContext *lex, JsonSemAction *sem)
/* extract the de-escaped string value, or the raw lexeme */
if (lex_peek(lex) == JSON_TOKEN_STRING)
{
- if (lex->strval != NULL)
- val = pstrdup(lex->strval->data);
+ if (lex->parse_strval)
+ {
+ val = STRDUP(lex->strval->data);
+ if (val == NULL)
+ return JSON_OUT_OF_MEMORY;
+ }
}
else
{
int len = (lex->token_terminator - lex->token_start);
- val = palloc(len + 1);
+ val = ALLOC(len + 1);
+ if (val == NULL)
+ return JSON_OUT_OF_MEMORY;
+
memcpy(val, lex->token_start, len);
val[len] = '\0';
}
@@ -320,8 +411,12 @@ parse_object_field(JsonLexContext *lex, JsonSemAction *sem)
if (lex_peek(lex) != JSON_TOKEN_STRING)
return report_parse_error(JSON_PARSE_STRING, lex);
- if ((ostart != NULL || oend != NULL) && lex->strval != NULL)
- fname = pstrdup(lex->strval->data);
+ if ((ostart != NULL || oend != NULL) && lex->parse_strval)
+ {
+ fname = STRDUP(lex->strval->data);
+ if (fname == NULL)
+ return JSON_OUT_OF_MEMORY;
+ }
result = json_lex(lex);
if (result != JSON_SUCCESS)
return result;
@@ -368,6 +463,10 @@ parse_object(JsonLexContext *lex, JsonSemAction *sem)
JsonParseErrorType result;
#ifndef FRONTEND
+ /*
+ * TODO: clients need some way to put a bound on stack growth. Parse level
+ * limits maybe?
+ */
check_stack_depth();
#endif
@@ -676,8 +775,15 @@ json_lex_string(JsonLexContext *lex)
int len;
int hi_surrogate = -1;
- if (lex->strval != NULL)
- resetStringInfo(lex->strval);
+ if (lex->parse_strval)
+ {
+#ifdef FRONTEND
+ /* make sure initialization succeeded */
+ if (lex->strval == NULL)
+ return JSON_OUT_OF_MEMORY;
+#endif
+ resetStrVal(lex->strval);
+ }
Assert(lex->input_length > 0);
s = lex->token_start;
@@ -737,7 +843,7 @@ json_lex_string(JsonLexContext *lex)
return JSON_UNICODE_ESCAPE_FORMAT;
}
}
- if (lex->strval != NULL)
+ if (lex->parse_strval)
{
/*
* Combine surrogate pairs.
@@ -797,19 +903,19 @@ json_lex_string(JsonLexContext *lex)
unicode_to_utf8(ch, (unsigned char *) utf8str);
utf8len = pg_utf_mblen((unsigned char *) utf8str);
- appendBinaryStringInfo(lex->strval, utf8str, utf8len);
+ appendBinaryPQExpBuffer(lex->strval, utf8str, utf8len);
}
else if (ch <= 0x007f)
{
/* The ASCII range is the same in all encodings */
- appendStringInfoChar(lex->strval, (char) ch);
+ appendPQExpBufferChar(lex->strval, (char) ch);
}
else
return JSON_UNICODE_HIGH_ESCAPE;
#endif /* FRONTEND */
}
}
- else if (lex->strval != NULL)
+ else if (lex->parse_strval)
{
if (hi_surrogate != -1)
return JSON_UNICODE_LOW_SURROGATE;
@@ -819,22 +925,22 @@ json_lex_string(JsonLexContext *lex)
case '"':
case '\\':
case '/':
- appendStringInfoChar(lex->strval, *s);
+ appendStrValChar(lex->strval, *s);
break;
case 'b':
- appendStringInfoChar(lex->strval, '\b');
+ appendStrValChar(lex->strval, '\b');
break;
case 'f':
- appendStringInfoChar(lex->strval, '\f');
+ appendStrValChar(lex->strval, '\f');
break;
case 'n':
- appendStringInfoChar(lex->strval, '\n');
+ appendStrValChar(lex->strval, '\n');
break;
case 'r':
- appendStringInfoChar(lex->strval, '\r');
+ appendStrValChar(lex->strval, '\r');
break;
case 't':
- appendStringInfoChar(lex->strval, '\t');
+ appendStrValChar(lex->strval, '\t');
break;
default:
/* Not a valid string escape, so signal error. */
@@ -858,12 +964,12 @@ json_lex_string(JsonLexContext *lex)
}
}
- else if (lex->strval != NULL)
+ else if (lex->parse_strval)
{
if (hi_surrogate != -1)
return JSON_UNICODE_LOW_SURROGATE;
- appendStringInfoChar(lex->strval, *s);
+ appendStrValChar(lex->strval, *s);
}
}
@@ -871,6 +977,11 @@ json_lex_string(JsonLexContext *lex)
if (hi_surrogate != -1)
return JSON_UNICODE_LOW_SURROGATE;
+#ifdef FRONTEND
+ if (lex->parse_strval && PQExpBufferBroken(lex->strval))
+ return JSON_OUT_OF_MEMORY;
+#endif
+
/* Hooray, we found the end of the string! */
lex->prev_token_terminator = lex->token_terminator;
lex->token_terminator = s + 1;
@@ -1043,72 +1154,93 @@ report_parse_error(JsonParseContext ctx, JsonLexContext *lex)
return JSON_SUCCESS; /* silence stupider compilers */
}
-
-#ifndef FRONTEND
-/*
- * Extract the current token from a lexing context, for error reporting.
- */
-static char *
-extract_token(JsonLexContext *lex)
-{
- int toklen = lex->token_terminator - lex->token_start;
- char *token = palloc(toklen + 1);
-
- memcpy(token, lex->token_start, toklen);
- token[toklen] = '\0';
- return token;
-}
-
/*
* Construct a detail message for a JSON error.
*
- * Note that the error message generated by this routine may not be
- * palloc'd, making it unsafe for frontend code as there is no way to
- * know if this can be safery pfree'd or not.
+ * The returned allocation is either static or owned by the JsonLexContext and
+ * should not be freed.
*/
char *
json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
{
+ int toklen = lex->token_terminator - lex->token_start;
+
+ if (error == JSON_OUT_OF_MEMORY)
+ {
+ /* Short circuit. Allocating anything for this case is unhelpful. */
+ return _("out of memory");
+ }
+
+ if (lex->errormsg)
+ resetStrVal(lex->errormsg);
+ else
+ lex->errormsg = createStrVal();
+
switch (error)
{
case JSON_SUCCESS:
/* fall through to the error code after switch */
break;
case JSON_ESCAPING_INVALID:
- return psprintf(_("Escape sequence \"\\%s\" is invalid."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Escape sequence \"\\%.*s\" is invalid."),
+ toklen, lex->token_start);
+ break;
case JSON_ESCAPING_REQUIRED:
- return psprintf(_("Character with value 0x%02x must be escaped."),
- (unsigned char) *(lex->token_terminator));
+ appendStrVal(lex->errormsg,
+ _("Character with value 0x%02x must be escaped."),
+ (unsigned char) *(lex->token_terminator));
+ break;
case JSON_EXPECTED_END:
- return psprintf(_("Expected end of input, but found \"%s\"."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Expected end of input, but found \"%.*s\"."),
+ toklen, lex->token_start);
+ break;
case JSON_EXPECTED_ARRAY_FIRST:
- return psprintf(_("Expected array element or \"]\", but found \"%s\"."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Expected array element or \"]\", but found \"%.*s\"."),
+ toklen, lex->token_start);
+ break;
case JSON_EXPECTED_ARRAY_NEXT:
- return psprintf(_("Expected \",\" or \"]\", but found \"%s\"."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Expected \",\" or \"]\", but found \"%.*s\"."),
+ toklen, lex->token_start);
+ break;
case JSON_EXPECTED_COLON:
- return psprintf(_("Expected \":\", but found \"%s\"."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Expected \":\", but found \"%.*s\"."),
+ toklen, lex->token_start);
+ break;
case JSON_EXPECTED_JSON:
- return psprintf(_("Expected JSON value, but found \"%s\"."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Expected JSON value, but found \"%.*s\"."),
+ toklen, lex->token_start);
+ break;
case JSON_EXPECTED_MORE:
return _("The input string ended unexpectedly.");
case JSON_EXPECTED_OBJECT_FIRST:
- return psprintf(_("Expected string or \"}\", but found \"%s\"."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Expected string or \"}\", but found \"%.*s\"."),
+ toklen, lex->token_start);
+ break;
case JSON_EXPECTED_OBJECT_NEXT:
- return psprintf(_("Expected \",\" or \"}\", but found \"%s\"."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Expected \",\" or \"}\", but found \"%.*s\"."),
+ toklen, lex->token_start);
+ break;
case JSON_EXPECTED_STRING:
- return psprintf(_("Expected string, but found \"%s\"."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Expected string, but found \"%.*s\"."),
+ toklen, lex->token_start);
+ break;
case JSON_INVALID_TOKEN:
- return psprintf(_("Token \"%s\" is invalid."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Token \"%.*s\" is invalid."),
+ toklen, lex->token_start);
+ break;
+ case JSON_OUT_OF_MEMORY:
+ /* should have been handled above; use the error path */
+ break;
case JSON_UNICODE_CODE_POINT_ZERO:
return _("\\u0000 cannot be converted to text.");
case JSON_UNICODE_ESCAPE_FORMAT:
@@ -1122,12 +1254,22 @@ json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
return _("Unicode low surrogate must follow a high surrogate.");
}
- /*
- * We don't use a default: case, so that the compiler will warn about
- * unhandled enum values. But this needs to be here anyway to cover the
- * possibility of an incorrect input.
- */
- elog(ERROR, "unexpected json parse error type: %d", (int) error);
- return NULL;
-}
+ /* Note that lex->errormsg can be NULL in FRONTEND code. */
+ if (lex->errormsg && !lex->errormsg->data[0])
+ {
+ /*
+ * We don't use a default: case, so that the compiler will warn about
+ * unhandled enum values. But this needs to be here anyway to cover the
+ * possibility of an incorrect input.
+ */
+ appendStrVal(lex->errormsg,
+ "unexpected json parse error type: %d", (int) error);
+ }
+
+#ifdef FRONTEND
+ if (PQExpBufferBroken(lex->errormsg))
+ return _("out of memory while constructing error description");
#endif
+
+ return lex->errormsg->data;
+}
diff --git a/src/include/common/jsonapi.h b/src/include/common/jsonapi.h
index ec3dfce9c3..dc71ab2cd3 100644
--- a/src/include/common/jsonapi.h
+++ b/src/include/common/jsonapi.h
@@ -14,8 +14,6 @@
#ifndef JSONAPI_H
#define JSONAPI_H
-#include "lib/stringinfo.h"
-
typedef enum
{
JSON_TOKEN_INVALID,
@@ -48,6 +46,7 @@ typedef enum
JSON_EXPECTED_OBJECT_NEXT,
JSON_EXPECTED_STRING,
JSON_INVALID_TOKEN,
+ JSON_OUT_OF_MEMORY,
JSON_UNICODE_CODE_POINT_ZERO,
JSON_UNICODE_ESCAPE_FORMAT,
JSON_UNICODE_HIGH_ESCAPE,
@@ -55,6 +54,17 @@ typedef enum
JSON_UNICODE_LOW_SURROGATE
} JsonParseErrorType;
+/*
+ * Don't depend on the internal type header for strval; if callers need access
+ * then they can include the appropriate header themselves.
+ */
+#ifdef FRONTEND
+#define StrValType PQExpBufferData
+#else
+#define StrValType StringInfoData
+#endif
+
+typedef struct StrValType StrValType;
/*
* All the fields in this structure should be treated as read-only.
@@ -81,7 +91,9 @@ typedef struct JsonLexContext
int lex_level;
int line_number; /* line number, starting from 1 */
char *line_start; /* where that line starts within input */
- StringInfo strval;
+ bool parse_strval;
+ StrValType *strval; /* only used if parse_strval == true */
+ StrValType *errormsg;
} JsonLexContext;
typedef void (*json_struct_action) (void *state);
@@ -141,9 +153,10 @@ extern JsonSemAction nullSemAction;
*/
extern JsonParseErrorType json_count_array_elements(JsonLexContext *lex,
int *elements);
+#ifndef FRONTEND
/*
- * constructor for JsonLexContext, with or without strval element.
+ * allocating constructor for JsonLexContext, with or without strval element.
* If supplied, the strval element will contain a de-escaped version of
* the lexeme. However, doing this imposes a performance penalty, so
* it should be avoided if the de-escaped lexeme is not required.
@@ -153,6 +166,32 @@ extern JsonLexContext *makeJsonLexContextCstringLen(char *json,
int encoding,
bool need_escapes);
+/*
+ * Counterpart to makeJsonLexContextCstringLen(): clears and deallocates lex.
+ * The context pointer should not be used after this call.
+ */
+extern void destroyJsonLexContext(JsonLexContext *lex);
+
+#endif /* !FRONTEND */
+
+/*
+ * stack constructor for JsonLexContext, with or without strval element.
+ * If supplied, the strval element will contain a de-escaped version of
+ * the lexeme. However, doing this imposes a performance penalty, so
+ * it should be avoided if the de-escaped lexeme is not required.
+ */
+extern void initJsonLexContextCstringLen(JsonLexContext *lex,
+ char *json,
+ int len,
+ int encoding,
+ bool need_escapes);
+
+/*
+ * Counterpart to initJsonLexContextCstringLen(): clears the contents of lex,
+ * but does not deallocate lex itself.
+ */
+extern void termJsonLexContext(JsonLexContext *lex);
+
/* lex one token */
extern JsonParseErrorType json_lex(JsonLexContext *lex);
--
2.25.1
[text/x-patch] v2-0002-libpq-add-OAUTHBEARER-SASL-mechanism.patch (35.7K, ../../[email protected]/3-v2-0002-libpq-add-OAUTHBEARER-SASL-mechanism.patch)
download | inline diff:
From 52ac4bd25ca19735eb2bd863e8b1549ccbe6560a Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Tue, 13 Apr 2021 10:27:27 -0700
Subject: [PATCH v2 2/5] libpq: add OAUTHBEARER SASL mechanism
DO NOT USE THIS PROOF OF CONCEPT IN PRODUCTION.
Implement OAUTHBEARER (RFC 7628) and OAuth 2.0 Device Authorization
Grants (RFC 8628) on the client side. When speaking to a OAuth-enabled
server, it looks a bit like this:
$ psql 'host=example.org oauth_client_id=f02c6361-0635-...'
Visit https://oauth.example.org/login and enter the code: FPQ2-M4BG
The OAuth issuer must support device authorization. No other OAuth flows
are currently implemented.
The client implementation requires libiddawc and its development
headers. Configure --with-oauth (and --with-includes/--with-libraries to
point at the iddawc installation, if it's in a custom location).
Several TODOs:
- don't retry forever if the server won't accept our token
- perform several sanity checks on the OAuth issuer's responses
- handle cases where the client has been set up with an issuer and
scope, but the Postgres server wants to use something different
- improve error debuggability during the OAuth handshake
- ...and more.
---
configure | 100 ++++
configure.ac | 19 +
src/Makefile.global.in | 1 +
src/include/common/oauth-common.h | 19 +
src/include/pg_config.h.in | 6 +
src/interfaces/libpq/Makefile | 7 +-
src/interfaces/libpq/fe-auth-oauth.c | 745 +++++++++++++++++++++++++++
src/interfaces/libpq/fe-auth-sasl.h | 5 +-
src/interfaces/libpq/fe-auth-scram.c | 6 +-
src/interfaces/libpq/fe-auth.c | 42 +-
src/interfaces/libpq/fe-auth.h | 3 +
src/interfaces/libpq/fe-connect.c | 38 ++
src/interfaces/libpq/libpq-int.h | 8 +
13 files changed, 980 insertions(+), 19 deletions(-)
create mode 100644 src/include/common/oauth-common.h
create mode 100644 src/interfaces/libpq/fe-auth-oauth.c
diff --git a/configure b/configure
index 7542fe30a1..2ddbe9a1d9 100755
--- a/configure
+++ b/configure
@@ -713,6 +713,7 @@ with_uuid
with_readline
with_systemd
with_selinux
+with_oauth
with_ldap
with_krb_srvnam
krb_srvtab
@@ -856,6 +857,7 @@ with_krb_srvnam
with_pam
with_bsd_auth
with_ldap
+with_oauth
with_bonjour
with_selinux
with_systemd
@@ -1562,6 +1564,7 @@ Optional Packages:
--with-pam build with PAM support
--with-bsd-auth build with BSD Authentication support
--with-ldap build with LDAP support
+ --with-oauth build with OAuth 2.0 support
--with-bonjour build with Bonjour support
--with-selinux build with SELinux support
--with-systemd build with systemd support
@@ -8144,6 +8147,42 @@ $as_echo "$with_ldap" >&6; }
+#
+# OAuth 2.0
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with OAuth support" >&5
+$as_echo_n "checking whether to build with OAuth support... " >&6; }
+
+
+
+# Check whether --with-oauth was given.
+if test "${with_oauth+set}" = set; then :
+ withval=$with_oauth;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_OAUTH 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-oauth option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_oauth=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_oauth" >&5
+$as_echo "$with_oauth" >&6; }
+
+
+
#
# Bonjour
#
@@ -13084,6 +13123,56 @@ fi
+if test "$with_oauth" = yes ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for i_init_session in -liddawc" >&5
+$as_echo_n "checking for i_init_session in -liddawc... " >&6; }
+if ${ac_cv_lib_iddawc_i_init_session+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-liddawc $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char i_init_session ();
+int
+main ()
+{
+return i_init_session ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_iddawc_i_init_session=yes
+else
+ ac_cv_lib_iddawc_i_init_session=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_iddawc_i_init_session" >&5
+$as_echo "$ac_cv_lib_iddawc_i_init_session" >&6; }
+if test "x$ac_cv_lib_iddawc_i_init_session" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBIDDAWC 1
+_ACEOF
+
+ LIBS="-liddawc $LIBS"
+
+else
+ as_fn_error $? "library 'iddawc' is required for OAuth support" "$LINENO" 5
+fi
+
+fi
+
# for contrib/sepgsql
if test "$with_selinux" = yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for security_compute_create_name in -lselinux" >&5
@@ -13978,6 +14067,17 @@ fi
done
+fi
+
+if test "$with_oauth" != no; then
+ ac_fn_c_check_header_mongrel "$LINENO" "iddawc.h" "ac_cv_header_iddawc_h" "$ac_includes_default"
+if test "x$ac_cv_header_iddawc_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <iddawc.h> is required for OAuth" "$LINENO" 5
+fi
+
+
fi
if test "$PORTNAME" = "win32" ; then
diff --git a/configure.ac b/configure.ac
index ed3cdb9a8e..22026476d9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -851,6 +851,17 @@ AC_MSG_RESULT([$with_ldap])
AC_SUBST(with_ldap)
+#
+# OAuth 2.0
+#
+AC_MSG_CHECKING([whether to build with OAuth support])
+PGAC_ARG_BOOL(with, oauth, no,
+ [build with OAuth 2.0 support],
+ [AC_DEFINE([USE_OAUTH], 1, [Define to 1 to build with OAuth 2.0 support. (--with-oauth)])])
+AC_MSG_RESULT([$with_oauth])
+AC_SUBST(with_oauth)
+
+
#
# Bonjour
#
@@ -1321,6 +1332,10 @@ fi
AC_SUBST(LDAP_LIBS_FE)
AC_SUBST(LDAP_LIBS_BE)
+if test "$with_oauth" = yes ; then
+ AC_CHECK_LIB(iddawc, i_init_session, [], [AC_MSG_ERROR([library 'iddawc' is required for OAuth support])])
+fi
+
# for contrib/sepgsql
if test "$with_selinux" = yes; then
AC_CHECK_LIB(selinux, security_compute_create_name, [],
@@ -1531,6 +1546,10 @@ elif test "$with_uuid" = ossp ; then
[AC_MSG_ERROR([header file <ossp/uuid.h> or <uuid.h> is required for OSSP UUID])])])
fi
+if test "$with_oauth" != no; then
+ AC_CHECK_HEADER(iddawc.h, [], [AC_MSG_ERROR([header file <iddawc.h> is required for OAuth])])
+fi
+
if test "$PORTNAME" = "win32" ; then
AC_CHECK_HEADERS(crtdefs.h)
fi
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 6e2f224cc4..d67912711e 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -193,6 +193,7 @@ with_ldap = @with_ldap@
with_libxml = @with_libxml@
with_libxslt = @with_libxslt@
with_llvm = @with_llvm@
+with_oauth = @with_oauth@
with_system_tzdata = @with_system_tzdata@
with_uuid = @with_uuid@
with_zlib = @with_zlib@
diff --git a/src/include/common/oauth-common.h b/src/include/common/oauth-common.h
new file mode 100644
index 0000000000..3fa95ac7e8
--- /dev/null
+++ b/src/include/common/oauth-common.h
@@ -0,0 +1,19 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-common.h
+ * Declarations for helper functions used for OAuth/OIDC authentication
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/oauth-common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef OAUTH_COMMON_H
+#define OAUTH_COMMON_H
+
+/* Name of SASL mechanism per IANA */
+#define OAUTHBEARER_NAME "OAUTHBEARER"
+
+#endif /* OAUTH_COMMON_H */
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 15ffdd895a..f82ab38536 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -331,6 +331,9 @@
/* Define to 1 if you have the `crypto' library (-lcrypto). */
#undef HAVE_LIBCRYPTO
+/* Define to 1 if you have the `iddawc' library (-liddawc). */
+#undef HAVE_LIBIDDAWC
+
/* Define to 1 if you have the `ldap' library (-lldap). */
#undef HAVE_LIBLDAP
@@ -926,6 +929,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 to build with OAuth 2.0 support. (--with-oauth) */
+#undef USE_OAUTH
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 7cbdeb589b..3cdf19294b 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -62,6 +62,11 @@ OBJS += \
fe-secure-gssapi.o
endif
+ifeq ($(with_oauth),yes)
+OBJS += \
+ fe-auth-oauth.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
@@ -83,7 +88,7 @@ endif
# that are built correctly for use in a shlib.
SHLIB_LINK_INTERNAL = -lpgcommon_shlib -lpgport_shlib
ifneq ($(PORTNAME), win32)
-SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
+SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -liddawc -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
else
SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi32 -lssl -lsocket -lnsl -lresolv -lintl -lm $(PTHREAD_LIBS), $(LIBS)) $(LDAP_LIBS_FE)
endif
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
new file mode 100644
index 0000000000..91d2c69f16
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -0,0 +1,745 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-oauth.c
+ * The front-end (client) implementation of OAuth/OIDC authentication.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-auth-oauth.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include <iddawc.h>
+
+#include "postgres_fe.h"
+
+#include "common/base64.h"
+#include "common/hmac.h"
+#include "common/jsonapi.h"
+#include "common/oauth-common.h"
+#include "fe-auth.h"
+#include "mb/pg_wchar.h"
+
+/* The exported OAuth callback mechanism. */
+static void *oauth_init(PGconn *conn, const char *password,
+ const char *sasl_mechanism);
+static void oauth_exchange(void *opaq, bool final,
+ char *input, int inputlen,
+ char **output, int *outputlen,
+ bool *done, bool *success);
+static bool oauth_channel_bound(void *opaq);
+static void oauth_free(void *opaq);
+
+const pg_fe_sasl_mech pg_oauth_mech = {
+ oauth_init,
+ oauth_exchange,
+ oauth_channel_bound,
+ oauth_free,
+};
+
+typedef enum
+{
+ FE_OAUTH_INIT,
+ FE_OAUTH_BEARER_SENT,
+ FE_OAUTH_SERVER_ERROR,
+} fe_oauth_state_enum;
+
+typedef struct
+{
+ fe_oauth_state_enum state;
+
+ PGconn *conn;
+} fe_oauth_state;
+
+static void *
+oauth_init(PGconn *conn, const char *password,
+ const char *sasl_mechanism)
+{
+ fe_oauth_state *state;
+
+ /*
+ * We only support one SASL mechanism here; anything else is programmer
+ * error.
+ */
+ Assert(sasl_mechanism != NULL);
+ Assert(!strcmp(sasl_mechanism, OAUTHBEARER_NAME));
+
+ state = malloc(sizeof(*state));
+ if (!state)
+ return NULL;
+
+ state->state = FE_OAUTH_INIT;
+ state->conn = conn;
+
+ return state;
+}
+
+static const char *
+iddawc_error_string(int errcode)
+{
+ switch (errcode)
+ {
+ case I_OK:
+ return "I_OK";
+
+ case I_ERROR:
+ return "I_ERROR";
+
+ case I_ERROR_PARAM:
+ return "I_ERROR_PARAM";
+
+ case I_ERROR_MEMORY:
+ return "I_ERROR_MEMORY";
+
+ case I_ERROR_UNAUTHORIZED:
+ return "I_ERROR_UNAUTHORIZED";
+
+ case I_ERROR_SERVER:
+ return "I_ERROR_SERVER";
+ }
+
+ return "<unknown>";
+}
+
+static void
+iddawc_error(PGconn *conn, int errcode, const char *msg)
+{
+ appendPQExpBufferStr(&conn->errorMessage, libpq_gettext(msg));
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext(" (iddawc error %s)\n"),
+ iddawc_error_string(errcode));
+}
+
+static void
+iddawc_request_error(PGconn *conn, struct _i_session *i, int err, const char *msg)
+{
+ const char *error_code;
+ const char *desc;
+
+ appendPQExpBuffer(&conn->errorMessage, "%s: ", libpq_gettext(msg));
+
+ error_code = i_get_str_parameter(i, I_OPT_ERROR);
+ if (!error_code)
+ {
+ /*
+ * The server didn't give us any useful information, so just print the
+ * error code.
+ */
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("(iddawc error %s)\n"),
+ iddawc_error_string(err));
+ return;
+ }
+
+ /* If the server gave a string description, print that too. */
+ desc = i_get_str_parameter(i, I_OPT_ERROR_DESCRIPTION);
+ if (desc)
+ appendPQExpBuffer(&conn->errorMessage, "%s ", desc);
+
+ appendPQExpBuffer(&conn->errorMessage, "(%s)\n", error_code);
+}
+
+static char *
+get_auth_token(PGconn *conn)
+{
+ PQExpBuffer token_buf = NULL;
+ struct _i_session session;
+ int err;
+ int auth_method;
+ bool user_prompted = false;
+ const char *verification_uri;
+ const char *user_code;
+ const char *access_token;
+ const char *token_type;
+ char *token = NULL;
+
+ if (!conn->oauth_discovery_uri)
+ return strdup(""); /* ask the server for one */
+
+ i_init_session(&session);
+
+ if (!conn->oauth_client_id)
+ {
+ /* We can't talk to a server without a client identifier. */
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("no oauth_client_id is set for the connection"));
+ goto cleanup;
+ }
+
+ token_buf = createPQExpBuffer();
+
+ if (!token_buf)
+ goto cleanup;
+
+ err = i_set_str_parameter(&session, I_OPT_OPENID_CONFIG_ENDPOINT, conn->oauth_discovery_uri);
+ if (err)
+ {
+ iddawc_error(conn, err, "failed to set OpenID config endpoint");
+ goto cleanup;
+ }
+
+ err = i_get_openid_config(&session);
+ if (err)
+ {
+ iddawc_error(conn, err, "failed to fetch OpenID discovery document");
+ goto cleanup;
+ }
+
+ if (!i_get_str_parameter(&session, I_OPT_TOKEN_ENDPOINT))
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("issuer has no token endpoint"));
+ goto cleanup;
+ }
+
+ if (!i_get_str_parameter(&session, I_OPT_DEVICE_AUTHORIZATION_ENDPOINT))
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("issuer does not support device authorization"));
+ goto cleanup;
+ }
+
+ err = i_set_response_type(&session, I_RESPONSE_TYPE_DEVICE_CODE);
+ if (err)
+ {
+ iddawc_error(conn, err, "failed to set device code response type");
+ goto cleanup;
+ }
+
+ auth_method = I_TOKEN_AUTH_METHOD_NONE;
+ if (conn->oauth_client_secret && *conn->oauth_client_secret)
+ auth_method = I_TOKEN_AUTH_METHOD_SECRET_BASIC;
+
+ err = i_set_parameter_list(&session,
+ I_OPT_CLIENT_ID, conn->oauth_client_id,
+ I_OPT_CLIENT_SECRET, conn->oauth_client_secret,
+ I_OPT_TOKEN_METHOD, auth_method,
+ I_OPT_SCOPE, conn->oauth_scope,
+ I_OPT_NONE
+ );
+ if (err)
+ {
+ iddawc_error(conn, err, "failed to set client identifier");
+ goto cleanup;
+ }
+
+ err = i_run_device_auth_request(&session);
+ if (err)
+ {
+ iddawc_request_error(conn, &session, err,
+ "failed to obtain device authorization");
+ goto cleanup;
+ }
+
+ verification_uri = i_get_str_parameter(&session, I_OPT_DEVICE_AUTH_VERIFICATION_URI);
+ if (!verification_uri)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("issuer did not provide a verification URI"));
+ goto cleanup;
+ }
+
+ user_code = i_get_str_parameter(&session, I_OPT_DEVICE_AUTH_USER_CODE);
+ if (!user_code)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("issuer did not provide a user code"));
+ goto cleanup;
+ }
+
+ /*
+ * Poll the token endpoint until either the user logs in and authorizes the
+ * use of a token, or a hard failure occurs. We perform one ping _before_
+ * prompting the user, so that we don't make them do the work of logging in
+ * only to find that the token endpoint is completely unreachable.
+ */
+ err = i_run_token_request(&session);
+ while (err)
+ {
+ const char *error_code;
+ uint interval;
+
+ error_code = i_get_str_parameter(&session, I_OPT_ERROR);
+
+ /*
+ * authorization_pending and slow_down are the only acceptable errors;
+ * anything else and we bail.
+ */
+ if (!error_code || (strcmp(error_code, "authorization_pending")
+ && strcmp(error_code, "slow_down")))
+ {
+ iddawc_request_error(conn, &session, err,
+ "OAuth token retrieval failed");
+ goto cleanup;
+ }
+
+ if (!user_prompted)
+ {
+ /*
+ * Now that we know the token endpoint isn't broken, give the user
+ * the login instructions.
+ */
+ pqInternalNotice(&conn->noticeHooks,
+ "Visit %s and enter the code: %s",
+ verification_uri, user_code);
+
+ user_prompted = true;
+ }
+
+ /*
+ * We are required to wait between polls; the server tells us how long.
+ * TODO: if interval's not set, we need to default to five seconds
+ * TODO: sanity check the interval
+ */
+ interval = i_get_int_parameter(&session, I_OPT_DEVICE_AUTH_INTERVAL);
+
+ /*
+ * A slow_down error requires us to permanently increase our retry
+ * interval by five seconds. RFC 8628, Sec. 3.5.
+ */
+ if (!strcmp(error_code, "slow_down"))
+ {
+ interval += 5;
+ i_set_int_parameter(&session, I_OPT_DEVICE_AUTH_INTERVAL, interval);
+ }
+
+ sleep(interval);
+
+ /*
+ * XXX Reset the error code before every call, because iddawc won't do
+ * that for us. This matters if the server first sends a "pending" error
+ * code, then later hard-fails without sending an error code to
+ * overwrite the first one.
+ *
+ * That we have to do this at all seems like a bug in iddawc.
+ */
+ i_set_str_parameter(&session, I_OPT_ERROR, NULL);
+
+ err = i_run_token_request(&session);
+ }
+
+ access_token = i_get_str_parameter(&session, I_OPT_ACCESS_TOKEN);
+ token_type = i_get_str_parameter(&session, I_OPT_TOKEN_TYPE);
+
+ if (!access_token || !token_type || strcasecmp(token_type, "Bearer"))
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("issuer did not provide a bearer token"));
+ goto cleanup;
+ }
+
+ appendPQExpBufferStr(token_buf, "Bearer ");
+ appendPQExpBufferStr(token_buf, access_token);
+
+ if (PQExpBufferBroken(token_buf))
+ goto cleanup;
+
+ token = strdup(token_buf->data);
+
+cleanup:
+ if (token_buf)
+ destroyPQExpBuffer(token_buf);
+ i_clean_session(&session);
+
+ return token;
+}
+
+#define kvsep "\x01"
+
+static char *
+client_initial_response(PGconn *conn)
+{
+ static const char * const resp_format = "n,," kvsep "auth=%s" kvsep kvsep;
+
+ PQExpBuffer token_buf;
+ PQExpBuffer discovery_buf = NULL;
+ char *token = NULL;
+ char *response = NULL;
+
+ token_buf = createPQExpBuffer();
+ if (!token_buf)
+ goto cleanup;
+
+ /*
+ * If we don't yet have a discovery URI, but the user gave us an explicit
+ * issuer, use the .well-known discovery URI for that issuer.
+ */
+ if (!conn->oauth_discovery_uri && conn->oauth_issuer)
+ {
+ discovery_buf = createPQExpBuffer();
+ if (!discovery_buf)
+ goto cleanup;
+
+ appendPQExpBufferStr(discovery_buf, conn->oauth_issuer);
+ appendPQExpBufferStr(discovery_buf, "/.well-known/openid-configuration");
+
+ if (PQExpBufferBroken(discovery_buf))
+ goto cleanup;
+
+ conn->oauth_discovery_uri = strdup(discovery_buf->data);
+ }
+
+ token = get_auth_token(conn);
+ if (!token)
+ goto cleanup;
+
+ appendPQExpBuffer(token_buf, resp_format, token);
+ if (PQExpBufferBroken(token_buf))
+ goto cleanup;
+
+ response = strdup(token_buf->data);
+
+cleanup:
+ if (token)
+ free(token);
+ if (discovery_buf)
+ destroyPQExpBuffer(discovery_buf);
+ if (token_buf)
+ destroyPQExpBuffer(token_buf);
+
+ return response;
+}
+
+#define ERROR_STATUS_FIELD "status"
+#define ERROR_SCOPE_FIELD "scope"
+#define ERROR_OPENID_CONFIGURATION_FIELD "openid-configuration"
+
+struct json_ctx
+{
+ char *errmsg; /* any non-NULL value stops all processing */
+ PQExpBufferData errbuf; /* backing memory for errmsg */
+ int nested; /* nesting level (zero is the top) */
+
+ const char *target_field_name; /* points to a static allocation */
+ char **target_field; /* see below */
+
+ /* target_field, if set, points to one of the following: */
+ char *status;
+ char *scope;
+ char *discovery_uri;
+};
+
+#define oauth_json_has_error(ctx) \
+ (PQExpBufferDataBroken((ctx)->errbuf) || (ctx)->errmsg)
+
+#define oauth_json_set_error(ctx, ...) \
+ do { \
+ appendPQExpBuffer(&(ctx)->errbuf, __VA_ARGS__); \
+ (ctx)->errmsg = (ctx)->errbuf.data; \
+ } while (0)
+
+static void
+oauth_json_object_start(void *state)
+{
+ struct json_ctx *ctx = state;
+
+ if (oauth_json_has_error(ctx))
+ return; /* short-circuit */
+
+ if (ctx->target_field)
+ {
+ Assert(ctx->nested == 1);
+
+ oauth_json_set_error(ctx,
+ libpq_gettext("field \"%s\" must be a string"),
+ ctx->target_field_name);
+ }
+
+ ++ctx->nested;
+}
+
+static void
+oauth_json_object_end(void *state)
+{
+ struct json_ctx *ctx = state;
+
+ if (oauth_json_has_error(ctx))
+ return; /* short-circuit */
+
+ --ctx->nested;
+}
+
+static void
+oauth_json_object_field_start(void *state, char *name, bool isnull)
+{
+ struct json_ctx *ctx = state;
+
+ if (oauth_json_has_error(ctx))
+ {
+ /* short-circuit */
+ free(name);
+ return;
+ }
+
+ if (ctx->nested == 1)
+ {
+ if (!strcmp(name, ERROR_STATUS_FIELD))
+ {
+ ctx->target_field_name = ERROR_STATUS_FIELD;
+ ctx->target_field = &ctx->status;
+ }
+ else if (!strcmp(name, ERROR_SCOPE_FIELD))
+ {
+ ctx->target_field_name = ERROR_SCOPE_FIELD;
+ ctx->target_field = &ctx->scope;
+ }
+ else if (!strcmp(name, ERROR_OPENID_CONFIGURATION_FIELD))
+ {
+ ctx->target_field_name = ERROR_OPENID_CONFIGURATION_FIELD;
+ ctx->target_field = &ctx->discovery_uri;
+ }
+ }
+
+ free(name);
+}
+
+static void
+oauth_json_array_start(void *state)
+{
+ struct json_ctx *ctx = state;
+
+ if (oauth_json_has_error(ctx))
+ return; /* short-circuit */
+
+ if (!ctx->nested)
+ {
+ ctx->errmsg = libpq_gettext("top-level element must be an object");
+ }
+ else if (ctx->target_field)
+ {
+ Assert(ctx->nested == 1);
+
+ oauth_json_set_error(ctx,
+ libpq_gettext("field \"%s\" must be a string"),
+ ctx->target_field_name);
+ }
+}
+
+static void
+oauth_json_scalar(void *state, char *token, JsonTokenType type)
+{
+ struct json_ctx *ctx = state;
+
+ if (oauth_json_has_error(ctx))
+ {
+ /* short-circuit */
+ free(token);
+ return;
+ }
+
+ if (!ctx->nested)
+ {
+ ctx->errmsg = libpq_gettext("top-level element must be an object");
+ }
+ else if (ctx->target_field)
+ {
+ Assert(ctx->nested == 1);
+
+ if (type == JSON_TOKEN_STRING)
+ {
+ *ctx->target_field = token;
+
+ ctx->target_field = NULL;
+ ctx->target_field_name = NULL;
+
+ return; /* don't free the token we're using */
+ }
+
+ oauth_json_set_error(ctx,
+ libpq_gettext("field \"%s\" must be a string"),
+ ctx->target_field_name);
+ }
+
+ free(token);
+}
+
+static bool
+handle_oauth_sasl_error(PGconn *conn, char *msg, int msglen)
+{
+ JsonLexContext lex = {0};
+ JsonSemAction sem = {0};
+ JsonParseErrorType err;
+ struct json_ctx ctx = {0};
+ char *errmsg = NULL;
+
+ /* Sanity check. */
+ if (strlen(msg) != msglen)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("server's error message contained an embedded NULL"));
+ return false;
+ }
+
+ initJsonLexContextCstringLen(&lex, msg, msglen, PG_UTF8, true);
+
+ initPQExpBuffer(&ctx.errbuf);
+ sem.semstate = &ctx;
+
+ sem.object_start = oauth_json_object_start;
+ sem.object_end = oauth_json_object_end;
+ sem.object_field_start = oauth_json_object_field_start;
+ sem.array_start = oauth_json_array_start;
+ sem.scalar = oauth_json_scalar;
+
+ err = pg_parse_json(&lex, &sem);
+
+ if (err != JSON_SUCCESS)
+ {
+ errmsg = json_errdetail(err, &lex);
+ }
+ else if (PQExpBufferDataBroken(ctx.errbuf))
+ {
+ errmsg = libpq_gettext("out of memory");
+ }
+ else if (ctx.errmsg)
+ {
+ errmsg = ctx.errmsg;
+ }
+
+ if (errmsg)
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("failed to parse server's error response: %s"),
+ errmsg);
+
+ /* Don't need the error buffer or the JSON lexer anymore. */
+ termPQExpBuffer(&ctx.errbuf);
+ termJsonLexContext(&lex);
+
+ if (errmsg)
+ return false;
+
+ /* TODO: what if these override what the user already specified? */
+ if (ctx.discovery_uri)
+ {
+ if (conn->oauth_discovery_uri)
+ free(conn->oauth_discovery_uri);
+
+ conn->oauth_discovery_uri = ctx.discovery_uri;
+ }
+
+ if (ctx.scope)
+ {
+ if (conn->oauth_scope)
+ free(conn->oauth_scope);
+
+ conn->oauth_scope = ctx.scope;
+ }
+ /* TODO: missing error scope should clear any existing connection scope */
+
+ if (!ctx.status)
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("server sent error response without a status"));
+ return false;
+ }
+
+ if (!strcmp(ctx.status, "invalid_token"))
+ {
+ /*
+ * invalid_token is the only error code we'll automatically retry for,
+ * but only if we have enough information to do so.
+ */
+ if (conn->oauth_discovery_uri)
+ conn->oauth_want_retry = true;
+ }
+ /* TODO: include status in hard failure message */
+
+ return true;
+}
+
+static void
+oauth_exchange(void *opaq, bool final,
+ char *input, int inputlen,
+ char **output, int *outputlen,
+ bool *done, bool *success)
+{
+ fe_oauth_state *state = opaq;
+ PGconn *conn = state->conn;
+
+ *done = false;
+ *success = false;
+ *output = NULL;
+ *outputlen = 0;
+
+ switch (state->state)
+ {
+ case FE_OAUTH_INIT:
+ Assert(inputlen == -1);
+
+ *output = client_initial_response(conn);
+ if (!*output)
+ goto error;
+
+ *outputlen = strlen(*output);
+ state->state = FE_OAUTH_BEARER_SENT;
+
+ break;
+
+ case FE_OAUTH_BEARER_SENT:
+ if (final)
+ {
+ /* TODO: ensure there is no message content here. */
+ *done = true;
+ *success = true;
+
+ break;
+ }
+
+ /*
+ * Error message sent by the server.
+ */
+ if (!handle_oauth_sasl_error(conn, input, inputlen))
+ goto error;
+
+ /*
+ * Respond with the required dummy message (RFC 7628, sec. 3.2.3).
+ */
+ *output = strdup(kvsep);
+ *outputlen = strlen(*output); /* == 1 */
+
+ state->state = FE_OAUTH_SERVER_ERROR;
+ break;
+
+ case FE_OAUTH_SERVER_ERROR:
+ /*
+ * After an error, the server should send an error response to fail
+ * the SASL handshake, which is handled in higher layers.
+ *
+ * If we get here, the server either sent *another* challenge which
+ * isn't defined in the RFC, or completed the handshake successfully
+ * after telling us it was going to fail. Neither is acceptable.
+ */
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("server sent additional OAuth data after error\n"));
+ goto error;
+
+ default:
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("invalid OAuth exchange state\n"));
+ goto error;
+ }
+
+ return;
+
+error:
+ *done = true;
+ *success = false;
+}
+
+static bool
+oauth_channel_bound(void *opaq)
+{
+ /* This mechanism does not support channel binding. */
+ return false;
+}
+
+static void
+oauth_free(void *opaq)
+{
+ fe_oauth_state *state = opaq;
+
+ free(state);
+}
diff --git a/src/interfaces/libpq/fe-auth-sasl.h b/src/interfaces/libpq/fe-auth-sasl.h
index 3d7ee576f2..0920102908 100644
--- a/src/interfaces/libpq/fe-auth-sasl.h
+++ b/src/interfaces/libpq/fe-auth-sasl.h
@@ -65,6 +65,8 @@ typedef struct pg_fe_sasl_mech
*
* state: The opaque mechanism state returned by init()
*
+ * final: true if the server has sent a final exchange outcome
+ *
* input: The challenge data sent by the server, or NULL when
* generating a client-first initial response (that is, when
* the server expects the client to send a message to start
@@ -92,7 +94,8 @@ typedef struct pg_fe_sasl_mech
* Ignored if *done is false.
*--------
*/
- void (*exchange) (void *state, char *input, int inputlen,
+ void (*exchange) (void *state, bool final,
+ char *input, int inputlen,
char **output, int *outputlen,
bool *done, bool *success);
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
index 4337e89ce9..489cbeda50 100644
--- a/src/interfaces/libpq/fe-auth-scram.c
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -24,7 +24,8 @@
/* The exported SCRAM callback mechanism. */
static void *scram_init(PGconn *conn, const char *password,
const char *sasl_mechanism);
-static void scram_exchange(void *opaq, char *input, int inputlen,
+static void scram_exchange(void *opaq, bool final,
+ char *input, int inputlen,
char **output, int *outputlen,
bool *done, bool *success);
static bool scram_channel_bound(void *opaq);
@@ -205,7 +206,8 @@ scram_free(void *opaq)
* Exchange a SCRAM message with backend.
*/
static void
-scram_exchange(void *opaq, char *input, int inputlen,
+scram_exchange(void *opaq, bool final,
+ char *input, int inputlen,
char **output, int *outputlen,
bool *done, bool *success)
{
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 3421ed4685..0b5b91962a 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -39,6 +39,7 @@
#endif
#include "common/md5.h"
+#include "common/oauth-common.h"
#include "common/scram-common.h"
#include "fe-auth.h"
#include "fe-auth-sasl.h"
@@ -423,7 +424,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
bool success;
const char *selected_mechanism;
PQExpBufferData mechanism_buf;
- char *password;
+ char *password = NULL;
initPQExpBuffer(&mechanism_buf);
@@ -445,8 +446,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
/*
* Parse the list of SASL authentication mechanisms in the
* AuthenticationSASL message, and select the best mechanism that we
- * support. SCRAM-SHA-256-PLUS and SCRAM-SHA-256 are the only ones
- * supported at the moment, listed by order of decreasing importance.
+ * support. Mechanisms are listed by order of decreasing importance.
*/
selected_mechanism = NULL;
for (;;)
@@ -486,6 +486,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
{
selected_mechanism = SCRAM_SHA_256_PLUS_NAME;
conn->sasl = &pg_scram_mech;
+ conn->password_needed = true;
}
#else
/*
@@ -523,7 +524,17 @@ pg_SASL_init(PGconn *conn, int payloadlen)
{
selected_mechanism = SCRAM_SHA_256_NAME;
conn->sasl = &pg_scram_mech;
+ conn->password_needed = true;
}
+#ifdef USE_OAUTH
+ else if (strcmp(mechanism_buf.data, OAUTHBEARER_NAME) == 0 &&
+ !selected_mechanism)
+ {
+ selected_mechanism = OAUTHBEARER_NAME;
+ conn->sasl = &pg_oauth_mech;
+ conn->password_needed = false;
+ }
+#endif
}
if (!selected_mechanism)
@@ -548,18 +559,19 @@ pg_SASL_init(PGconn *conn, int payloadlen)
/*
* First, select the password to use for the exchange, complaining if
- * there isn't one. Currently, all supported SASL mechanisms require a
- * password, so we can just go ahead here without further distinction.
+ * there isn't one and the SASL mechanism needs it.
*/
- conn->password_needed = true;
- password = conn->connhost[conn->whichhost].password;
- if (password == NULL)
- password = conn->pgpass;
- if (password == NULL || password[0] == '\0')
+ if (conn->password_needed)
{
- appendPQExpBufferStr(&conn->errorMessage,
- PQnoPasswordSupplied);
- goto error;
+ password = conn->connhost[conn->whichhost].password;
+ if (password == NULL)
+ password = conn->pgpass;
+ if (password == NULL || password[0] == '\0')
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ PQnoPasswordSupplied);
+ goto error;
+ }
}
Assert(conn->sasl);
@@ -577,7 +589,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
goto oom_error;
/* Get the mechanism-specific Initial Client Response, if any */
- conn->sasl->exchange(conn->sasl_state,
+ conn->sasl->exchange(conn->sasl_state, false,
NULL, -1,
&initialresponse, &initialresponselen,
&done, &success);
@@ -658,7 +670,7 @@ pg_SASL_continue(PGconn *conn, int payloadlen, bool final)
/* For safety and convenience, ensure the buffer is NULL-terminated. */
challenge[payloadlen] = '\0';
- conn->sasl->exchange(conn->sasl_state,
+ conn->sasl->exchange(conn->sasl_state, final,
challenge, payloadlen,
&output, &outputlen,
&done, &success);
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index 63927480ee..03bea124a6 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -26,4 +26,7 @@ extern char *pg_fe_getauthname(PQExpBuffer errorMessage);
extern const pg_fe_sasl_mech pg_scram_mech;
extern char *pg_fe_scram_build_secret(const char *password);
+/* Mechanisms in fe-auth-oauth.c */
+extern const pg_fe_sasl_mech pg_oauth_mech;
+
#endif /* FE_AUTH_H */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 49eec3e835..ba9c097060 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,23 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ /* OAuth v2 */
+ {"oauth_issuer", NULL, NULL, NULL,
+ "OAuth-Issuer", "", 40,
+ offsetof(struct pg_conn, oauth_issuer)},
+
+ {"oauth_client_id", NULL, NULL, NULL,
+ "OAuth-Client-ID", "", 40,
+ offsetof(struct pg_conn, oauth_client_id)},
+
+ {"oauth_client_secret", NULL, NULL, NULL,
+ "OAuth-Client-Secret", "", 40,
+ offsetof(struct pg_conn, oauth_client_secret)},
+
+ {"oauth_scope", NULL, NULL, NULL,
+ "OAuth-Scope", "", 15,
+ offsetof(struct pg_conn, oauth_scope)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
@@ -606,6 +623,7 @@ pqDropServerData(PGconn *conn)
conn->write_err_msg = NULL;
conn->be_pid = 0;
conn->be_key = 0;
+ /* conn->oauth_want_retry = false; TODO */
}
@@ -3355,6 +3373,16 @@ keep_going: /* We will come back to here until there is
/* Check to see if we should mention pgpassfile */
pgpassfileWarning(conn);
+#ifdef USE_OAUTH
+ if (conn->sasl == &pg_oauth_mech
+ && conn->oauth_want_retry)
+ {
+ /* TODO: only allow retry once */
+ need_new_connection = true;
+ goto keep_going;
+ }
+#endif
+
#ifdef ENABLE_GSS
/*
@@ -4129,6 +4157,16 @@ freePGconn(PGconn *conn)
free(conn->rowBuf);
if (conn->target_session_attrs)
free(conn->target_session_attrs);
+ if (conn->oauth_issuer)
+ free(conn->oauth_issuer);
+ if (conn->oauth_discovery_uri)
+ free(conn->oauth_discovery_uri);
+ if (conn->oauth_client_id)
+ free(conn->oauth_client_id);
+ if (conn->oauth_client_secret)
+ free(conn->oauth_client_secret);
+ if (conn->oauth_scope)
+ free(conn->oauth_scope);
termPQExpBuffer(&conn->errorMessage);
termPQExpBuffer(&conn->workBuffer);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 490458adef..3d20482550 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -394,6 +394,14 @@ struct pg_conn
char *ssl_max_protocol_version; /* maximum TLS protocol version */
char *target_session_attrs; /* desired session properties */
+ /* OAuth v2 */
+ char *oauth_issuer; /* token issuer URL */
+ char *oauth_discovery_uri; /* URI of the issuer's discovery document */
+ char *oauth_client_id; /* client identifier */
+ char *oauth_client_secret; /* client secret */
+ char *oauth_scope; /* access token scope */
+ bool oauth_want_retry; /* should we retry on failure? */
+
/* Optional file to write trace info to */
FILE *Pfdebug;
int traceFlags;
--
2.25.1
[text/x-patch] v2-0003-backend-add-OAUTHBEARER-SASL-mechanism.patch (38.6K, ../../[email protected]/4-v2-0003-backend-add-OAUTHBEARER-SASL-mechanism.patch)
download | inline diff:
From d09a00b4d52a5ed578ee5cd7623108ebdd12f202 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Tue, 4 May 2021 16:21:11 -0700
Subject: [PATCH v2 3/5] backend: add OAUTHBEARER SASL mechanism
DO NOT USE THIS PROOF OF CONCEPT IN PRODUCTION.
Implement OAUTHBEARER (RFC 7628) on the server side. This adds a new
auth method, oauth, to pg_hba.
Because OAuth implementations vary so wildly, and bearer token
validation is heavily dependent on the issuing party, authn/z is done by
communicating with an external program: the oauth_validator_command.
This command must do the following:
1. Receive the bearer token by reading its contents from a file
descriptor passed from the server. (The numeric value of this
descriptor may be inserted into the oauth_validator_command using the
%f specifier.)
This MUST be the first action the command performs. The server will
not begin reading stdout from the command until the token has been
read in full, so if the command tries to print anything and hits a
buffer limit, the backend will deadlock and time out.
2. Validate the bearer token. The correct way to do this depends on the
issuer, but it generally involves either cryptographic operations to
prove that the token was issued by a trusted party, or the
presentation of the bearer token to some other party so that _it_ can
perform validation.
The command MUST maintain confidentiality of the bearer token, since
in most cases it can be used just like a password. (There are ways to
cryptographically bind tokens to client certificates, but they are
way beyond the scope of this commit message.)
If the token cannot be validated, the command must exit with a
non-zero status. Further authentication/authorization is pointless if
the bearer token wasn't issued by someone you trust.
3. Authenticate the user, authorize the user, or both:
a. To authenticate the user, use the bearer token to retrieve some
trusted identifier string for the end user. The exact process for
this is, again, issuer-dependent. The command should print the
authenticated identity string to stdout, followed by a newline.
If the user cannot be authenticated, the validator should not
print anything to stdout. It should also exit with a non-zero
status, unless the token may be used to authorize the connection
through some other means (see below).
On a success, the command may then exit with a zero success code.
By default, the server will then check to make sure the identity
string matches the role that is being used (or matches a usermap
entry, if one is in use).
b. To optionally authorize the user, in combination with the HBA
option trust_validator_authz=1 (see below), the validator simply
returns a zero exit code if the client should be allowed to
connect with its presented role (which can be passed to the
command using the %r specifier), or a non-zero code otherwise.
The hard part is in determining whether the given token truly
authorizes the client to use the given role, which must
unfortunately be left as an exercise to the reader.
This obviously requires some care, as a poorly implemented token
validator may silently open the entire database to anyone with a
bearer token. But it may be a more portable approach, since OAuth
is designed as an authorization framework, not an authentication
framework. For example, the user's bearer token could carry an
"allow_superuser_access" claim, which would authorize pseudonymous
database access as any role. It's then up to the OAuth system
administrators to ensure that allow_superuser_access is doled out
only to the proper users.
c. It's possible that the user can be successfully authenticated but
isn't authorized to connect. In this case, the command may print
the authenticated ID and then fail with a non-zero exit code.
(This makes it easier to see what's going on in the Postgres
logs.)
4. Token validators may optionally log to stderr. This will be printed
verbatim into the Postgres server logs.
The oauth method supports the following HBA options (but note that two
of them are not optional, since we have no way of choosing sensible
defaults):
issuer: Required. The URL of the OAuth issuing party, which the client
must contact to receive a bearer token.
Some real-world examples as of time of writing:
- https://accounts.google.com
- https://login.microsoft.com/[tenant-id]/v2.0
scope: Required. The OAuth scope(s) required for the server to
authenticate and/or authorize the user. This is heavily
deployment-specific, but a simple example is "openid email".
map: Optional. Specify a standard PostgreSQL user map; this works
the same as with other auth methods such as peer. If a map is
not specified, the user ID returned by the token validator
must exactly match the role that's being requested (but see
trust_validator_authz, below).
trust_validator_authz:
Optional. When set to 1, this allows the token validator to
take full control of the authorization process. Standard user
mapping is skipped: if the validator command succeeds, the
client is allowed to connect under its desired role and no
further checks are done.
Unlike the client, servers support OAuth without needing to be built
against libiddawc (since the responsibility for "speaking" OAuth/OIDC
correctly is delegated entirely to the oauth_validator_command).
Several TODOs:
- port to platforms other than "modern Linux"
- overhaul the communication with oauth_validator_command, which is
currently a bad hack on OpenPipeStream()
- implement more sanity checks on the OAUTHBEARER message format and
tokens sent by the client
- implement more helpful handling of HBA misconfigurations
- properly interpolate JSON when generating error responses
- use logdetail during auth failures
- deal with role names that can't be safely passed to system() without
shell-escaping
- allow passing the configured issuer to the oauth_validator_command, to
deal with multi-issuer setups
- ...and more.
---
src/backend/libpq/Makefile | 1 +
src/backend/libpq/auth-oauth.c | 797 +++++++++++++++++++++++++++++++++
src/backend/libpq/auth-sasl.c | 10 +-
src/backend/libpq/auth-scram.c | 4 +-
src/backend/libpq/auth.c | 26 +-
src/backend/libpq/hba.c | 29 +-
src/backend/utils/misc/guc.c | 12 +
src/include/libpq/auth.h | 17 +
src/include/libpq/hba.h | 8 +-
src/include/libpq/oauth.h | 24 +
src/include/libpq/sasl.h | 11 +
11 files changed, 907 insertions(+), 32 deletions(-)
create mode 100644 src/backend/libpq/auth-oauth.c
create mode 100644 src/include/libpq/oauth.h
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a4..98eb2a8242 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -15,6 +15,7 @@ include $(top_builddir)/src/Makefile.global
# be-fsstubs is here for historical reasons, probably belongs elsewhere
OBJS = \
+ auth-oauth.o \
auth-sasl.o \
auth-scram.o \
auth.o \
diff --git a/src/backend/libpq/auth-oauth.c b/src/backend/libpq/auth-oauth.c
new file mode 100644
index 0000000000..c47211132c
--- /dev/null
+++ b/src/backend/libpq/auth-oauth.c
@@ -0,0 +1,797 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-oauth.c
+ * Server-side implementation of the SASL OAUTHBEARER mechanism.
+ *
+ * See the following RFC for more details:
+ * - RFC 7628: https://tools.ietf.org/html/rfc7628
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/libpq/auth-oauth.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "common/oauth-common.h"
+#include "lib/stringinfo.h"
+#include "libpq/auth.h"
+#include "libpq/hba.h"
+#include "libpq/oauth.h"
+#include "libpq/sasl.h"
+#include "storage/fd.h"
+
+/* GUC */
+char *oauth_validator_command;
+
+static void oauth_get_mechanisms(Port *port, StringInfo buf);
+static void *oauth_init(Port *port, const char *selected_mech, const char *shadow_pass);
+static int oauth_exchange(void *opaq, const char *input, int inputlen,
+ char **output, int *outputlen, char **logdetail);
+
+/* Mechanism declaration */
+const pg_be_sasl_mech pg_be_oauth_mech = {
+ oauth_get_mechanisms,
+ oauth_init,
+ oauth_exchange,
+
+ PG_MAX_AUTH_TOKEN_LENGTH,
+};
+
+
+typedef enum
+{
+ OAUTH_STATE_INIT = 0,
+ OAUTH_STATE_ERROR,
+ OAUTH_STATE_FINISHED,
+} oauth_state;
+
+struct oauth_ctx
+{
+ oauth_state state;
+ Port *port;
+ const char *issuer;
+ const char *scope;
+};
+
+static char *sanitize_char(char c);
+static char *parse_kvpairs_for_auth(char **input);
+static void generate_error_response(struct oauth_ctx *ctx, char **output, int *outputlen);
+static bool validate(Port *port, const char *auth, char **logdetail);
+static bool run_validator_command(Port *port, const char *token);
+static bool check_exit(FILE **fh, const char *command);
+static bool unset_cloexec(int fd);
+static bool username_ok_for_shell(const char *username);
+
+#define KVSEP 0x01
+#define AUTH_KEY "auth"
+#define BEARER_SCHEME "Bearer "
+
+static void
+oauth_get_mechanisms(Port *port, StringInfo buf)
+{
+ /* Only OAUTHBEARER is supported. */
+ appendStringInfoString(buf, OAUTHBEARER_NAME);
+ appendStringInfoChar(buf, '\0');
+}
+
+static void *
+oauth_init(Port *port, const char *selected_mech, const char *shadow_pass)
+{
+ struct oauth_ctx *ctx;
+
+ if (strcmp(selected_mech, OAUTHBEARER_NAME))
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("client selected an invalid SASL authentication mechanism")));
+
+ ctx = palloc0(sizeof(*ctx));
+
+ ctx->state = OAUTH_STATE_INIT;
+ ctx->port = port;
+
+ Assert(port->hba);
+ ctx->issuer = port->hba->oauth_issuer;
+ ctx->scope = port->hba->oauth_scope;
+
+ return ctx;
+}
+
+static int
+oauth_exchange(void *opaq, const char *input, int inputlen,
+ char **output, int *outputlen, char **logdetail)
+{
+ char *p;
+ char cbind_flag;
+ char *auth;
+
+ struct oauth_ctx *ctx = opaq;
+
+ *output = NULL;
+ *outputlen = -1;
+
+ /*
+ * If the client didn't include an "Initial Client Response" in the
+ * SASLInitialResponse message, send an empty challenge, to which the
+ * client will respond with the same data that usually comes in the
+ * Initial Client Response.
+ */
+ if (input == NULL)
+ {
+ Assert(ctx->state == OAUTH_STATE_INIT);
+
+ *output = pstrdup("");
+ *outputlen = 0;
+ return PG_SASL_EXCHANGE_CONTINUE;
+ }
+
+ /*
+ * Check that the input length agrees with the string length of the input.
+ */
+ if (inputlen == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("The message is empty.")));
+ if (inputlen != strlen(input))
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message length does not match input length.")));
+
+ switch (ctx->state)
+ {
+ case OAUTH_STATE_INIT:
+ /* Handle this case below. */
+ break;
+
+ case OAUTH_STATE_ERROR:
+ /*
+ * Only one response is valid for the client during authentication
+ * failure: a single kvsep.
+ */
+ if (inputlen != 1 || *input != KVSEP)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Client did not send a kvsep response.")));
+
+ /* The (failed) handshake is now complete. */
+ ctx->state = OAUTH_STATE_FINISHED;
+ return PG_SASL_EXCHANGE_FAILURE;
+
+ default:
+ elog(ERROR, "invalid OAUTHBEARER exchange state");
+ return PG_SASL_EXCHANGE_FAILURE;
+ }
+
+ /* Handle the client's initial message. */
+ p = pstrdup(input);
+
+ /*
+ * OAUTHBEARER does not currently define a channel binding (so there is no
+ * OAUTHBEARER-PLUS, and we do not accept a 'p' specifier). We accept a 'y'
+ * specifier purely for the remote chance that a future specification could
+ * define one; then future clients can still interoperate with this server
+ * implementation. 'n' is the expected case.
+ */
+ cbind_flag = *p;
+ switch (cbind_flag)
+ {
+ case 'p':
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("The server does not support channel binding for OAuth, but the client message includes channel binding data.")));
+ break;
+
+ case 'y': /* fall through */
+ case 'n':
+ p++;
+ if (*p != ',')
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Comma expected, but found character %s.",
+ sanitize_char(*p))));
+ p++;
+ break;
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Unexpected channel-binding flag %s.",
+ sanitize_char(cbind_flag))));
+ }
+
+ /*
+ * Forbid optional authzid (authorization identity). We don't support it.
+ */
+ if (*p == 'a')
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("client uses authorization identity, but it is not supported")));
+ if (*p != ',')
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Unexpected attribute %s in client-first-message.",
+ sanitize_char(*p))));
+ p++;
+
+ /* All remaining fields are separated by the RFC's kvsep (\x01). */
+ if (*p != KVSEP)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Key-value separator expected, but found character %s.",
+ sanitize_char(*p))));
+ p++;
+
+ auth = parse_kvpairs_for_auth(&p);
+ if (!auth)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message does not contain an auth value.")));
+
+ /* We should be at the end of our message. */
+ if (*p)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message contains additional data after the final terminator.")));
+
+ if (!validate(ctx->port, auth, logdetail))
+ {
+ generate_error_response(ctx, output, outputlen);
+
+ ctx->state = OAUTH_STATE_ERROR;
+ return PG_SASL_EXCHANGE_CONTINUE;
+ }
+
+ ctx->state = OAUTH_STATE_FINISHED;
+ return PG_SASL_EXCHANGE_SUCCESS;
+}
+
+/*
+ * Convert an arbitrary byte to printable form. For error messages.
+ *
+ * If it's a printable ASCII character, print it as a single character.
+ * otherwise, print it in hex.
+ *
+ * The returned pointer points to a static buffer.
+ */
+static char *
+sanitize_char(char c)
+{
+ static char buf[5];
+
+ if (c >= 0x21 && c <= 0x7E)
+ snprintf(buf, sizeof(buf), "'%c'", c);
+ else
+ snprintf(buf, sizeof(buf), "0x%02x", (unsigned char) c);
+ return buf;
+}
+
+/*
+ * Consumes all kvpairs in an OAUTHBEARER exchange message. If the "auth" key is
+ * found, its value is returned.
+ */
+static char *
+parse_kvpairs_for_auth(char **input)
+{
+ char *pos = *input;
+ char *auth = NULL;
+
+ /*
+ * The relevant ABNF, from Sec. 3.1:
+ *
+ * kvsep = %x01
+ * key = 1*(ALPHA)
+ * value = *(VCHAR / SP / HTAB / CR / LF )
+ * kvpair = key "=" value kvsep
+ * ;;gs2-header = See RFC 5801
+ * client-resp = (gs2-header kvsep *kvpair kvsep) / kvsep
+ *
+ * By the time we reach this code, the gs2-header and initial kvsep have
+ * already been validated. We start at the beginning of the first kvpair.
+ */
+
+ while (*pos)
+ {
+ char *end;
+ char *sep;
+ char *key;
+ char *value;
+
+ /*
+ * Find the end of this kvpair. Note that input is null-terminated by
+ * the SASL code, so the strchr() is bounded.
+ */
+ end = strchr(pos, KVSEP);
+ if (!end)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message contains an unterminated key/value pair.")));
+ *end = '\0';
+
+ if (pos == end)
+ {
+ /* Empty kvpair, signifying the end of the list. */
+ *input = pos + 1;
+ return auth;
+ }
+
+ /*
+ * Find the end of the key name.
+ *
+ * TODO further validate the key/value grammar? empty keys, bad chars...
+ */
+ sep = strchr(pos, '=');
+ if (!sep)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message contains a key without a value.")));
+ *sep = '\0';
+
+ /* Both key and value are now safely terminated. */
+ key = pos;
+ value = sep + 1;
+
+ if (!strcmp(key, AUTH_KEY))
+ {
+ if (auth)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message contains multiple auth values.")));
+
+ auth = value;
+ }
+ else
+ {
+ /*
+ * The RFC also defines the host and port keys, but they are not
+ * required for OAUTHBEARER and we do not use them. Also, per
+ * Sec. 3.1, any key/value pairs we don't recognize must be ignored.
+ */
+ }
+
+ /* Move to the next pair. */
+ pos = end + 1;
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message did not contain a final terminator.")));
+
+ return NULL; /* unreachable */
+}
+
+static void
+generate_error_response(struct oauth_ctx *ctx, char **output, int *outputlen)
+{
+ StringInfoData buf;
+
+ /*
+ * The admin needs to set an issuer and scope for OAuth to work. There's not
+ * really a way to hide this from the user, either, because we can't choose
+ * a "default" issuer, so be honest in the failure message.
+ *
+ * TODO: see if there's a better place to fail, earlier than this.
+ */
+ if (!ctx->issuer || !ctx->scope)
+ ereport(FATAL,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("OAuth is not properly configured for this user"),
+ errdetail_log("The issuer and scope parameters must be set in pg_hba.conf.")));
+
+
+ initStringInfo(&buf);
+
+ /*
+ * TODO: JSON escaping
+ */
+ appendStringInfo(&buf,
+ "{ "
+ "\"status\": \"invalid_token\", "
+ "\"openid-configuration\": \"%s/.well-known/openid-configuration\","
+ "\"scope\": \"%s\" "
+ "}",
+ ctx->issuer, ctx->scope);
+
+ *output = buf.data;
+ *outputlen = buf.len;
+}
+
+static bool
+validate(Port *port, const char *auth, char **logdetail)
+{
+ static const char * const b64_set = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789-._~+/";
+
+ const char *token;
+ size_t span;
+ int ret;
+
+ /* TODO: handle logdetail when the test framework can check it */
+
+ /*
+ * Only Bearer tokens are accepted. The ABNF is defined in RFC 6750, Sec.
+ * 2.1:
+ *
+ * b64token = 1*( ALPHA / DIGIT /
+ * "-" / "." / "_" / "~" / "+" / "/" ) *"="
+ * credentials = "Bearer" 1*SP b64token
+ *
+ * The "credentials" construction is what we receive in our auth value.
+ *
+ * Since that spec is subordinate to HTTP (i.e. the HTTP Authorization
+ * header format; RFC 7235 Sec. 2), the "Bearer" scheme string must be
+ * compared case-insensitively. (This is not mentioned in RFC 6750, but it's
+ * pointed out in RFC 7628 Sec. 4.)
+ *
+ * TODO: handle the Authorization spec, RFC 7235 Sec. 2.1.
+ */
+ if (strncasecmp(auth, BEARER_SCHEME, strlen(BEARER_SCHEME)))
+ return false;
+
+ /* Pull the bearer token out of the auth value. */
+ token = auth + strlen(BEARER_SCHEME);
+
+ /* Swallow any additional spaces. */
+ while (*token == ' ')
+ token++;
+
+ /*
+ * Before invoking the validator command, sanity-check the token format to
+ * avoid any injection attacks later in the chain. Invalid formats are
+ * technically a protocol violation, but don't reflect any information about
+ * the sensitive Bearer token back to the client; log at COMMERROR instead.
+ */
+
+ /* Tokens must not be empty. */
+ if (!*token)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Bearer token is empty.")));
+ return false;
+ }
+
+ /*
+ * Make sure the token contains only allowed characters. Tokens may end with
+ * any number of '=' characters.
+ */
+ span = strspn(token, b64_set);
+ while (token[span] == '=')
+ span++;
+
+ if (token[span] != '\0')
+ {
+ /*
+ * This error message could be more helpful by printing the problematic
+ * character(s), but that'd be a bit like printing a piece of someone's
+ * password into the logs.
+ */
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Bearer token is not in the correct format.")));
+ return false;
+ }
+
+ /* Have the validator check the token. */
+ if (!run_validator_command(port, token))
+ return false;
+
+ if (port->hba->oauth_skip_usermap)
+ {
+ /*
+ * If the validator is our authorization authority, we're done.
+ * Authentication may or may not have been performed depending on the
+ * validator implementation; all that matters is that the validator says
+ * the user can log in with the target role.
+ */
+ return true;
+ }
+
+ /* Make sure the validator authenticated the user. */
+ if (!port->authn_id)
+ {
+ /* TODO: use logdetail; reduce message duplication */
+ ereport(LOG,
+ (errmsg("OAuth bearer authentication failed for user \"%s\": validator provided no identity",
+ port->user_name)));
+ return false;
+ }
+
+ /* Finally, check the user map. */
+ ret = check_usermap(port->hba->usermap, port->user_name, port->authn_id,
+ false);
+ return (ret == STATUS_OK);
+}
+
+static bool
+run_validator_command(Port *port, const char *token)
+{
+ bool success = false;
+ int rc;
+ int pipefd[2];
+ int rfd = -1;
+ int wfd = -1;
+
+ StringInfoData command = { 0 };
+ char *p;
+ FILE *fh = NULL;
+
+ ssize_t written;
+ char *line = NULL;
+ size_t size = 0;
+ ssize_t len;
+
+ Assert(oauth_validator_command);
+
+ if (!oauth_validator_command[0])
+ {
+ ereport(COMMERROR,
+ (errmsg("oauth_validator_command is not set"),
+ errhint("To allow OAuth authenticated connections, set "
+ "oauth_validator_command in postgresql.conf.")));
+ return false;
+ }
+
+ /*
+ * Since popen() is unidirectional, open up a pipe for the other direction.
+ * Use CLOEXEC to ensure that our write end doesn't accidentally get copied
+ * into child processes, which would prevent us from closing it cleanly.
+ *
+ * XXX this is ugly. We should just read from the child process's stdout,
+ * but that's a lot more code.
+ * XXX by bypassing the popen API, we open the potential of process
+ * deadlock. Clearly document child process requirements (i.e. the child
+ * MUST read all data off of the pipe before writing anything).
+ * TODO: port to Windows using _pipe().
+ */
+ rc = pipe2(pipefd, O_CLOEXEC);
+ if (rc < 0)
+ {
+ ereport(COMMERROR,
+ (errcode_for_file_access(),
+ errmsg("could not create child pipe: %m")));
+ return false;
+ }
+
+ rfd = pipefd[0];
+ wfd = pipefd[1];
+
+ /* Allow the read pipe be passed to the child. */
+ if (!unset_cloexec(rfd))
+ {
+ /* error message was already logged */
+ goto cleanup;
+ }
+
+ /*
+ * Construct the command, substituting any recognized %-specifiers:
+ *
+ * %f: the file descriptor of the input pipe
+ * %r: the role that the client wants to assume (port->user_name)
+ * %%: a literal '%'
+ */
+ initStringInfo(&command);
+
+ for (p = oauth_validator_command; *p; p++)
+ {
+ if (p[0] == '%')
+ {
+ switch (p[1])
+ {
+ case 'f':
+ appendStringInfo(&command, "%d", rfd);
+ p++;
+ break;
+ case 'r':
+ /*
+ * TODO: decide how this string should be escaped. The role
+ * is controlled by the client, so if we don't escape it,
+ * command injections are inevitable.
+ *
+ * This is probably an indication that the role name needs
+ * to be communicated to the validator process in some other
+ * way. For this proof of concept, just be incredibly strict
+ * about the characters that are allowed in user names.
+ */
+ if (!username_ok_for_shell(port->user_name))
+ goto cleanup;
+
+ appendStringInfoString(&command, port->user_name);
+ p++;
+ break;
+ case '%':
+ appendStringInfoChar(&command, '%');
+ p++;
+ break;
+ default:
+ appendStringInfoChar(&command, p[0]);
+ }
+ }
+ else
+ appendStringInfoChar(&command, p[0]);
+ }
+
+ /* Execute the command. */
+ fh = OpenPipeStream(command.data, "re");
+ /* TODO: handle failures */
+
+ /* We don't need the read end of the pipe anymore. */
+ close(rfd);
+ rfd = -1;
+
+ /* Give the command the token to validate. */
+ written = write(wfd, token, strlen(token));
+ if (written != strlen(token))
+ {
+ /* TODO must loop for short writes, EINTR et al */
+ ereport(COMMERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write token to child pipe: %m")));
+ goto cleanup;
+ }
+
+ close(wfd);
+ wfd = -1;
+
+ /*
+ * Read the command's response.
+ *
+ * TODO: getline() is probably too new to use, unfortunately.
+ * TODO: loop over all lines
+ */
+ if ((len = getline(&line, &size, fh)) >= 0)
+ {
+ /* TODO: fail if the authn_id doesn't end with a newline */
+ if (len > 0)
+ line[len - 1] = '\0';
+
+ set_authn_id(port, line);
+ }
+ else if (ferror(fh))
+ {
+ ereport(COMMERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read from command \"%s\": %m",
+ command.data)));
+ goto cleanup;
+ }
+
+ /* Make sure the command exits cleanly. */
+ if (!check_exit(&fh, command.data))
+ {
+ /* error message already logged */
+ goto cleanup;
+ }
+
+ /* Done. */
+ success = true;
+
+cleanup:
+ if (line)
+ free(line);
+
+ /*
+ * In the successful case, the pipe fds are already closed. For the error
+ * case, always close out the pipe before waiting for the command, to
+ * prevent deadlock.
+ */
+ if (rfd >= 0)
+ close(rfd);
+ if (wfd >= 0)
+ close(wfd);
+
+ if (fh)
+ {
+ Assert(!success);
+ check_exit(&fh, command.data);
+ }
+
+ if (command.data)
+ pfree(command.data);
+
+ return success;
+}
+
+static bool
+check_exit(FILE **fh, const char *command)
+{
+ int rc;
+
+ rc = ClosePipeStream(*fh);
+ *fh = NULL;
+
+ if (rc == -1)
+ {
+ /* pclose() itself failed. */
+ ereport(COMMERROR,
+ (errcode_for_file_access(),
+ errmsg("could not close pipe to command \"%s\": %m",
+ command)));
+ }
+ else if (rc != 0)
+ {
+ char *reason = wait_result_to_str(rc);
+
+ ereport(COMMERROR,
+ (errmsg("failed to execute command \"%s\": %s",
+ command, reason)));
+
+ pfree(reason);
+ }
+
+ return (rc == 0);
+}
+
+static bool
+unset_cloexec(int fd)
+{
+ int flags;
+ int rc;
+
+ flags = fcntl(fd, F_GETFD);
+ if (flags == -1)
+ {
+ ereport(COMMERROR,
+ (errcode_for_file_access(),
+ errmsg("could not get fd flags for child pipe: %m")));
+ return false;
+ }
+
+ rc = fcntl(fd, F_SETFD, flags & ~FD_CLOEXEC);
+ if (rc < 0)
+ {
+ ereport(COMMERROR,
+ (errcode_for_file_access(),
+ errmsg("could not unset FD_CLOEXEC for child pipe: %m")));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * XXX This should go away eventually and be replaced with either a proper
+ * escape or a different strategy for communication with the validator command.
+ */
+static bool
+username_ok_for_shell(const char *username)
+{
+ /* This set is borrowed from fe_utils' appendShellStringNoError(). */
+ static const char * const allowed = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789-_./:";
+ size_t span;
+
+ Assert(username && username[0]); /* should have already been checked */
+
+ span = strspn(username, allowed);
+ if (username[span] != '\0')
+ {
+ ereport(COMMERROR,
+ (errmsg("PostgreSQL user name contains unsafe characters and cannot be passed to the OAuth validator")));
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/backend/libpq/auth-sasl.c b/src/backend/libpq/auth-sasl.c
index 6cfd90fa21..f6c49a4de5 100644
--- a/src/backend/libpq/auth-sasl.c
+++ b/src/backend/libpq/auth-sasl.c
@@ -20,14 +20,6 @@
#include "libpq/pqformat.h"
#include "libpq/sasl.h"
-/*
- * Maximum accepted size of SASL messages.
- *
- * The messages that the server or libpq generate are much smaller than this,
- * but have some headroom.
- */
-#define PG_MAX_SASL_MESSAGE_LENGTH 1024
-
/*
* Perform a SASL exchange with a libpq client, using a specific mechanism
* implementation.
@@ -103,7 +95,7 @@ CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
/* Get the actual SASL message */
initStringInfo(&buf);
- if (pq_getmessage(&buf, PG_MAX_SASL_MESSAGE_LENGTH))
+ if (pq_getmessage(&buf, mech->max_message_length))
{
/* EOF - pq_getmessage already logged error */
pfree(buf.data);
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 9df8f17837..5bb0388c01 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -117,7 +117,9 @@ static int scram_exchange(void *opaq, const char *input, int inputlen,
const pg_be_sasl_mech pg_be_scram_mech = {
scram_get_mechanisms,
scram_init,
- scram_exchange
+ scram_exchange,
+
+ PG_MAX_SASL_MESSAGE_LENGTH
};
/*
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 8cc23ef7fb..fbcc2c55b4 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -29,6 +29,7 @@
#include "libpq/auth.h"
#include "libpq/crypt.h"
#include "libpq/libpq.h"
+#include "libpq/oauth.h"
#include "libpq/pqformat.h"
#include "libpq/sasl.h"
#include "libpq/scram.h"
@@ -47,7 +48,6 @@
*/
static void auth_failed(Port *port, int status, char *logdetail);
static char *recv_password_packet(Port *port);
-static void set_authn_id(Port *port, const char *id);
/*----------------------------------------------------------------
@@ -205,22 +205,6 @@ static int CheckRADIUSAuth(Port *port);
static int PerformRadiusTransaction(const char *server, const char *secret, const char *portstr, const char *identifier, const char *user_name, const char *passwd);
-/*
- * Maximum accepted size of GSS and SSPI authentication tokens.
- * We also use this as a limit on ordinary password packet lengths.
- *
- * Kerberos tickets are usually quite small, but the TGTs issued by Windows
- * domain controllers include an authorization field known as the Privilege
- * Attribute Certificate (PAC), which contains the user's Windows permissions
- * (group memberships etc.). The PAC is copied into all tickets obtained on
- * the basis of this TGT (even those issued by Unix realms which the Windows
- * realm trusts), and can be several kB in size. The maximum token size
- * accepted by Windows systems is determined by the MaxAuthToken Windows
- * registry setting. Microsoft recommends that it is not set higher than
- * 65535 bytes, so that seems like a reasonable limit for us as well.
- */
-#define PG_MAX_AUTH_TOKEN_LENGTH 65535
-
/*----------------------------------------------------------------
* Global authentication functions
*----------------------------------------------------------------
@@ -309,6 +293,9 @@ auth_failed(Port *port, int status, char *logdetail)
case uaRADIUS:
errstr = gettext_noop("RADIUS authentication failed for user \"%s\"");
break;
+ case uaOAuth:
+ errstr = gettext_noop("OAuth bearer authentication failed for user \"%s\"");
+ break;
default:
errstr = gettext_noop("authentication failed for user \"%s\": invalid authentication method");
break;
@@ -343,7 +330,7 @@ auth_failed(Port *port, int status, char *logdetail)
* lifetime of the Port, so it is safe to pass a string that is managed by an
* external library.
*/
-static void
+void
set_authn_id(Port *port, const char *id)
{
Assert(id);
@@ -628,6 +615,9 @@ ClientAuthentication(Port *port)
case uaTrust:
status = STATUS_OK;
break;
+ case uaOAuth:
+ status = CheckSASLAuth(&pg_be_oauth_mech, port, NULL, NULL);
+ break;
}
if ((status == STATUS_OK && port->hba->clientcert == clientCertFull)
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 3be8778d21..98147700dd 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -134,7 +134,8 @@ static const char *const UserAuthName[] =
"ldap",
"cert",
"radius",
- "peer"
+ "peer",
+ "oauth",
};
@@ -1399,6 +1400,8 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
#endif
else if (strcmp(token->string, "radius") == 0)
parsedline->auth_method = uaRADIUS;
+ else if (strcmp(token->string, "oauth") == 0)
+ parsedline->auth_method = uaOAuth;
else
{
ereport(elevel,
@@ -1713,8 +1716,9 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
hbaline->auth_method != uaPeer &&
hbaline->auth_method != uaGSS &&
hbaline->auth_method != uaSSPI &&
+ hbaline->auth_method != uaOAuth &&
hbaline->auth_method != uaCert)
- INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, gssapi, sspi, and cert"));
+ INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, gssapi, sspi, oauth, and cert"));
hbaline->usermap = pstrdup(val);
}
else if (strcmp(name, "clientcert") == 0)
@@ -2098,6 +2102,27 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
hbaline->radiusidentifiers = parsed_identifiers;
hbaline->radiusidentifiers_s = pstrdup(val);
}
+ else if (strcmp(name, "issuer") == 0)
+ {
+ if (hbaline->auth_method != uaOAuth)
+ INVALID_AUTH_OPTION("issuer", gettext_noop("oauth"));
+ hbaline->oauth_issuer = pstrdup(val);
+ }
+ else if (strcmp(name, "scope") == 0)
+ {
+ if (hbaline->auth_method != uaOAuth)
+ INVALID_AUTH_OPTION("scope", gettext_noop("oauth"));
+ hbaline->oauth_scope = pstrdup(val);
+ }
+ else if (strcmp(name, "trust_validator_authz") == 0)
+ {
+ if (hbaline->auth_method != uaOAuth)
+ INVALID_AUTH_OPTION("trust_validator_authz", gettext_noop("oauth"));
+ if (strcmp(val, "1") == 0)
+ hbaline->oauth_skip_usermap = true;
+ else
+ hbaline->oauth_skip_usermap = false;
+ }
else
{
ereport(elevel,
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 467b0fd6fe..2b42862f71 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -56,6 +56,7 @@
#include "libpq/auth.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
+#include "libpq/oauth.h"
#include "miscadmin.h"
#include "optimizer/cost.h"
#include "optimizer/geqo.h"
@@ -4594,6 +4595,17 @@ static struct config_string ConfigureNamesString[] =
check_backtrace_functions, assign_backtrace_functions, NULL
},
+ {
+ {"oauth_validator_command", PGC_SIGHUP, CONN_AUTH_AUTH,
+ gettext_noop("Command to validate OAuth v2 bearer tokens."),
+ NULL,
+ GUC_SUPERUSER_ONLY
+ },
+ &oauth_validator_command,
+ "",
+ NULL, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/include/libpq/auth.h b/src/include/libpq/auth.h
index 3d6734f253..1c77dcb0c1 100644
--- a/src/include/libpq/auth.h
+++ b/src/include/libpq/auth.h
@@ -16,6 +16,22 @@
#include "libpq/libpq-be.h"
+/*
+ * Maximum accepted size of GSS and SSPI authentication tokens.
+ * We also use this as a limit on ordinary password packet lengths.
+ *
+ * Kerberos tickets are usually quite small, but the TGTs issued by Windows
+ * domain controllers include an authorization field known as the Privilege
+ * Attribute Certificate (PAC), which contains the user's Windows permissions
+ * (group memberships etc.). The PAC is copied into all tickets obtained on
+ * the basis of this TGT (even those issued by Unix realms which the Windows
+ * realm trusts), and can be several kB in size. The maximum token size
+ * accepted by Windows systems is determined by the MaxAuthToken Windows
+ * registry setting. Microsoft recommends that it is not set higher than
+ * 65535 bytes, so that seems like a reasonable limit for us as well.
+ */
+#define PG_MAX_AUTH_TOKEN_LENGTH 65535
+
extern char *pg_krb_server_keyfile;
extern bool pg_krb_caseins_users;
extern char *pg_krb_realm;
@@ -23,6 +39,7 @@ extern char *pg_krb_realm;
extern void ClientAuthentication(Port *port);
extern void sendAuthRequest(Port *port, AuthRequest areq, const char *extradata,
int extralen);
+extern void set_authn_id(Port *port, const char *id);
/* Hook for plugins to get control in ClientAuthentication() */
typedef void (*ClientAuthentication_hook_type) (Port *, int);
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 8d9f3821b1..441dd5623e 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -38,8 +38,9 @@ typedef enum UserAuth
uaLDAP,
uaCert,
uaRADIUS,
- uaPeer
-#define USER_AUTH_LAST uaPeer /* Must be last value of this enum */
+ uaPeer,
+ uaOAuth
+#define USER_AUTH_LAST uaOAuth /* Must be last value of this enum */
} UserAuth;
/*
@@ -120,6 +121,9 @@ typedef struct HbaLine
char *radiusidentifiers_s;
List *radiusports;
char *radiusports_s;
+ char *oauth_issuer;
+ char *oauth_scope;
+ bool oauth_skip_usermap;
} HbaLine;
typedef struct IdentLine
diff --git a/src/include/libpq/oauth.h b/src/include/libpq/oauth.h
new file mode 100644
index 0000000000..870e426af1
--- /dev/null
+++ b/src/include/libpq/oauth.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth.h
+ * Interface to libpq/auth-oauth.c
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/libpq/oauth.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_OAUTH_H
+#define PG_OAUTH_H
+
+#include "libpq/libpq-be.h"
+#include "libpq/sasl.h"
+
+extern char *oauth_validator_command;
+
+/* Implementation */
+extern const pg_be_sasl_mech pg_be_oauth_mech;
+
+#endif /* PG_OAUTH_H */
diff --git a/src/include/libpq/sasl.h b/src/include/libpq/sasl.h
index 4c611bab6b..c0a88430d5 100644
--- a/src/include/libpq/sasl.h
+++ b/src/include/libpq/sasl.h
@@ -26,6 +26,14 @@
#define PG_SASL_EXCHANGE_SUCCESS 1
#define PG_SASL_EXCHANGE_FAILURE 2
+/*
+ * Maximum accepted size of SASL messages.
+ *
+ * The messages that the server or libpq generate are much smaller than this,
+ * but have some headroom.
+ */
+#define PG_MAX_SASL_MESSAGE_LENGTH 1024
+
/*
* Backend SASL mechanism callbacks.
*
@@ -127,6 +135,9 @@ typedef struct pg_be_sasl_mech
const char *input, int inputlen,
char **output, int *outputlen,
char **logdetail);
+
+ /* The maximum size allowed for client SASLResponses. */
+ int max_message_length;
} pg_be_sasl_mech;
/* Common implementation for auth.c */
--
2.25.1
[text/x-patch] v2-0004-Add-a-very-simple-authn_id-extension.patch (2.8K, ../../[email protected]/5-v2-0004-Add-a-very-simple-authn_id-extension.patch)
download | inline diff:
From 7c4175f9ad87141d40dd44d6c9fe9312ce8e5b88 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Tue, 18 May 2021 15:01:29 -0700
Subject: [PATCH v2 4/5] Add a very simple authn_id extension
...for retrieving the authn_id from the server in tests.
---
contrib/authn_id/Makefile | 19 +++++++++++++++++++
contrib/authn_id/authn_id--1.0.sql | 8 ++++++++
contrib/authn_id/authn_id.c | 28 ++++++++++++++++++++++++++++
contrib/authn_id/authn_id.control | 5 +++++
4 files changed, 60 insertions(+)
create mode 100644 contrib/authn_id/Makefile
create mode 100644 contrib/authn_id/authn_id--1.0.sql
create mode 100644 contrib/authn_id/authn_id.c
create mode 100644 contrib/authn_id/authn_id.control
diff --git a/contrib/authn_id/Makefile b/contrib/authn_id/Makefile
new file mode 100644
index 0000000000..46026358e0
--- /dev/null
+++ b/contrib/authn_id/Makefile
@@ -0,0 +1,19 @@
+# contrib/authn_id/Makefile
+
+MODULE_big = authn_id
+OBJS = authn_id.o
+
+EXTENSION = authn_id
+DATA = authn_id--1.0.sql
+PGFILEDESC = "authn_id - information about the authenticated user"
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/authn_id
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/authn_id/authn_id--1.0.sql b/contrib/authn_id/authn_id--1.0.sql
new file mode 100644
index 0000000000..af2a4d3991
--- /dev/null
+++ b/contrib/authn_id/authn_id--1.0.sql
@@ -0,0 +1,8 @@
+/* contrib/authn_id/authn_id--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION authn_id" to load this file. \quit
+
+CREATE FUNCTION authn_id() RETURNS text
+AS 'MODULE_PATHNAME', 'authn_id'
+LANGUAGE C IMMUTABLE;
diff --git a/contrib/authn_id/authn_id.c b/contrib/authn_id/authn_id.c
new file mode 100644
index 0000000000..0fecac36a8
--- /dev/null
+++ b/contrib/authn_id/authn_id.c
@@ -0,0 +1,28 @@
+/*
+ * Extension to expose the current user's authn_id.
+ *
+ * contrib/authn_id/authn_id.c
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "libpq/libpq-be.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(authn_id);
+
+/*
+ * Returns the current user's authenticated identity.
+ */
+Datum
+authn_id(PG_FUNCTION_ARGS)
+{
+ if (!MyProcPort->authn_id)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(MyProcPort->authn_id));
+}
diff --git a/contrib/authn_id/authn_id.control b/contrib/authn_id/authn_id.control
new file mode 100644
index 0000000000..e0f9e06bed
--- /dev/null
+++ b/contrib/authn_id/authn_id.control
@@ -0,0 +1,5 @@
+# authn_id extension
+comment = 'current user identity'
+default_version = '1.0'
+module_pathname = '$libdir/authn_id'
+relocatable = true
--
2.25.1
[text/x-patch] v2-0005-Add-pytest-suite-for-OAuth.patch (131.6K, ../../[email protected]/6-v2-0005-Add-pytest-suite-for-OAuth.patch)
download | inline diff:
From 0281635f35a44e0fdfd4369423f98ebe5b467ce3 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Fri, 4 Jun 2021 09:06:38 -0700
Subject: [PATCH v2 5/5] Add pytest suite for OAuth
Requires Python 3; on the first run of `make installcheck` the
dependencies will be installed into ./venv for you. See the README for
more details.
---
src/test/python/.gitignore | 2 +
src/test/python/Makefile | 38 +
src/test/python/README | 54 ++
src/test/python/client/__init__.py | 0
src/test/python/client/conftest.py | 126 +++
src/test/python/client/test_client.py | 180 ++++
src/test/python/client/test_oauth.py | 936 ++++++++++++++++++
src/test/python/pq3.py | 727 ++++++++++++++
src/test/python/pytest.ini | 4 +
src/test/python/requirements.txt | 7 +
src/test/python/server/__init__.py | 0
src/test/python/server/conftest.py | 45 +
src/test/python/server/test_oauth.py | 1012 ++++++++++++++++++++
src/test/python/server/test_server.py | 21 +
src/test/python/server/validate_bearer.py | 101 ++
src/test/python/server/validate_reflect.py | 34 +
src/test/python/test_internals.py | 138 +++
src/test/python/test_pq3.py | 558 +++++++++++
src/test/python/tls.py | 195 ++++
19 files changed, 4178 insertions(+)
create mode 100644 src/test/python/.gitignore
create mode 100644 src/test/python/Makefile
create mode 100644 src/test/python/README
create mode 100644 src/test/python/client/__init__.py
create mode 100644 src/test/python/client/conftest.py
create mode 100644 src/test/python/client/test_client.py
create mode 100644 src/test/python/client/test_oauth.py
create mode 100644 src/test/python/pq3.py
create mode 100644 src/test/python/pytest.ini
create mode 100644 src/test/python/requirements.txt
create mode 100644 src/test/python/server/__init__.py
create mode 100644 src/test/python/server/conftest.py
create mode 100644 src/test/python/server/test_oauth.py
create mode 100644 src/test/python/server/test_server.py
create mode 100755 src/test/python/server/validate_bearer.py
create mode 100755 src/test/python/server/validate_reflect.py
create mode 100644 src/test/python/test_internals.py
create mode 100644 src/test/python/test_pq3.py
create mode 100644 src/test/python/tls.py
diff --git a/src/test/python/.gitignore b/src/test/python/.gitignore
new file mode 100644
index 0000000000..0e8f027b2e
--- /dev/null
+++ b/src/test/python/.gitignore
@@ -0,0 +1,2 @@
+__pycache__/
+/venv/
diff --git a/src/test/python/Makefile b/src/test/python/Makefile
new file mode 100644
index 0000000000..b0695b6287
--- /dev/null
+++ b/src/test/python/Makefile
@@ -0,0 +1,38 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+# Only Python 3 is supported, but if it's named something different on your
+# system you can override it with the PYTHON3 variable.
+PYTHON3 := python3
+
+# All dependencies are placed into this directory. The default is .gitignored
+# for you, but you can override it if you'd like.
+VENV := ./venv
+
+override VBIN := $(VENV)/bin
+override PIP := $(VBIN)/pip
+override PYTEST := $(VBIN)/py.test
+override ISORT := $(VBIN)/isort
+override BLACK := $(VBIN)/black
+
+.PHONY: installcheck indent
+
+installcheck: $(PYTEST)
+ $(PYTEST) -v -rs
+
+indent: $(ISORT) $(BLACK)
+ $(ISORT) --profile black *.py client/*.py server/*.py
+ $(BLACK) *.py client/*.py server/*.py
+
+$(PYTEST) $(ISORT) $(BLACK) &: requirements.txt | $(PIP)
+ $(PIP) install --force-reinstall -r $<
+
+$(PIP):
+ $(PYTHON3) -m venv $(VENV)
+
+# A convenience recipe to rebuild psycopg2 against the local libpq.
+.PHONY: rebuild-psycopg2
+rebuild-psycopg2: | $(PIP)
+ $(PIP) install --force-reinstall --no-binary :all: $(shell grep psycopg2 requirements.txt)
diff --git a/src/test/python/README b/src/test/python/README
new file mode 100644
index 0000000000..0bda582c4b
--- /dev/null
+++ b/src/test/python/README
@@ -0,0 +1,54 @@
+A test suite for exercising both the libpq client and the server backend at the
+protocol level, based on pytest and Construct.
+
+The test suite currently assumes that the standard PG* environment variables
+point to the database under test and are sufficient to log in a superuser on
+that system. In other words, a bare `psql` needs to Just Work before the test
+suite can do its thing. For a newly built dev cluster, typically all that I need
+to do is a
+
+ export PGDATABASE=postgres
+
+but you can adjust as needed for your setup.
+
+## Requirements
+
+A supported version (3.6+) of Python.
+
+The first run of
+
+ make installcheck
+
+will install a local virtual environment and all needed dependencies. During
+development, if libpq changes incompatibly, you can issue
+
+ $ make rebuild-psycopg2
+
+to force a rebuild of the client library.
+
+## Hacking
+
+The code style is enforced by a _very_ opinionated autoformatter. Running the
+
+ make indent
+
+recipe will invoke it for you automatically. Don't fight the tool; part of the
+zen is in knowing that if the formatter makes your code ugly, there's probably a
+cleaner way to write your code.
+
+## Advanced Usage
+
+The Makefile is there for convenience, but you don't have to use it. Activate
+the virtualenv to be able to use pytest directly:
+
+ $ source venv/bin/activate
+ $ py.test -k oauth
+ ...
+ $ py.test ./server/test_server.py
+ ...
+ $ deactivate # puts the PATH et al back the way it was before
+
+To make quick smoke tests possible, slow tests have been marked explicitly. You
+can skip them by saying e.g.
+
+ $ py.test -m 'not slow'
diff --git a/src/test/python/client/__init__.py b/src/test/python/client/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/test/python/client/conftest.py b/src/test/python/client/conftest.py
new file mode 100644
index 0000000000..f38da7a138
--- /dev/null
+++ b/src/test/python/client/conftest.py
@@ -0,0 +1,126 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import socket
+import sys
+import threading
+
+import psycopg2
+import pytest
+
+import pq3
+
+BLOCKING_TIMEOUT = 2 # the number of seconds to wait for blocking calls
+
+
[email protected]
+def server_socket(unused_tcp_port_factory):
+ """
+ Returns a listening socket bound to an ephemeral port.
+ """
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
+ s.bind(("127.0.0.1", unused_tcp_port_factory()))
+ s.listen(1)
+ s.settimeout(BLOCKING_TIMEOUT)
+ yield s
+
+
+class ClientHandshake(threading.Thread):
+ """
+ A thread that connects to a local Postgres server using psycopg2. Once the
+ opening handshake completes, the connection will be immediately closed.
+ """
+
+ def __init__(self, *, port, **kwargs):
+ super().__init__()
+
+ kwargs["port"] = port
+ self._kwargs = kwargs
+
+ self.exception = None
+
+ def run(self):
+ try:
+ conn = psycopg2.connect(host="127.0.0.1", **self._kwargs)
+ conn.close()
+ except Exception as e:
+ self.exception = e
+
+ def check_completed(self, timeout=BLOCKING_TIMEOUT):
+ """
+ Joins the client thread. Raises an exception if the thread could not be
+ joined, or if it threw an exception itself. (The exception will be
+ cleared, so future calls to check_completed will succeed.)
+ """
+ self.join(timeout)
+
+ if self.is_alive():
+ raise TimeoutError("client thread did not handshake within the timeout")
+ elif self.exception:
+ e = self.exception
+ self.exception = None
+ raise e
+
+
[email protected]
+def accept(server_socket):
+ """
+ Returns a factory function that, when called, returns a pair (sock, client)
+ where sock is a server socket that has accepted a connection from client,
+ and client is an instance of ClientHandshake. Clients will complete their
+ handshakes and cleanly disconnect.
+
+ The default connstring options may be extended or overridden by passing
+ arbitrary keyword arguments. Keep in mind that you generally should not
+ override the host or port, since they point to the local test server.
+
+ For situations where a client needs to connect more than once to complete a
+ handshake, the accept function may be called more than once. (The client
+ returned for subsequent calls will always be the same client that was
+ returned for the first call.)
+
+ Tests must either complete the handshake so that the client thread can be
+ automatically joined during teardown, or else call client.check_completed()
+ and manually handle any expected errors.
+ """
+ _, port = server_socket.getsockname()
+
+ client = None
+ default_opts = dict(
+ port=port,
+ user=pq3.pguser(),
+ sslmode="disable",
+ )
+
+ def factory(**kwargs):
+ nonlocal client
+
+ if client is None:
+ opts = dict(default_opts)
+ opts.update(kwargs)
+
+ # The server_socket is already listening, so the client thread can
+ # be safely started; it'll block on the connection until we accept.
+ client = ClientHandshake(**opts)
+ client.start()
+
+ sock, _ = server_socket.accept()
+ return sock, client
+
+ yield factory
+ client.check_completed()
+
+
[email protected]
+def conn(accept):
+ """
+ Returns an accepted, wrapped pq3 connection to a psycopg2 client. The socket
+ will be closed when the test finishes, and the client will be checked for a
+ cleanly completed handshake.
+ """
+ sock, client = accept()
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ yield conn
diff --git a/src/test/python/client/test_client.py b/src/test/python/client/test_client.py
new file mode 100644
index 0000000000..c4c946fda4
--- /dev/null
+++ b/src/test/python/client/test_client.py
@@ -0,0 +1,180 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import base64
+import sys
+
+import psycopg2
+import pytest
+from cryptography.hazmat.primitives import hashes, hmac
+
+import pq3
+
+
+def finish_handshake(conn):
+ """
+ Sends the AuthenticationOK message and the standard opening salvo of server
+ messages, then asserts that the client immediately sends a Terminate message
+ to close the connection cleanly.
+ """
+ pq3.send(conn, pq3.types.AuthnRequest, type=pq3.authn.OK)
+ pq3.send(conn, pq3.types.ParameterStatus, name=b"client_encoding", value=b"UTF-8")
+ pq3.send(conn, pq3.types.ParameterStatus, name=b"DateStyle", value=b"ISO, MDY")
+ pq3.send(conn, pq3.types.ReadyForQuery, status=b"I")
+
+ pkt = pq3.recv1(conn)
+ assert pkt.type == pq3.types.Terminate
+
+
+def test_handshake(conn):
+ startup = pq3.recv1(conn, cls=pq3.Startup)
+ assert startup.proto == pq3.protocol(3, 0)
+
+ finish_handshake(conn)
+
+
+def test_aborted_connection(accept):
+ """
+ Make sure the client correctly reports an early close during handshakes.
+ """
+ sock, client = accept()
+ sock.close()
+
+ expected = "server closed the connection unexpectedly"
+ with pytest.raises(psycopg2.OperationalError, match=expected):
+ client.check_completed()
+
+
+#
+# SCRAM-SHA-256 (see RFC 5802: https://tools.ietf.org/html/rfc5802)
+#
+
+
[email protected]
+def password():
+ """
+ Returns a password for use by both client and server.
+ """
+ # TODO: parameterize this with passwords that require SASLprep.
+ return "secret"
+
+
[email protected]
+def pwconn(accept, password):
+ """
+ Like the conn fixture, but uses a password in the connection.
+ """
+ sock, client = accept(password=password)
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ yield conn
+
+
+def sha256(data):
+ """The H(str) function from Section 2.2."""
+ digest = hashes.Hash(hashes.SHA256())
+ digest.update(data)
+ return digest.finalize()
+
+
+def hmac_256(key, data):
+ """The HMAC(key, str) function from Section 2.2."""
+ h = hmac.HMAC(key, hashes.SHA256())
+ h.update(data)
+ return h.finalize()
+
+
+def xor(a, b):
+ """The XOR operation from Section 2.2."""
+ res = bytearray(a)
+ for i, byte in enumerate(b):
+ res[i] ^= byte
+ return bytes(res)
+
+
+def h_i(data, salt, i):
+ """The Hi(str, salt, i) function from Section 2.2."""
+ assert i > 0
+
+ acc = hmac_256(data, salt + b"\x00\x00\x00\x01")
+ last = acc
+ i -= 1
+
+ while i:
+ u = hmac_256(data, last)
+ acc = xor(acc, u)
+
+ last = u
+ i -= 1
+
+ return acc
+
+
+def test_scram(pwconn, password):
+ startup = pq3.recv1(pwconn, cls=pq3.Startup)
+ assert startup.proto == pq3.protocol(3, 0)
+
+ pq3.send(
+ pwconn,
+ pq3.types.AuthnRequest,
+ type=pq3.authn.SASL,
+ body=[b"SCRAM-SHA-256", b""],
+ )
+
+ # Get the client-first-message.
+ pkt = pq3.recv1(pwconn)
+ assert pkt.type == pq3.types.PasswordMessage
+
+ initial = pq3.SASLInitialResponse.parse(pkt.payload)
+ assert initial.name == b"SCRAM-SHA-256"
+
+ c_bind, authzid, c_name, c_nonce = initial.data.split(b",")
+ assert c_bind == b"n" # no channel bindings on a plaintext connection
+ assert authzid == b"" # we don't support authzid currently
+ assert c_name == b"n=" # libpq doesn't honor the GS2 username
+ assert c_nonce.startswith(b"r=")
+
+ # Send the server-first-message.
+ salt = b"12345"
+ iterations = 2
+
+ s_nonce = c_nonce + b"somenonce"
+ s_salt = b"s=" + base64.b64encode(salt)
+ s_iterations = b"i=%d" % iterations
+
+ msg = b",".join([s_nonce, s_salt, s_iterations])
+ pq3.send(pwconn, pq3.types.AuthnRequest, type=pq3.authn.SASLContinue, body=msg)
+
+ # Get the client-final-message.
+ pkt = pq3.recv1(pwconn)
+ assert pkt.type == pq3.types.PasswordMessage
+
+ c_bind_final, c_nonce_final, c_proof = pkt.payload.split(b",")
+ assert c_bind_final == b"c=" + base64.b64encode(c_bind + b"," + authzid + b",")
+ assert c_nonce_final == s_nonce
+
+ # Calculate what the client proof should be.
+ salted_password = h_i(password.encode("ascii"), salt, iterations)
+ client_key = hmac_256(salted_password, b"Client Key")
+ stored_key = sha256(client_key)
+
+ auth_message = b",".join(
+ [c_name, c_nonce, s_nonce, s_salt, s_iterations, c_bind_final, c_nonce_final]
+ )
+ client_signature = hmac_256(stored_key, auth_message)
+ client_proof = xor(client_key, client_signature)
+
+ expected = b"p=" + base64.b64encode(client_proof)
+ assert c_proof == expected
+
+ # Send the correct server signature.
+ server_key = hmac_256(salted_password, b"Server Key")
+ server_signature = hmac_256(server_key, auth_message)
+
+ s_verify = b"v=" + base64.b64encode(server_signature)
+ pq3.send(pwconn, pq3.types.AuthnRequest, type=pq3.authn.SASLFinal, body=s_verify)
+
+ # Done!
+ finish_handshake(pwconn)
diff --git a/src/test/python/client/test_oauth.py b/src/test/python/client/test_oauth.py
new file mode 100644
index 0000000000..a754a9c0b6
--- /dev/null
+++ b/src/test/python/client/test_oauth.py
@@ -0,0 +1,936 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import base64
+import http.server
+import json
+import secrets
+import sys
+import threading
+import time
+import urllib.parse
+
+import psycopg2
+import pytest
+
+import pq3
+
+from .conftest import BLOCKING_TIMEOUT
+
+
+def finish_handshake(conn):
+ """
+ Sends the AuthenticationOK message and the standard opening salvo of server
+ messages, then asserts that the client immediately sends a Terminate message
+ to close the connection cleanly.
+ """
+ pq3.send(conn, pq3.types.AuthnRequest, type=pq3.authn.OK)
+ pq3.send(conn, pq3.types.ParameterStatus, name=b"client_encoding", value=b"UTF-8")
+ pq3.send(conn, pq3.types.ParameterStatus, name=b"DateStyle", value=b"ISO, MDY")
+ pq3.send(conn, pq3.types.ReadyForQuery, status=b"I")
+
+ pkt = pq3.recv1(conn)
+ assert pkt.type == pq3.types.Terminate
+
+
+#
+# OAUTHBEARER (see RFC 7628: https://tools.ietf.org/html/rfc7628)
+#
+
+
+def start_oauth_handshake(conn):
+ """
+ Negotiates an OAUTHBEARER SASL challenge. Returns the client's initial
+ response data.
+ """
+ startup = pq3.recv1(conn, cls=pq3.Startup)
+ assert startup.proto == pq3.protocol(3, 0)
+
+ pq3.send(
+ conn, pq3.types.AuthnRequest, type=pq3.authn.SASL, body=[b"OAUTHBEARER", b""]
+ )
+
+ pkt = pq3.recv1(conn)
+ assert pkt.type == pq3.types.PasswordMessage
+
+ initial = pq3.SASLInitialResponse.parse(pkt.payload)
+ assert initial.name == b"OAUTHBEARER"
+
+ return initial.data
+
+
+def get_auth_value(initial):
+ """
+ Finds the auth value (e.g. "Bearer somedata..." in the client's initial SASL
+ response.
+ """
+ kvpairs = initial.split(b"\x01")
+ assert kvpairs[0] == b"n,," # no channel binding or authzid
+ assert kvpairs[2] == b"" # ends with an empty kvpair
+ assert kvpairs[3] == b"" # ...and there's nothing after it
+ assert len(kvpairs) == 4
+
+ key, value = kvpairs[1].split(b"=", 2)
+ assert key == b"auth"
+
+ return value
+
+
+def xtest_oauth_success(conn): # TODO
+ initial = start_oauth_handshake(conn)
+
+ auth = get_auth_value(initial)
+ assert auth.startswith(b"Bearer ")
+
+ # Accept the token. TODO actually validate
+ pq3.send(conn, pq3.types.AuthnRequest, type=pq3.authn.SASLFinal)
+ finish_handshake(conn)
+
+
+class OpenIDProvider(threading.Thread):
+ """
+ A thread that runs a mock OpenID provider server.
+ """
+
+ def __init__(self, *, port):
+ super().__init__()
+
+ self.exception = None
+
+ addr = ("", port)
+ self.server = self._Server(addr, self._Handler)
+
+ # TODO: allow HTTPS only, somehow
+ oauth = self._OAuthState()
+ oauth.host = f"localhost:{port}"
+ oauth.issuer = f"http://localhost:{port}"
+
+ # The following endpoints are required to be advertised by providers,
+ # even though our chosen client implementation does not actually make
+ # use of them.
+ oauth.register_endpoint(
+ "authorization_endpoint", "POST", "/authorize", self._authorization_handler
+ )
+ oauth.register_endpoint("jwks_uri", "GET", "/keys", self._jwks_handler)
+
+ self.server.oauth = oauth
+
+ def run(self):
+ try:
+ self.server.serve_forever()
+ except Exception as e:
+ self.exception = e
+
+ def stop(self, timeout=BLOCKING_TIMEOUT):
+ """
+ Shuts down the server and joins its thread. Raises an exception if the
+ thread could not be joined, or if it threw an exception itself. Must
+ only be called once, after start().
+ """
+ self.server.shutdown()
+ self.join(timeout)
+
+ if self.is_alive():
+ raise TimeoutError("client thread did not handshake within the timeout")
+ elif self.exception:
+ e = self.exception
+ raise e
+
+ class _OAuthState(object):
+ def __init__(self):
+ self.endpoint_paths = {}
+ self._endpoints = {}
+
+ def register_endpoint(self, name, method, path, func):
+ if method not in self._endpoints:
+ self._endpoints[method] = {}
+
+ self._endpoints[method][path] = func
+ self.endpoint_paths[name] = path
+
+ def endpoint(self, method, path):
+ if method not in self._endpoints:
+ return None
+
+ return self._endpoints[method].get(path)
+
+ class _Server(http.server.HTTPServer):
+ def handle_error(self, request, addr):
+ self.shutdown_request(request)
+ raise
+
+ @staticmethod
+ def _jwks_handler(headers, params):
+ return 200, {"keys": []}
+
+ @staticmethod
+ def _authorization_handler(headers, params):
+ # We don't actually want this to be called during these tests -- we
+ # should be using the device authorization endpoint instead.
+ assert (
+ False
+ ), "authorization handler called instead of device authorization handler"
+
+ class _Handler(http.server.BaseHTTPRequestHandler):
+ timeout = BLOCKING_TIMEOUT
+
+ def _discovery_handler(self, headers, params):
+ oauth = self.server.oauth
+
+ doc = {
+ "issuer": oauth.issuer,
+ "response_types_supported": ["token"],
+ "subject_types_supported": ["public"],
+ "id_token_signing_alg_values_supported": ["RS256"],
+ }
+
+ for name, path in oauth.endpoint_paths.items():
+ doc[name] = oauth.issuer + path
+
+ return 200, doc
+
+ def _handle(self, *, params=None, handler=None):
+ oauth = self.server.oauth
+ assert self.headers["Host"] == oauth.host
+
+ if handler is None:
+ handler = oauth.endpoint(self.command, self.path)
+ assert (
+ handler is not None
+ ), f"no registered endpoint for {self.command} {self.path}"
+
+ code, resp = handler(self.headers, params)
+
+ self.send_response(code)
+ self.send_header("Content-Type", "application/json")
+ self.end_headers()
+
+ resp = json.dumps(resp)
+ resp = resp.encode("utf-8")
+ self.wfile.write(resp)
+
+ self.close_connection = True
+
+ def do_GET(self):
+ if self.path == "/.well-known/openid-configuration":
+ self._handle(handler=self._discovery_handler)
+ return
+
+ self._handle()
+
+ def _request_body(self):
+ length = self.headers["Content-Length"]
+
+ # Handle only an explicit content-length.
+ assert length is not None
+ length = int(length)
+
+ return self.rfile.read(length).decode("utf-8")
+
+ def do_POST(self):
+ assert self.headers["Content-Type"] == "application/x-www-form-urlencoded"
+
+ body = self._request_body()
+ params = urllib.parse.parse_qs(body)
+
+ self._handle(params=params)
+
+
[email protected]
+def openid_provider(unused_tcp_port_factory):
+ """
+ A fixture that returns the OAuth state of a running OpenID provider server. The
+ server will be stopped when the fixture is torn down.
+ """
+ thread = OpenIDProvider(port=unused_tcp_port_factory())
+ thread.start()
+
+ try:
+ yield thread.server.oauth
+ finally:
+ thread.stop()
+
+
[email protected]("secret", [None, "", "hunter2"])
[email protected]("scope", [None, "", "openid email"])
[email protected]("retries", [0, 1])
+def test_oauth_with_explicit_issuer(
+ capfd, accept, openid_provider, retries, scope, secret
+):
+ client_id = secrets.token_hex()
+
+ sock, client = accept(
+ oauth_issuer=openid_provider.issuer,
+ oauth_client_id=client_id,
+ oauth_client_secret=secret,
+ oauth_scope=scope,
+ )
+
+ device_code = secrets.token_hex()
+ user_code = f"{secrets.token_hex(2)}-{secrets.token_hex(2)}"
+ verification_url = "https://example.com/device"
+
+ access_token = secrets.token_urlsafe()
+
+ def check_client_authn(headers, params):
+ if not secret:
+ assert params["client_id"] == [client_id]
+ return
+
+ # Require the client to use Basic authn; request-body credentials are
+ # NOT RECOMMENDED (RFC 6749, Sec. 2.3.1).
+ assert "Authorization" in headers
+
+ method, creds = headers["Authorization"].split()
+ assert method == "Basic"
+
+ expected = f"{client_id}:{secret}"
+ assert base64.b64decode(creds) == expected.encode("ascii")
+
+ # Set up our provider callbacks.
+ # NOTE that these callbacks will be called on a background thread. Don't do
+ # any unprotected state mutation here.
+
+ def authorization_endpoint(headers, params):
+ check_client_authn(headers, params)
+
+ if scope:
+ assert params["scope"] == [scope]
+ else:
+ assert "scope" not in params
+
+ resp = {
+ "device_code": device_code,
+ "user_code": user_code,
+ "interval": 0,
+ "verification_uri": verification_url,
+ "expires_in": 5,
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "device_authorization_endpoint", "POST", "/device", authorization_endpoint
+ )
+
+ attempts = 0
+ retry_lock = threading.Lock()
+
+ def token_endpoint(headers, params):
+ check_client_authn(headers, params)
+
+ assert params["grant_type"] == ["urn:ietf:params:oauth:grant-type:device_code"]
+ assert params["device_code"] == [device_code]
+
+ now = time.monotonic()
+
+ with retry_lock:
+ nonlocal attempts
+
+ # If the test wants to force the client to retry, return an
+ # authorization_pending response and decrement the retry count.
+ if attempts < retries:
+ attempts += 1
+ return 400, {"error": "authorization_pending"}
+
+ # Successfully finish the request by sending the access bearer token.
+ resp = {
+ "access_token": access_token,
+ "token_type": "bearer",
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "token_endpoint", "POST", "/token", token_endpoint
+ )
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ # Initiate a handshake, which should result in the above endpoints
+ # being called.
+ initial = start_oauth_handshake(conn)
+
+ # Validate and accept the token.
+ auth = get_auth_value(initial)
+ assert auth == f"Bearer {access_token}".encode("ascii")
+
+ pq3.send(conn, pq3.types.AuthnRequest, type=pq3.authn.SASLFinal)
+ finish_handshake(conn)
+
+ if retries:
+ # Finally, make sure that the client prompted the user with the expected
+ # authorization URL and user code.
+ expected = f"Visit {verification_url} and enter the code: {user_code}"
+ _, stderr = capfd.readouterr()
+ assert expected in stderr
+
+
+def test_oauth_requires_client_id(accept, openid_provider):
+ sock, client = accept(
+ oauth_issuer=openid_provider.issuer,
+ # Do not set a client ID; this should cause a client error after the
+ # server asks for OAUTHBEARER and the client tries to contact the
+ # issuer.
+ )
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ # Initiate a handshake.
+ startup = pq3.recv1(conn, cls=pq3.Startup)
+ assert startup.proto == pq3.protocol(3, 0)
+
+ pq3.send(
+ conn,
+ pq3.types.AuthnRequest,
+ type=pq3.authn.SASL,
+ body=[b"OAUTHBEARER", b""],
+ )
+
+ # The client should disconnect at this point.
+ assert not conn.read()
+
+ expected_error = "no oauth_client_id is set"
+ with pytest.raises(psycopg2.OperationalError, match=expected_error):
+ client.check_completed()
+
+
[email protected]
[email protected]("error_code", ["authorization_pending", "slow_down"])
[email protected]("retries", [1, 2])
+def test_oauth_retry_interval(accept, openid_provider, retries, error_code):
+ sock, client = accept(
+ oauth_issuer=openid_provider.issuer,
+ oauth_client_id="some-id",
+ )
+
+ expected_retry_interval = 1
+ access_token = secrets.token_urlsafe()
+
+ # Set up our provider callbacks.
+ # NOTE that these callbacks will be called on a background thread. Don't do
+ # any unprotected state mutation here.
+
+ def authorization_endpoint(headers, params):
+ resp = {
+ "device_code": "my-device-code",
+ "user_code": "my-user-code",
+ "interval": expected_retry_interval,
+ "verification_uri": "https://example.com",
+ "expires_in": 5,
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "device_authorization_endpoint", "POST", "/device", authorization_endpoint
+ )
+
+ attempts = 0
+ last_retry = None
+ retry_lock = threading.Lock()
+
+ def token_endpoint(headers, params):
+ now = time.monotonic()
+
+ with retry_lock:
+ nonlocal attempts, last_retry, expected_retry_interval
+
+ # Make sure the retry interval is being respected by the client.
+ if last_retry is not None:
+ interval = now - last_retry
+ assert interval >= expected_retry_interval
+
+ last_retry = now
+
+ # If the test wants to force the client to retry, return the desired
+ # error response and decrement the retry count.
+ if attempts < retries:
+ attempts += 1
+
+ # A slow_down code requires the client to additionally increase
+ # its interval by five seconds.
+ if error_code == "slow_down":
+ expected_retry_interval += 5
+
+ return 400, {"error": error_code}
+
+ # Successfully finish the request by sending the access bearer token.
+ resp = {
+ "access_token": access_token,
+ "token_type": "bearer",
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "token_endpoint", "POST", "/token", token_endpoint
+ )
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ # Initiate a handshake, which should result in the above endpoints
+ # being called.
+ initial = start_oauth_handshake(conn)
+
+ # Validate and accept the token.
+ auth = get_auth_value(initial)
+ assert auth == f"Bearer {access_token}".encode("ascii")
+
+ pq3.send(conn, pq3.types.AuthnRequest, type=pq3.authn.SASLFinal)
+ finish_handshake(conn)
+
+
[email protected](
+ "failure_mode, error_pattern",
+ [
+ pytest.param(
+ {
+ "error": "invalid_client",
+ "error_description": "client authentication failed",
+ },
+ r"client authentication failed \(invalid_client\)",
+ id="authentication failure with description",
+ ),
+ pytest.param(
+ {"error": "invalid_request"},
+ r"\(invalid_request\)",
+ id="invalid request without description",
+ ),
+ pytest.param(
+ {},
+ r"failed to obtain device authorization",
+ id="broken error response",
+ ),
+ ],
+)
+def test_oauth_device_authorization_failures(
+ accept, openid_provider, failure_mode, error_pattern
+):
+ client_id = secrets.token_hex()
+
+ sock, client = accept(
+ oauth_issuer=openid_provider.issuer,
+ oauth_client_id=client_id,
+ )
+
+ # Set up our provider callbacks.
+ # NOTE that these callbacks will be called on a background thread. Don't do
+ # any unprotected state mutation here.
+
+ def authorization_endpoint(headers, params):
+ return 400, failure_mode
+
+ openid_provider.register_endpoint(
+ "device_authorization_endpoint", "POST", "/device", authorization_endpoint
+ )
+
+ def token_endpoint(headers, params):
+ assert False, "token endpoint was invoked unexpectedly"
+
+ openid_provider.register_endpoint(
+ "token_endpoint", "POST", "/token", token_endpoint
+ )
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ # Initiate a handshake, which should result in the above endpoints
+ # being called.
+ startup = pq3.recv1(conn, cls=pq3.Startup)
+ assert startup.proto == pq3.protocol(3, 0)
+
+ pq3.send(
+ conn,
+ pq3.types.AuthnRequest,
+ type=pq3.authn.SASL,
+ body=[b"OAUTHBEARER", b""],
+ )
+
+ # The client should not continue the connection due to the hardcoded
+ # provider failure; we disconnect here.
+
+ # Now make sure the client correctly failed.
+ with pytest.raises(psycopg2.OperationalError, match=error_pattern):
+ client.check_completed()
+
+
[email protected](
+ "failure_mode, error_pattern",
+ [
+ pytest.param(
+ {
+ "error": "expired_token",
+ "error_description": "the device code has expired",
+ },
+ r"the device code has expired \(expired_token\)",
+ id="expired token with description",
+ ),
+ pytest.param(
+ {"error": "access_denied"},
+ r"\(access_denied\)",
+ id="access denied without description",
+ ),
+ pytest.param(
+ {},
+ r"OAuth token retrieval failed",
+ id="broken error response",
+ ),
+ ],
+)
[email protected]("retries", [0, 1])
+def test_oauth_token_failures(
+ accept, openid_provider, retries, failure_mode, error_pattern
+):
+ client_id = secrets.token_hex()
+
+ sock, client = accept(
+ oauth_issuer=openid_provider.issuer,
+ oauth_client_id=client_id,
+ )
+
+ device_code = secrets.token_hex()
+ user_code = f"{secrets.token_hex(2)}-{secrets.token_hex(2)}"
+
+ # Set up our provider callbacks.
+ # NOTE that these callbacks will be called on a background thread. Don't do
+ # any unprotected state mutation here.
+
+ def authorization_endpoint(headers, params):
+ assert params["client_id"] == [client_id]
+
+ resp = {
+ "device_code": device_code,
+ "user_code": user_code,
+ "interval": 0,
+ "verification_uri": "https://example.com/device",
+ "expires_in": 5,
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "device_authorization_endpoint", "POST", "/device", authorization_endpoint
+ )
+
+ retry_lock = threading.Lock()
+
+ def token_endpoint(headers, params):
+ with retry_lock:
+ nonlocal retries
+
+ # If the test wants to force the client to retry, return an
+ # authorization_pending response and decrement the retry count.
+ if retries > 0:
+ retries -= 1
+ return 400, {"error": "authorization_pending"}
+
+ return 400, failure_mode
+
+ openid_provider.register_endpoint(
+ "token_endpoint", "POST", "/token", token_endpoint
+ )
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ # Initiate a handshake, which should result in the above endpoints
+ # being called.
+ startup = pq3.recv1(conn, cls=pq3.Startup)
+ assert startup.proto == pq3.protocol(3, 0)
+
+ pq3.send(
+ conn,
+ pq3.types.AuthnRequest,
+ type=pq3.authn.SASL,
+ body=[b"OAUTHBEARER", b""],
+ )
+
+ # The client should not continue the connection due to the hardcoded
+ # provider failure; we disconnect here.
+
+ # Now make sure the client correctly failed.
+ with pytest.raises(psycopg2.OperationalError, match=error_pattern):
+ client.check_completed()
+
+
[email protected]("scope", [None, "openid email"])
[email protected](
+ "base_response",
+ [
+ {"status": "invalid_token"},
+ {"extra_object": {"key": "value"}, "status": "invalid_token"},
+ {"extra_object": {"status": 1}, "status": "invalid_token"},
+ ],
+)
+def test_oauth_discovery(accept, openid_provider, base_response, scope):
+ sock, client = accept(oauth_client_id=secrets.token_hex())
+
+ device_code = secrets.token_hex()
+ user_code = f"{secrets.token_hex(2)}-{secrets.token_hex(2)}"
+ verification_url = "https://example.com/device"
+
+ access_token = secrets.token_urlsafe()
+
+ # Set up our provider callbacks.
+ # NOTE that these callbacks will be called on a background thread. Don't do
+ # any unprotected state mutation here.
+
+ def authorization_endpoint(headers, params):
+ if scope:
+ assert params["scope"] == [scope]
+ else:
+ assert "scope" not in params
+
+ resp = {
+ "device_code": device_code,
+ "user_code": user_code,
+ "interval": 0,
+ "verification_uri": verification_url,
+ "expires_in": 5,
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "device_authorization_endpoint", "POST", "/device", authorization_endpoint
+ )
+
+ def token_endpoint(headers, params):
+ assert params["grant_type"] == ["urn:ietf:params:oauth:grant-type:device_code"]
+ assert params["device_code"] == [device_code]
+
+ # Successfully finish the request by sending the access bearer token.
+ resp = {
+ "access_token": access_token,
+ "token_type": "bearer",
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "token_endpoint", "POST", "/token", token_endpoint
+ )
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ initial = start_oauth_handshake(conn)
+
+ # For discovery, the client should send an empty auth header. See
+ # RFC 7628, Sec. 4.3.
+ auth = get_auth_value(initial)
+ assert auth == b""
+
+ # We will fail the first SASL exchange. First return a link to the
+ # discovery document, pointing to the test provider server.
+ resp = dict(base_response)
+
+ discovery_uri = f"{openid_provider.issuer}/.well-known/openid-configuration"
+ resp["openid-configuration"] = discovery_uri
+
+ if scope:
+ resp["scope"] = scope
+
+ resp = json.dumps(resp)
+
+ pq3.send(
+ conn,
+ pq3.types.AuthnRequest,
+ type=pq3.authn.SASLContinue,
+ body=resp.encode("ascii"),
+ )
+
+ # Per RFC, the client is required to send a dummy ^A response.
+ pkt = pq3.recv1(conn)
+ assert pkt.type == pq3.types.PasswordMessage
+ assert pkt.payload == b"\x01"
+
+ # Now fail the SASL exchange.
+ pq3.send(
+ conn,
+ pq3.types.ErrorResponse,
+ fields=[
+ b"SFATAL",
+ b"C28000",
+ b"Mdoesn't matter",
+ b"",
+ ],
+ )
+
+ # The client will connect to us a second time, using the parameters we sent
+ # it.
+ sock, _ = accept()
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ initial = start_oauth_handshake(conn)
+
+ # Validate and accept the token.
+ auth = get_auth_value(initial)
+ assert auth == f"Bearer {access_token}".encode("ascii")
+
+ pq3.send(conn, pq3.types.AuthnRequest, type=pq3.authn.SASLFinal)
+ finish_handshake(conn)
+
+
[email protected](
+ "response,expected_error",
+ [
+ pytest.param(
+ "abcde",
+ 'Token "abcde" is invalid',
+ id="bad JSON: invalid syntax",
+ ),
+ pytest.param(
+ '"abcde"',
+ "top-level element must be an object",
+ id="bad JSON: top-level element is a string",
+ ),
+ pytest.param(
+ "[]",
+ "top-level element must be an object",
+ id="bad JSON: top-level element is an array",
+ ),
+ pytest.param(
+ "{}",
+ "server sent error response without a status",
+ id="bad JSON: no status member",
+ ),
+ pytest.param(
+ '{ "status": null }',
+ 'field "status" must be a string',
+ id="bad JSON: null status member",
+ ),
+ pytest.param(
+ '{ "status": 0 }',
+ 'field "status" must be a string',
+ id="bad JSON: int status member",
+ ),
+ pytest.param(
+ '{ "status": [ "bad" ] }',
+ 'field "status" must be a string',
+ id="bad JSON: array status member",
+ ),
+ pytest.param(
+ '{ "status": { "bad": "bad" } }',
+ 'field "status" must be a string',
+ id="bad JSON: object status member",
+ ),
+ pytest.param(
+ '{ "nested": { "status": "bad" } }',
+ "server sent error response without a status",
+ id="bad JSON: nested status",
+ ),
+ pytest.param(
+ '{ "status": "invalid_token" ',
+ "The input string ended unexpectedly",
+ id="bad JSON: unterminated object",
+ ),
+ pytest.param(
+ '{ "status": "invalid_token" } { }',
+ 'Expected end of input, but found "{"',
+ id="bad JSON: trailing data",
+ ),
+ pytest.param(
+ '{ "status": "invalid_token", "openid-configuration": 1 }',
+ 'field "openid-configuration" must be a string',
+ id="bad JSON: int openid-configuration member",
+ ),
+ pytest.param(
+ '{ "status": "invalid_token", "openid-configuration": 1 }',
+ 'field "openid-configuration" must be a string',
+ id="bad JSON: int openid-configuration member",
+ ),
+ pytest.param(
+ '{ "status": "invalid_token", "scope": 1 }',
+ 'field "scope" must be a string',
+ id="bad JSON: int scope member",
+ ),
+ ],
+)
+def test_oauth_discovery_server_error(accept, response, expected_error):
+ sock, client = accept(oauth_client_id=secrets.token_hex())
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ initial = start_oauth_handshake(conn)
+
+ # Fail the SASL exchange with an invalid JSON response.
+ pq3.send(
+ conn,
+ pq3.types.AuthnRequest,
+ type=pq3.authn.SASLContinue,
+ body=response.encode("utf-8"),
+ )
+
+ # The client should disconnect, so the socket is closed here. (If
+ # the client doesn't disconnect, it will report a different error
+ # below and the test will fail.)
+
+ with pytest.raises(psycopg2.OperationalError, match=expected_error):
+ client.check_completed()
+
+
[email protected](
+ "sasl_err,resp_type,resp_payload,expected_error",
+ [
+ pytest.param(
+ {"status": "invalid_request"},
+ pq3.types.ErrorResponse,
+ dict(
+ fields=[b"SFATAL", b"C28000", b"Mexpected error message", b""],
+ ),
+ "expected error message",
+ id="standard server error: invalid_request",
+ ),
+ pytest.param(
+ {"status": "invalid_token"},
+ pq3.types.ErrorResponse,
+ dict(
+ fields=[b"SFATAL", b"C28000", b"Mexpected error message", b""],
+ ),
+ "expected error message",
+ id="standard server error: invalid_token without discovery URI",
+ ),
+ pytest.param(
+ {"status": "invalid_request"},
+ pq3.types.AuthnRequest,
+ dict(type=pq3.authn.SASLContinue, body=b""),
+ "server sent additional OAuth data",
+ id="broken server: additional challenge after error",
+ ),
+ pytest.param(
+ {"status": "invalid_request"},
+ pq3.types.AuthnRequest,
+ dict(type=pq3.authn.SASLFinal),
+ "server sent additional OAuth data",
+ id="broken server: SASL success after error",
+ ),
+ ],
+)
+def test_oauth_server_error(accept, sasl_err, resp_type, resp_payload, expected_error):
+ sock, client = accept()
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ start_oauth_handshake(conn)
+
+ # Ignore the client data. Return an error "challenge".
+ resp = json.dumps(sasl_err)
+ resp = resp.encode("utf-8")
+
+ pq3.send(
+ conn, pq3.types.AuthnRequest, type=pq3.authn.SASLContinue, body=resp
+ )
+
+ # Per RFC, the client is required to send a dummy ^A response.
+ pkt = pq3.recv1(conn)
+ assert pkt.type == pq3.types.PasswordMessage
+ assert pkt.payload == b"\x01"
+
+ # Now fail the SASL exchange (in either a valid way, or an invalid
+ # one, depending on the test).
+ pq3.send(conn, resp_type, **resp_payload)
+
+ with pytest.raises(psycopg2.OperationalError, match=expected_error):
+ client.check_completed()
diff --git a/src/test/python/pq3.py b/src/test/python/pq3.py
new file mode 100644
index 0000000000..3a22dad0b6
--- /dev/null
+++ b/src/test/python/pq3.py
@@ -0,0 +1,727 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import contextlib
+import getpass
+import io
+import os
+import ssl
+import sys
+import textwrap
+
+from construct import *
+
+import tls
+
+
+def protocol(major, minor):
+ """
+ Returns the protocol version, in integer format, corresponding to the given
+ major and minor version numbers.
+ """
+ return (major << 16) | minor
+
+
+# Startup
+
+StringList = GreedyRange(NullTerminated(GreedyBytes))
+
+
+class KeyValueAdapter(Adapter):
+ """
+ Turns a key-value store into a null-terminated list of null-terminated
+ strings, as presented on the wire in the startup packet.
+ """
+
+ def _encode(self, obj, context, path):
+ if isinstance(obj, list):
+ return obj
+
+ l = []
+
+ for k, v in obj.items():
+ if isinstance(k, str):
+ k = k.encode("utf-8")
+ l.append(k)
+
+ if isinstance(v, str):
+ v = v.encode("utf-8")
+ l.append(v)
+
+ l.append(b"")
+ return l
+
+ def _decode(self, obj, context, path):
+ # TODO: turn a list back into a dict
+ return obj
+
+
+KeyValues = KeyValueAdapter(StringList)
+
+_startup_payload = Switch(
+ this.proto,
+ {
+ protocol(3, 0): KeyValues,
+ },
+ default=GreedyBytes,
+)
+
+
+def _default_protocol(this):
+ try:
+ if isinstance(this.payload, (list, dict)):
+ return protocol(3, 0)
+ except AttributeError:
+ pass # no payload passed during build
+
+ return 0
+
+
+def _startup_payload_len(this):
+ """
+ The payload field has a fixed size based on the length of the packet. But
+ if the caller hasn't supplied an explicit length at build time, we have to
+ build the payload to figure out how long it is, which requires us to know
+ the length first... This function exists solely to break the cycle.
+ """
+ assert this._building, "_startup_payload_len() cannot be called during parsing"
+
+ try:
+ payload = this.payload
+ except AttributeError:
+ return 0 # no payload
+
+ if isinstance(payload, bytes):
+ # already serialized; just use the given length
+ return len(payload)
+
+ try:
+ proto = this.proto
+ except AttributeError:
+ proto = _default_protocol(this)
+
+ data = _startup_payload.build(payload, proto=proto)
+ return len(data)
+
+
+Startup = Struct(
+ "len" / Default(Int32sb, lambda this: _startup_payload_len(this) + 8),
+ "proto" / Default(Hex(Int32sb), _default_protocol),
+ "payload" / FixedSized(this.len - 8, Default(_startup_payload, b"")),
+)
+
+# Pq3
+
+# Adapted from construct.core.EnumIntegerString
+class EnumNamedByte:
+ def __init__(self, val, name):
+ self._val = val
+ self._name = name
+
+ def __int__(self):
+ return ord(self._val)
+
+ def __str__(self):
+ return "(enum) %s %r" % (self._name, self._val)
+
+ def __repr__(self):
+ return "EnumNamedByte(%r)" % self._val
+
+ def __eq__(self, other):
+ if isinstance(other, EnumNamedByte):
+ other = other._val
+ if not isinstance(other, bytes):
+ return NotImplemented
+
+ return self._val == other
+
+ def __hash__(self):
+ return hash(self._val)
+
+
+# Adapted from construct.core.Enum
+class ByteEnum(Adapter):
+ def __init__(self, **mapping):
+ super(ByteEnum, self).__init__(Byte)
+ self.namemapping = {k: EnumNamedByte(v, k) for k, v in mapping.items()}
+ self.decmapping = {v: EnumNamedByte(v, k) for k, v in mapping.items()}
+
+ def __getattr__(self, name):
+ if name in self.namemapping:
+ return self.decmapping[self.namemapping[name]]
+ raise AttributeError
+
+ def _decode(self, obj, context, path):
+ b = bytes([obj])
+ try:
+ return self.decmapping[b]
+ except KeyError:
+ return EnumNamedByte(b, "(unknown)")
+
+ def _encode(self, obj, context, path):
+ if isinstance(obj, int):
+ return obj
+ elif isinstance(obj, bytes):
+ return ord(obj)
+ return int(obj)
+
+
+types = ByteEnum(
+ ErrorResponse=b"E",
+ ReadyForQuery=b"Z",
+ Query=b"Q",
+ EmptyQueryResponse=b"I",
+ AuthnRequest=b"R",
+ PasswordMessage=b"p",
+ BackendKeyData=b"K",
+ CommandComplete=b"C",
+ ParameterStatus=b"S",
+ DataRow=b"D",
+ Terminate=b"X",
+)
+
+
+authn = Enum(
+ Int32ub,
+ OK=0,
+ SASL=10,
+ SASLContinue=11,
+ SASLFinal=12,
+)
+
+
+_authn_body = Switch(
+ this.type,
+ {
+ authn.OK: Terminated,
+ authn.SASL: StringList,
+ },
+ default=GreedyBytes,
+)
+
+
+def _data_len(this):
+ assert this._building, "_data_len() cannot be called during parsing"
+
+ if not hasattr(this, "data") or this.data is None:
+ return -1
+
+ return len(this.data)
+
+
+# The protocol reuses the PasswordMessage for several authentication response
+# types, and there's no good way to figure out which is which without keeping
+# state for the entire stream. So this is a separate Construct that can be
+# explicitly parsed/built by code that knows it's needed.
+SASLInitialResponse = Struct(
+ "name" / NullTerminated(GreedyBytes),
+ "len" / Default(Int32sb, lambda this: _data_len(this)),
+ "data"
+ / IfThenElse(
+ # Allow tests to explicitly pass an incorrect length during testing, by
+ # not enforcing a FixedSized during build. (The len calculation above
+ # defaults to the correct size.)
+ this._building,
+ Optional(GreedyBytes),
+ If(this.len != -1, Default(FixedSized(this.len, GreedyBytes), b"")),
+ ),
+ Terminated, # make sure the entire response is consumed
+)
+
+
+_column = FocusedSeq(
+ "data",
+ "len" / Default(Int32sb, lambda this: _data_len(this)),
+ "data" / If(this.len != -1, FixedSized(this.len, GreedyBytes)),
+)
+
+
+_payload_map = {
+ types.ErrorResponse: Struct("fields" / StringList),
+ types.ReadyForQuery: Struct("status" / Bytes(1)),
+ types.Query: Struct("query" / NullTerminated(GreedyBytes)),
+ types.EmptyQueryResponse: Terminated,
+ types.AuthnRequest: Struct("type" / authn, "body" / Default(_authn_body, b"")),
+ types.BackendKeyData: Struct("pid" / Int32ub, "key" / Hex(Int32ub)),
+ types.CommandComplete: Struct("tag" / NullTerminated(GreedyBytes)),
+ types.ParameterStatus: Struct(
+ "name" / NullTerminated(GreedyBytes), "value" / NullTerminated(GreedyBytes)
+ ),
+ types.DataRow: Struct("columns" / Default(PrefixedArray(Int16sb, _column), b"")),
+ types.Terminate: Terminated,
+}
+
+
+_payload = FocusedSeq(
+ "_payload",
+ "_payload"
+ / Switch(
+ this._.type,
+ _payload_map,
+ default=GreedyBytes,
+ ),
+ Terminated, # make sure every payload consumes the entire packet
+)
+
+
+def _payload_len(this):
+ """
+ See _startup_payload_len() for an explanation.
+ """
+ assert this._building, "_payload_len() cannot be called during parsing"
+
+ try:
+ payload = this.payload
+ except AttributeError:
+ return 0 # no payload
+
+ if isinstance(payload, bytes):
+ # already serialized; just use the given length
+ return len(payload)
+
+ data = _payload.build(payload, type=this.type)
+ return len(data)
+
+
+Pq3 = Struct(
+ "type" / types,
+ "len" / Default(Int32ub, lambda this: _payload_len(this) + 4),
+ "payload" / FixedSized(this.len - 4, Default(_payload, b"")),
+)
+
+
+# Environment
+
+
+def pghost():
+ return os.environ.get("PGHOST", default="localhost")
+
+
+def pgport():
+ return int(os.environ.get("PGPORT", default=5432))
+
+
+def pguser():
+ try:
+ return os.environ["PGUSER"]
+ except KeyError:
+ return getpass.getuser()
+
+
+def pgdatabase():
+ return os.environ.get("PGDATABASE", default="postgres")
+
+
+# Connections
+
+
+def _hexdump_translation_map():
+ """
+ For hexdumps. Translates any unprintable or non-ASCII bytes into '.'.
+ """
+ input = bytearray()
+
+ for i in range(128):
+ c = chr(i)
+
+ if not c.isprintable():
+ input += bytes([i])
+
+ input += bytes(range(128, 256))
+
+ return bytes.maketrans(input, b"." * len(input))
+
+
+class _DebugStream(object):
+ """
+ Wraps a file-like object and adds hexdumps of the read and write data. Call
+ end_packet() on a _DebugStream to write the accumulated hexdumps to the
+ output stream, along with the packet that was sent.
+ """
+
+ _translation_map = _hexdump_translation_map()
+
+ def __init__(self, stream, out=sys.stdout):
+ """
+ Creates a new _DebugStream wrapping the given stream (which must have
+ been created by wrap()). All attributes not provided by the _DebugStream
+ are delegated to the wrapped stream. out is the text stream to which
+ hexdumps are written.
+ """
+ self.raw = stream
+ self._out = out
+ self._rbuf = io.BytesIO()
+ self._wbuf = io.BytesIO()
+
+ def __getattr__(self, name):
+ return getattr(self.raw, name)
+
+ def __setattr__(self, name, value):
+ if name in ("raw", "_out", "_rbuf", "_wbuf"):
+ return object.__setattr__(self, name, value)
+
+ setattr(self.raw, name, value)
+
+ def read(self, *args, **kwargs):
+ buf = self.raw.read(*args, **kwargs)
+
+ self._rbuf.write(buf)
+ return buf
+
+ def write(self, b):
+ self._wbuf.write(b)
+ return self.raw.write(b)
+
+ def recv(self, *args):
+ buf = self.raw.recv(*args)
+
+ self._rbuf.write(buf)
+ return buf
+
+ def _flush(self, buf, prefix):
+ width = 16
+ hexwidth = width * 3 - 1
+
+ count = 0
+ buf.seek(0)
+
+ while True:
+ line = buf.read(16)
+
+ if not line:
+ if count:
+ self._out.write("\n") # separate the output block with a newline
+ return
+
+ self._out.write("%s %04X:\t" % (prefix, count))
+ self._out.write("%*s\t" % (-hexwidth, line.hex(" ")))
+ self._out.write(line.translate(self._translation_map).decode("ascii"))
+ self._out.write("\n")
+
+ count += 16
+
+ def print_debug(self, obj, *, prefix=""):
+ contents = ""
+ if obj is not None:
+ contents = str(obj)
+
+ for line in contents.splitlines():
+ self._out.write("%s%s\n" % (prefix, line))
+
+ self._out.write("\n")
+
+ def flush_debug(self, *, prefix=""):
+ self._flush(self._rbuf, prefix + "<")
+ self._rbuf = io.BytesIO()
+
+ self._flush(self._wbuf, prefix + ">")
+ self._wbuf = io.BytesIO()
+
+ def end_packet(self, pkt, *, read=False, prefix="", indent=" "):
+ """
+ Marks the end of a logical "packet" of data. A string representation of
+ pkt will be printed, and the debug buffers will be flushed with an
+ indent. All lines can be optionally prefixed.
+
+ If read is True, the packet representation is written after the debug
+ buffers; otherwise the default of False (meaning write) causes the
+ packet representation to be dumped first. This is meant to capture the
+ logical flow of layer translation.
+ """
+ write = not read
+
+ if write:
+ self.print_debug(pkt, prefix=prefix + "> ")
+
+ self.flush_debug(prefix=prefix + indent)
+
+ if read:
+ self.print_debug(pkt, prefix=prefix + "< ")
+
+
[email protected]
+def wrap(socket, *, debug_stream=None):
+ """
+ Transforms a raw socket into a connection that can be used for Construct
+ building and parsing. The return value is a context manager and can be used
+ in a with statement.
+ """
+ # It is critical that buffering be disabled here, so that we can still
+ # manipulate the raw socket without desyncing the stream.
+ with socket.makefile("rwb", buffering=0) as sfile:
+ # Expose the original socket's recv() on the SocketIO object we return.
+ def recv(self, *args):
+ return socket.recv(*args)
+
+ sfile.recv = recv.__get__(sfile)
+
+ conn = sfile
+ if debug_stream:
+ conn = _DebugStream(conn, debug_stream)
+
+ try:
+ yield conn
+ finally:
+ if debug_stream:
+ conn.flush_debug(prefix="? ")
+
+
+def _send(stream, cls, obj):
+ debugging = hasattr(stream, "flush_debug")
+ out = io.BytesIO()
+
+ # Ideally we would build directly to the passed stream, but because we need
+ # to reparse the generated output for the debugging case, build to an
+ # intermediate BytesIO and send it instead.
+ cls.build_stream(obj, out)
+ buf = out.getvalue()
+
+ stream.write(buf)
+ if debugging:
+ pkt = cls.parse(buf)
+ stream.end_packet(pkt)
+
+ stream.flush()
+
+
+def send(stream, packet_type, payload_data=None, **payloadkw):
+ """
+ Sends a packet on the given pq3 connection. type is the pq3.types member
+ that should be assigned to the packet. If payload_data is given, it will be
+ used as the packet payload; otherwise the key/value pairs in payloadkw will
+ be the payload contents.
+ """
+ data = payloadkw
+
+ if payload_data is not None:
+ if payloadkw:
+ raise ValueError(
+ "payload_data and payload keywords may not be used simultaneously"
+ )
+
+ data = payload_data
+
+ _send(stream, Pq3, dict(type=packet_type, payload=data))
+
+
+def send_startup(stream, proto=None, **kwargs):
+ """
+ Sends a startup packet on the given pq3 connection. In most cases you should
+ use the handshake functions instead, which will do this for you.
+
+ By default, a protocol version 3 packet will be sent. This can be overridden
+ with the proto parameter.
+ """
+ pkt = {}
+
+ if proto is not None:
+ pkt["proto"] = proto
+ if kwargs:
+ pkt["payload"] = kwargs
+
+ _send(stream, Startup, pkt)
+
+
+def recv1(stream, *, cls=Pq3):
+ """
+ Receives a single pq3 packet from the given stream and returns it.
+ """
+ resp = cls.parse_stream(stream)
+
+ debugging = hasattr(stream, "flush_debug")
+ if debugging:
+ stream.end_packet(resp, read=True)
+
+ return resp
+
+
+def handshake(stream, **kwargs):
+ """
+ Performs a libpq v3 startup handshake. kwargs should contain the key/value
+ parameters to send to the server in the startup packet.
+ """
+ # Send our startup parameters.
+ send_startup(stream, **kwargs)
+
+ # Receive and dump packets until the server indicates it's ready for our
+ # first query.
+ while True:
+ resp = recv1(stream)
+ if resp is None:
+ raise RuntimeError("server closed connection during handshake")
+
+ if resp.type == types.ReadyForQuery:
+ return
+ elif resp.type == types.ErrorResponse:
+ raise RuntimeError(
+ f"received error response from peer: {resp.payload.fields!r}"
+ )
+
+
+# TLS
+
+
+class _TLSStream(object):
+ """
+ A file-like object that performs TLS encryption/decryption on a wrapped
+ stream. Differs from ssl.SSLSocket in that we have full visibility and
+ control over the TLS layer.
+ """
+
+ def __init__(self, stream, context):
+ self._stream = stream
+ self._debugging = hasattr(stream, "flush_debug")
+
+ self._in = ssl.MemoryBIO()
+ self._out = ssl.MemoryBIO()
+ self._ssl = context.wrap_bio(self._in, self._out)
+
+ def handshake(self):
+ try:
+ self._pump(lambda: self._ssl.do_handshake())
+ finally:
+ self._flush_debug(prefix="? ")
+
+ def read(self, *args):
+ return self._pump(lambda: self._ssl.read(*args))
+
+ def write(self, *args):
+ return self._pump(lambda: self._ssl.write(*args))
+
+ def _decode(self, buf):
+ """
+ Attempts to decode a buffer of TLS data into a packet representation
+ that can be printed.
+
+ TODO: handle buffers (and record fragments) that don't align with packet
+ boundaries.
+ """
+ end = len(buf)
+ bio = io.BytesIO(buf)
+
+ ret = io.StringIO()
+
+ while bio.tell() < end:
+ record = tls.Plaintext.parse_stream(bio)
+
+ if ret.tell() > 0:
+ ret.write("\n")
+ ret.write("[Record] ")
+ ret.write(str(record))
+ ret.write("\n")
+
+ if record.type == tls.ContentType.handshake:
+ record_cls = tls.Handshake
+ else:
+ continue
+
+ innerlen = len(record.fragment)
+ inner = io.BytesIO(record.fragment)
+
+ while inner.tell() < innerlen:
+ msg = record_cls.parse_stream(inner)
+
+ indented = "[Message] " + str(msg)
+ indented = textwrap.indent(indented, " ")
+
+ ret.write("\n")
+ ret.write(indented)
+ ret.write("\n")
+
+ return ret.getvalue()
+
+ def flush(self):
+ if not self._out.pending:
+ self._stream.flush()
+ return
+
+ buf = self._out.read()
+ self._stream.write(buf)
+
+ if self._debugging:
+ pkt = self._decode(buf)
+ self._stream.end_packet(pkt, prefix=" ")
+
+ self._stream.flush()
+
+ def _pump(self, operation):
+ while True:
+ try:
+ return operation()
+ except (ssl.SSLWantReadError, ssl.SSLWantWriteError) as e:
+ want = e
+ self._read_write(want)
+
+ def _recv(self, maxsize):
+ buf = self._stream.recv(4096)
+ if not buf:
+ self._in.write_eof()
+ return
+
+ self._in.write(buf)
+
+ if not self._debugging:
+ return
+
+ pkt = self._decode(buf)
+ self._stream.end_packet(pkt, read=True, prefix=" ")
+
+ def _read_write(self, want):
+ # XXX This needs work. So many corner cases yet to handle. For one,
+ # doing blocking writes in flush may lead to distributed deadlock if the
+ # peer is already blocking on its writes.
+
+ if isinstance(want, ssl.SSLWantWriteError):
+ assert self._out.pending, "SSL backend wants write without data"
+
+ self.flush()
+
+ if isinstance(want, ssl.SSLWantReadError):
+ self._recv(4096)
+
+ def _flush_debug(self, prefix):
+ if not self._debugging:
+ return
+
+ self._stream.flush_debug(prefix=prefix)
+
+
[email protected]
+def tls_handshake(stream, context):
+ """
+ Performs a TLS handshake over the given stream (which must have been created
+ via a call to wrap()), and returns a new stream which transparently tunnels
+ data over the TLS connection.
+
+ If the passed stream has debugging enabled, the returned stream will also
+ have debugging, using the same output IO.
+ """
+ debugging = hasattr(stream, "flush_debug")
+
+ # Send our startup parameters.
+ send_startup(stream, proto=protocol(1234, 5679))
+
+ # Look at the SSL response.
+ resp = stream.read(1)
+ if debugging:
+ stream.flush_debug(prefix=" ")
+
+ if resp == b"N":
+ raise RuntimeError("server does not support SSLRequest")
+ if resp != b"S":
+ raise RuntimeError(f"unexpected response of type {resp!r} during TLS startup")
+
+ tls = _TLSStream(stream, context)
+ tls.handshake()
+
+ if debugging:
+ tls = _DebugStream(tls, stream._out)
+
+ try:
+ yield tls
+ # TODO: teardown/unwrap the connection?
+ finally:
+ if debugging:
+ tls.flush_debug(prefix="? ")
diff --git a/src/test/python/pytest.ini b/src/test/python/pytest.ini
new file mode 100644
index 0000000000..ab7a6e7fb9
--- /dev/null
+++ b/src/test/python/pytest.ini
@@ -0,0 +1,4 @@
+[pytest]
+
+markers =
+ slow: mark test as slow
diff --git a/src/test/python/requirements.txt b/src/test/python/requirements.txt
new file mode 100644
index 0000000000..32f105ea84
--- /dev/null
+++ b/src/test/python/requirements.txt
@@ -0,0 +1,7 @@
+black
+cryptography~=3.4.6
+construct~=2.10.61
+isort~=5.6
+psycopg2~=2.8.6
+pytest~=6.1
+pytest-asyncio~=0.14.0
diff --git a/src/test/python/server/__init__.py b/src/test/python/server/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/test/python/server/conftest.py b/src/test/python/server/conftest.py
new file mode 100644
index 0000000000..ba7342a453
--- /dev/null
+++ b/src/test/python/server/conftest.py
@@ -0,0 +1,45 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import contextlib
+import socket
+import sys
+
+import pytest
+
+import pq3
+
+
[email protected]
+def connect():
+ """
+ A factory fixture that, when called, returns a socket connected to a
+ Postgres server, wrapped in a pq3 connection. The calling test will be
+ skipped automatically if a server is not running at PGHOST:PGPORT, so it's
+ best to connect as soon as possible after the test case begins, to avoid
+ doing unnecessary work.
+ """
+ # Set up an ExitStack to handle safe cleanup of all of the moving pieces.
+ with contextlib.ExitStack() as stack:
+
+ def conn_factory():
+ addr = (pq3.pghost(), pq3.pgport())
+
+ try:
+ sock = socket.create_connection(addr, timeout=2)
+ except ConnectionError as e:
+ pytest.skip(f"unable to connect to {addr}: {e}")
+
+ # Have ExitStack close our socket.
+ stack.enter_context(sock)
+
+ # Wrap the connection in a pq3 layer and have ExitStack clean it up
+ # too.
+ wrap_ctx = pq3.wrap(sock, debug_stream=sys.stdout)
+ conn = stack.enter_context(wrap_ctx)
+
+ return conn
+
+ yield conn_factory
diff --git a/src/test/python/server/test_oauth.py b/src/test/python/server/test_oauth.py
new file mode 100644
index 0000000000..cb5ca7fa23
--- /dev/null
+++ b/src/test/python/server/test_oauth.py
@@ -0,0 +1,1012 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import base64
+import contextlib
+import json
+import os
+import pathlib
+import secrets
+import shlex
+import shutil
+import socket
+import struct
+from multiprocessing import shared_memory
+
+import psycopg2
+import pytest
+from psycopg2 import sql
+
+import pq3
+
+MAX_SASL_MESSAGE_LENGTH = 65535
+
+INVALID_AUTHORIZATION_ERRCODE = b"28000"
+PROTOCOL_VIOLATION_ERRCODE = b"08P01"
+FEATURE_NOT_SUPPORTED_ERRCODE = b"0A000"
+
+SHARED_MEM_NAME = "oauth-pytest"
+MAX_TOKEN_SIZE = 4096
+MAX_UINT16 = 2 ** 16 - 1
+
+
+def skip_if_no_postgres():
+ """
+ Used by the oauth_ctx fixture to skip this test module if no Postgres server
+ is running.
+
+ This logic is nearly duplicated with the conn fixture. Ideally oauth_ctx
+ would depend on that, but a module-scope fixture can't depend on a
+ test-scope fixture, and we haven't reached the rule of three yet.
+ """
+ addr = (pq3.pghost(), pq3.pgport())
+
+ try:
+ with socket.create_connection(addr, timeout=2):
+ pass
+ except ConnectionError as e:
+ pytest.skip(f"unable to connect to {addr}: {e}")
+
+
[email protected]
+def prepend_file(path, lines):
+ """
+ A context manager that prepends a file on disk with the desired lines of
+ text. When the context manager is exited, the file will be restored to its
+ original contents.
+ """
+ # First make a backup of the original file.
+ bak = path + ".bak"
+ shutil.copy2(path, bak)
+
+ try:
+ # Write the new lines, followed by the original file content.
+ with open(path, "w") as new, open(bak, "r") as orig:
+ new.writelines(lines)
+ shutil.copyfileobj(orig, new)
+
+ # Return control to the calling code.
+ yield
+
+ finally:
+ # Put the backup back into place.
+ os.replace(bak, path)
+
+
[email protected](scope="module")
+def oauth_ctx():
+ """
+ Creates a database and user that use the oauth auth method. The context
+ object contains the dbname and user attributes as strings to be used during
+ connection, as well as the issuer and scope that have been set in the HBA
+ configuration.
+
+ This fixture assumes that the standard PG* environment variables point to a
+ server running on a local machine, and that the PGUSER has rights to create
+ databases and roles.
+ """
+ skip_if_no_postgres() # don't bother running these tests without a server
+
+ id = secrets.token_hex(4)
+
+ class Context:
+ dbname = "oauth_test_" + id
+
+ user = "oauth_user_" + id
+ map_user = "oauth_map_user_" + id
+ authz_user = "oauth_authz_user_" + id
+
+ issuer = "https://example.com/" + id
+ scope = "openid " + id
+
+ ctx = Context()
+ hba_lines = (
+ f'host {ctx.dbname} {ctx.map_user} samehost oauth issuer="{ctx.issuer}" scope="{ctx.scope}" map=oauth\n',
+ f'host {ctx.dbname} {ctx.authz_user} samehost oauth issuer="{ctx.issuer}" scope="{ctx.scope}" trust_validator_authz=1\n',
+ f'host {ctx.dbname} all samehost oauth issuer="{ctx.issuer}" scope="{ctx.scope}"\n',
+ )
+ ident_lines = (r"oauth /^(.*)@example\.com$ \1",)
+
+ conn = psycopg2.connect("")
+ conn.autocommit = True
+
+ with contextlib.closing(conn):
+ c = conn.cursor()
+
+ # Create our roles and database.
+ user = sql.Identifier(ctx.user)
+ map_user = sql.Identifier(ctx.map_user)
+ authz_user = sql.Identifier(ctx.authz_user)
+ dbname = sql.Identifier(ctx.dbname)
+
+ c.execute(sql.SQL("CREATE ROLE {} LOGIN;").format(user))
+ c.execute(sql.SQL("CREATE ROLE {} LOGIN;").format(map_user))
+ c.execute(sql.SQL("CREATE ROLE {} LOGIN;").format(authz_user))
+ c.execute(sql.SQL("CREATE DATABASE {};").format(dbname))
+
+ # Make this test script the server's oauth_validator.
+ path = pathlib.Path(__file__).parent / "validate_bearer.py"
+ path = str(path.absolute())
+
+ cmd = f"{shlex.quote(path)} {SHARED_MEM_NAME} <&%f"
+ c.execute("ALTER SYSTEM SET oauth_validator_command TO %s;", (cmd,))
+
+ # Replace pg_hba and pg_ident.
+ c.execute("SHOW hba_file;")
+ hba = c.fetchone()[0]
+
+ c.execute("SHOW ident_file;")
+ ident = c.fetchone()[0]
+
+ with prepend_file(hba, hba_lines), prepend_file(ident, ident_lines):
+ c.execute("SELECT pg_reload_conf();")
+
+ # Use the new database and user.
+ yield ctx
+
+ # Put things back the way they were.
+ c.execute("SELECT pg_reload_conf();")
+
+ c.execute("ALTER SYSTEM RESET oauth_validator_command;")
+ c.execute(sql.SQL("DROP DATABASE {};").format(dbname))
+ c.execute(sql.SQL("DROP ROLE {};").format(authz_user))
+ c.execute(sql.SQL("DROP ROLE {};").format(map_user))
+ c.execute(sql.SQL("DROP ROLE {};").format(user))
+
+
[email protected]()
+def conn(oauth_ctx, connect):
+ """
+ A convenience wrapper for connect(). The main purpose of this fixture is to
+ make sure oauth_ctx runs its setup code before the connection is made.
+ """
+ return connect()
+
+
[email protected](scope="module", autouse=True)
+def authn_id_extension(oauth_ctx):
+ """
+ Performs a `CREATE EXTENSION authn_id` in the test database. This fixture is
+ autoused, so tests don't need to rely on it.
+ """
+ conn = psycopg2.connect(database=oauth_ctx.dbname)
+ conn.autocommit = True
+
+ with contextlib.closing(conn):
+ c = conn.cursor()
+ c.execute("CREATE EXTENSION authn_id;")
+
+
[email protected](scope="session")
+def shared_mem():
+ """
+ Yields a shared memory segment that can be used for communication between
+ the bearer_token fixture and ./validate_bearer.py.
+ """
+ size = MAX_TOKEN_SIZE + 2 # two byte length prefix
+ mem = shared_memory.SharedMemory(SHARED_MEM_NAME, create=True, size=size)
+
+ try:
+ with contextlib.closing(mem):
+ yield mem
+ finally:
+ mem.unlink()
+
+
[email protected]()
+def bearer_token(shared_mem):
+ """
+ Returns a factory function that, when called, will store a Bearer token in
+ shared_mem. If token is None (the default), a new token will be generated
+ using secrets.token_urlsafe() and returned; otherwise the passed token will
+ be used as-is.
+
+ When token is None, the generated token size in bytes may be specified as an
+ argument; if unset, a small 16-byte token will be generated. The token size
+ may not exceed MAX_TOKEN_SIZE in any case.
+
+ The return value is the token, converted to a bytes object.
+
+ As a special case for testing failure modes, accept_any may be set to True.
+ This signals to the validator command that any bearer token should be
+ accepted. The returned token in this case may be used or discarded as needed
+ by the test.
+ """
+
+ def set_token(token=None, *, size=16, accept_any=False):
+ if token is not None:
+ size = len(token)
+
+ if size > MAX_TOKEN_SIZE:
+ raise ValueError(f"token size {size} exceeds maximum size {MAX_TOKEN_SIZE}")
+
+ if token is None:
+ if size % 4:
+ raise ValueError(f"requested token size {size} is not a multiple of 4")
+
+ token = secrets.token_urlsafe(size // 4 * 3)
+ assert len(token) == size
+
+ try:
+ token = token.encode("ascii")
+ except AttributeError:
+ pass # already encoded
+
+ if accept_any:
+ # Two-byte magic value.
+ shared_mem.buf[:2] = struct.pack("H", MAX_UINT16)
+ else:
+ # Two-byte length prefix, then the token data.
+ shared_mem.buf[:2] = struct.pack("H", len(token))
+ shared_mem.buf[2 : size + 2] = token
+
+ return token
+
+ return set_token
+
+
+def begin_oauth_handshake(conn, oauth_ctx, *, user=None):
+ if user is None:
+ user = oauth_ctx.authz_user
+
+ pq3.send_startup(conn, user=user, database=oauth_ctx.dbname)
+
+ resp = pq3.recv1(conn)
+ assert resp.type == pq3.types.AuthnRequest
+
+ # The server should advertise exactly one mechanism.
+ assert resp.payload.type == pq3.authn.SASL
+ assert resp.payload.body == [b"OAUTHBEARER", b""]
+
+
+def send_initial_response(conn, *, auth=None, bearer=None):
+ """
+ Sends the OAUTHBEARER initial response on the connection, using the given
+ bearer token. Alternatively to a bearer token, the initial response's auth
+ field may be explicitly specified to test corner cases.
+ """
+ if bearer is not None and auth is not None:
+ raise ValueError("exactly one of the auth and bearer kwargs must be set")
+
+ if bearer is not None:
+ auth = b"Bearer " + bearer
+
+ if auth is None:
+ raise ValueError("exactly one of the auth and bearer kwargs must be set")
+
+ initial = pq3.SASLInitialResponse.build(
+ dict(
+ name=b"OAUTHBEARER",
+ data=b"n,,\x01auth=" + auth + b"\x01\x01",
+ )
+ )
+ pq3.send(conn, pq3.types.PasswordMessage, initial)
+
+
+def expect_handshake_success(conn):
+ """
+ Validates that the server responds with an AuthnOK message, and then drains
+ the connection until a ReadyForQuery message is received.
+ """
+ resp = pq3.recv1(conn)
+
+ assert resp.type == pq3.types.AuthnRequest
+ assert resp.payload.type == pq3.authn.OK
+ assert not resp.payload.body
+
+ receive_until(conn, pq3.types.ReadyForQuery)
+
+
+def expect_handshake_failure(conn, oauth_ctx):
+ """
+ Performs the OAUTHBEARER SASL failure "handshake" and validates the server's
+ side of the conversation, including the final ErrorResponse.
+ """
+
+ # We expect a discovery "challenge" back from the server before the authn
+ # failure message.
+ resp = pq3.recv1(conn)
+ assert resp.type == pq3.types.AuthnRequest
+
+ req = resp.payload
+ assert req.type == pq3.authn.SASLContinue
+
+ body = json.loads(req.body)
+ assert body["status"] == "invalid_token"
+ assert body["scope"] == oauth_ctx.scope
+
+ expected_config = oauth_ctx.issuer + "/.well-known/openid-configuration"
+ assert body["openid-configuration"] == expected_config
+
+ # Send the dummy response to complete the failed handshake.
+ pq3.send(conn, pq3.types.PasswordMessage, b"\x01")
+ resp = pq3.recv1(conn)
+
+ err = ExpectedError(INVALID_AUTHORIZATION_ERRCODE, "bearer authentication failed")
+ err.match(resp)
+
+
+def receive_until(conn, type):
+ """
+ receive_until pulls packets off the pq3 connection until a packet with the
+ desired type is found, or an error response is received.
+ """
+ while True:
+ pkt = pq3.recv1(conn)
+
+ if pkt.type == type:
+ return pkt
+ elif pkt.type == pq3.types.ErrorResponse:
+ raise RuntimeError(
+ f"received error response from peer: {pkt.payload.fields!r}"
+ )
+
+
[email protected]("token_len", [16, 1024, 4096])
[email protected](
+ "auth_prefix",
+ [
+ b"Bearer ",
+ b"bearer ",
+ b"Bearer ",
+ ],
+)
+def test_oauth(conn, oauth_ctx, bearer_token, auth_prefix, token_len):
+ begin_oauth_handshake(conn, oauth_ctx)
+
+ # Generate our bearer token with the desired length.
+ token = bearer_token(size=token_len)
+ auth = auth_prefix + token
+
+ send_initial_response(conn, auth=auth)
+ expect_handshake_success(conn)
+
+ # Make sure that the server has not set an authenticated ID.
+ pq3.send(conn, pq3.types.Query, query=b"SELECT authn_id();")
+ resp = receive_until(conn, pq3.types.DataRow)
+
+ row = resp.payload
+ assert row.columns == [None]
+
+
[email protected](
+ "token_value",
+ [
+ "abcdzA==",
+ "123456M=",
+ "x-._~+/x",
+ ],
+)
+def test_oauth_bearer_corner_cases(conn, oauth_ctx, bearer_token, token_value):
+ begin_oauth_handshake(conn, oauth_ctx)
+
+ send_initial_response(conn, bearer=bearer_token(token_value))
+
+ expect_handshake_success(conn)
+
+
[email protected](
+ "user,authn_id,should_succeed",
+ [
+ pytest.param(
+ lambda ctx: ctx.user,
+ lambda ctx: ctx.user,
+ True,
+ id="validator authn: succeeds when authn_id == username",
+ ),
+ pytest.param(
+ lambda ctx: ctx.user,
+ lambda ctx: None,
+ False,
+ id="validator authn: fails when authn_id is not set",
+ ),
+ pytest.param(
+ lambda ctx: ctx.user,
+ lambda ctx: ctx.authz_user,
+ False,
+ id="validator authn: fails when authn_id != username",
+ ),
+ pytest.param(
+ lambda ctx: ctx.map_user,
+ lambda ctx: ctx.map_user + "@example.com",
+ True,
+ id="validator with map: succeeds when authn_id matches map",
+ ),
+ pytest.param(
+ lambda ctx: ctx.map_user,
+ lambda ctx: None,
+ False,
+ id="validator with map: fails when authn_id is not set",
+ ),
+ pytest.param(
+ lambda ctx: ctx.map_user,
+ lambda ctx: ctx.map_user + "@example.net",
+ False,
+ id="validator with map: fails when authn_id doesn't match map",
+ ),
+ pytest.param(
+ lambda ctx: ctx.authz_user,
+ lambda ctx: None,
+ True,
+ id="validator authz: succeeds with no authn_id",
+ ),
+ pytest.param(
+ lambda ctx: ctx.authz_user,
+ lambda ctx: "",
+ True,
+ id="validator authz: succeeds with empty authn_id",
+ ),
+ pytest.param(
+ lambda ctx: ctx.authz_user,
+ lambda ctx: "postgres",
+ True,
+ id="validator authz: succeeds with basic username",
+ ),
+ pytest.param(
+ lambda ctx: ctx.authz_user,
+ lambda ctx: "[email protected]",
+ True,
+ id="validator authz: succeeds with email address",
+ ),
+ ],
+)
+def test_oauth_authn_id(conn, oauth_ctx, bearer_token, user, authn_id, should_succeed):
+ token = None
+
+ authn_id = authn_id(oauth_ctx)
+ if authn_id is not None:
+ authn_id = authn_id.encode("ascii")
+
+ # As a hack to get the validator to reflect arbitrary output from this
+ # test, encode the desired output as a base64 token. The validator will
+ # key on the leading "output=" to differentiate this from the random
+ # tokens generated by secrets.token_urlsafe().
+ output = b"output=" + authn_id + b"\n"
+ token = base64.urlsafe_b64encode(output)
+
+ token = bearer_token(token)
+ username = user(oauth_ctx)
+
+ begin_oauth_handshake(conn, oauth_ctx, user=username)
+ send_initial_response(conn, bearer=token)
+
+ if not should_succeed:
+ expect_handshake_failure(conn, oauth_ctx)
+ return
+
+ expect_handshake_success(conn)
+
+ # Check the reported authn_id.
+ pq3.send(conn, pq3.types.Query, query=b"SELECT authn_id();")
+ resp = receive_until(conn, pq3.types.DataRow)
+
+ row = resp.payload
+ assert row.columns == [authn_id]
+
+
+class ExpectedError(object):
+ def __init__(self, code, msg=None, detail=None):
+ self.code = code
+ self.msg = msg
+ self.detail = detail
+
+ # Protect against the footgun of an accidental empty string, which will
+ # "match" anything. If you don't want to match message or detail, just
+ # don't pass them.
+ if self.msg == "":
+ raise ValueError("msg must be non-empty or None")
+ if self.detail == "":
+ raise ValueError("detail must be non-empty or None")
+
+ def _getfield(self, resp, type):
+ """
+ Searches an ErrorResponse for a single field of the given type (e.g.
+ "M", "C", "D") and returns its value. Asserts if it doesn't find exactly
+ one field.
+ """
+ prefix = type.encode("ascii")
+ fields = [f for f in resp.payload.fields if f.startswith(prefix)]
+
+ assert len(fields) == 1
+ return fields[0][1:] # strip off the type byte
+
+ def match(self, resp):
+ """
+ Checks that the given response matches the expected code, message, and
+ detail (if given). The error code must match exactly. The expected
+ message and detail must be contained within the actual strings.
+ """
+ assert resp.type == pq3.types.ErrorResponse
+
+ code = self._getfield(resp, "C")
+ assert code == self.code
+
+ if self.msg:
+ msg = self._getfield(resp, "M")
+ expected = self.msg.encode("utf-8")
+ assert expected in msg
+
+ if self.detail:
+ detail = self._getfield(resp, "D")
+ expected = self.detail.encode("utf-8")
+ assert expected in detail
+
+
+def test_oauth_rejected_bearer(conn, oauth_ctx, bearer_token):
+ # Generate a new bearer token, which we will proceed not to use.
+ _ = bearer_token()
+
+ begin_oauth_handshake(conn, oauth_ctx)
+
+ # Send a bearer token that doesn't match what the validator expects. It
+ # should fail the connection.
+ send_initial_response(conn, bearer=b"xxxxxx")
+
+ expect_handshake_failure(conn, oauth_ctx)
+
+
[email protected](
+ "bad_bearer",
+ [
+ b"Bearer ",
+ b"Bearer a===b",
+ b"Bearer hello!",
+ b"Bearer [email protected]",
+ b'OAuth realm="Example"',
+ b"",
+ ],
+)
+def test_oauth_invalid_bearer(conn, oauth_ctx, bearer_token, bad_bearer):
+ # Tell the validator to accept any token. This ensures that the invalid
+ # bearer tokens are rejected before the validation step.
+ _ = bearer_token(accept_any=True)
+
+ begin_oauth_handshake(conn, oauth_ctx)
+ send_initial_response(conn, auth=bad_bearer)
+
+ expect_handshake_failure(conn, oauth_ctx)
+
+
[email protected]
[email protected](
+ "resp_type,resp,err",
+ [
+ pytest.param(
+ None,
+ None,
+ None,
+ marks=pytest.mark.slow,
+ id="no response (expect timeout)",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ b"hello",
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "did not send a kvsep response",
+ ),
+ id="bad dummy response",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ b"\x01\x01",
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "did not send a kvsep response",
+ ),
+ id="multiple kvseps",
+ ),
+ pytest.param(
+ pq3.types.Query,
+ dict(query=b""),
+ ExpectedError(PROTOCOL_VIOLATION_ERRCODE, "expected SASL response"),
+ id="bad response message type",
+ ),
+ ],
+)
+def test_oauth_bad_response_to_error_challenge(conn, oauth_ctx, resp_type, resp, err):
+ begin_oauth_handshake(conn, oauth_ctx)
+
+ # Send an empty auth initial response, which will force an authn failure.
+ send_initial_response(conn, auth=b"")
+
+ # We expect a discovery "challenge" back from the server before the authn
+ # failure message.
+ pkt = pq3.recv1(conn)
+ assert pkt.type == pq3.types.AuthnRequest
+
+ req = pkt.payload
+ assert req.type == pq3.authn.SASLContinue
+
+ body = json.loads(req.body)
+ assert body["status"] == "invalid_token"
+
+ if resp_type is None:
+ # Do not send the dummy response. We should time out and not get a
+ # response from the server.
+ with pytest.raises(socket.timeout):
+ conn.read(1)
+
+ # Done with the test.
+ return
+
+ # Send the bad response.
+ pq3.send(conn, resp_type, resp)
+
+ # Make sure the server fails the connection correctly.
+ pkt = pq3.recv1(conn)
+ err.match(pkt)
+
+
[email protected](
+ "type,payload,err",
+ [
+ pytest.param(
+ pq3.types.ErrorResponse,
+ dict(fields=[b""]),
+ ExpectedError(PROTOCOL_VIOLATION_ERRCODE, "expected SASL response"),
+ id="error response in initial message",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ b"x" * (MAX_SASL_MESSAGE_LENGTH + 1),
+ ExpectedError(
+ INVALID_AUTHORIZATION_ERRCODE, "bearer authentication failed"
+ ),
+ id="overlong initial response data",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"SCRAM-SHA-256")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE, "invalid SASL authentication mechanism"
+ ),
+ id="bad SASL mechanism selection",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", len=2, data=b"x")),
+ ExpectedError(PROTOCOL_VIOLATION_ERRCODE, "insufficient data"),
+ id="SASL data underflow",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", len=0, data=b"x")),
+ ExpectedError(PROTOCOL_VIOLATION_ERRCODE, "invalid message format"),
+ id="SASL data overflow",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "message is empty",
+ ),
+ id="empty",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"n,,\x01auth=\x01\x01\0")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "length does not match input length",
+ ),
+ id="contains null byte",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"\x01")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Unexpected channel-binding flag", # XXX this is a bit strange
+ ),
+ id="initial error response",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"p=tls-server-end-point,,\x01")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "server does not support channel binding",
+ ),
+ id="uses channel binding",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"x,,\x01")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Unexpected channel-binding flag",
+ ),
+ id="invalid channel binding specifier",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Comma expected",
+ ),
+ id="bad GS2 header: missing channel binding terminator",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y,a")),
+ ExpectedError(
+ FEATURE_NOT_SUPPORTED_ERRCODE,
+ "client uses authorization identity",
+ ),
+ id="bad GS2 header: authzid in use",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y,b,")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Unexpected attribute",
+ ),
+ id="bad GS2 header: extra attribute",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y,")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Unexpected attribute 0x00", # XXX this is a bit strange
+ ),
+ id="bad GS2 header: missing authzid terminator",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y,,")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Key-value separator expected",
+ ),
+ id="missing initial kvsep",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y,,")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Key-value separator expected",
+ ),
+ id="missing initial kvsep",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"y,,\x01\x01")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "does not contain an auth value",
+ ),
+ id="missing auth value: empty key-value list",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"y,,\x01host=example.com\x01\x01")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "does not contain an auth value",
+ ),
+ id="missing auth value: other keys present",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"y,,\x01host=example.com")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "unterminated key/value pair",
+ ),
+ id="missing value terminator",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y,,\x01")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "did not contain a final terminator",
+ ),
+ id="missing list terminator: empty list",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"y,,\x01auth=Bearer 0\x01")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "did not contain a final terminator",
+ ),
+ id="missing list terminator: with auth value",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"y,,\x01auth=Bearer 0\x01\x01blah")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "additional data after the final terminator",
+ ),
+ id="additional key after terminator",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"y,,\x01key\x01\x01")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "key without a value",
+ ),
+ id="key without value",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(
+ name=b"OAUTHBEARER",
+ data=b"y,,\x01auth=Bearer 0\x01auth=Bearer 1\x01\x01",
+ )
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "contains multiple auth values",
+ ),
+ id="multiple auth values",
+ ),
+ ],
+)
+def test_oauth_bad_initial_response(conn, oauth_ctx, type, payload, err):
+ begin_oauth_handshake(conn, oauth_ctx)
+
+ # The server expects a SASL response; give it something else instead.
+ if not isinstance(payload, dict):
+ payload = dict(payload_data=payload)
+ pq3.send(conn, type, **payload)
+
+ resp = pq3.recv1(conn)
+ err.match(resp)
+
+
+def test_oauth_empty_initial_response(conn, oauth_ctx, bearer_token):
+ begin_oauth_handshake(conn, oauth_ctx)
+
+ # Send an initial response without data.
+ initial = pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER"))
+ pq3.send(conn, pq3.types.PasswordMessage, initial)
+
+ # The server should respond with an empty challenge so we can send the data
+ # it wants.
+ pkt = pq3.recv1(conn)
+
+ assert pkt.type == pq3.types.AuthnRequest
+ assert pkt.payload.type == pq3.authn.SASLContinue
+ assert not pkt.payload.body
+
+ # Now send the initial data.
+ data = b"n,,\x01auth=Bearer " + bearer_token() + b"\x01\x01"
+ pq3.send(conn, pq3.types.PasswordMessage, data)
+
+ # Server should now complete the handshake.
+ expect_handshake_success(conn)
+
+
[email protected]()
+def set_validator():
+ """
+ A per-test fixture that allows a test to override the setting of
+ oauth_validator_command for the cluster. The setting will be reverted during
+ teardown.
+
+ Passing None will perform an ALTER SYSTEM RESET.
+ """
+ conn = psycopg2.connect("")
+ conn.autocommit = True
+
+ with contextlib.closing(conn):
+ c = conn.cursor()
+
+ # Save the previous value.
+ c.execute("SHOW oauth_validator_command;")
+ prev_cmd = c.fetchone()[0]
+
+ def setter(cmd):
+ c.execute("ALTER SYSTEM SET oauth_validator_command TO %s;", (cmd,))
+ c.execute("SELECT pg_reload_conf();")
+
+ yield setter
+
+ # Restore the previous value.
+ c.execute("ALTER SYSTEM SET oauth_validator_command TO %s;", (prev_cmd,))
+ c.execute("SELECT pg_reload_conf();")
+
+
+def test_oauth_no_validator(oauth_ctx, set_validator, connect, bearer_token):
+ # Clear out our validator command, then establish a new connection.
+ set_validator("")
+ conn = connect()
+
+ begin_oauth_handshake(conn, oauth_ctx)
+ send_initial_response(conn, bearer=bearer_token())
+
+ # The server should fail the connection.
+ expect_handshake_failure(conn, oauth_ctx)
+
+
+def test_oauth_validator_role(oauth_ctx, set_validator, connect):
+ # Switch the validator implementation. This validator will reflect the
+ # PGUSER as the authenticated identity.
+ path = pathlib.Path(__file__).parent / "validate_reflect.py"
+ path = str(path.absolute())
+
+ set_validator(f"{shlex.quote(path)} '%r' <&%f")
+ conn = connect()
+
+ # Log in. Note that the reflection validator ignores the bearer token.
+ begin_oauth_handshake(conn, oauth_ctx, user=oauth_ctx.user)
+ send_initial_response(conn, bearer=b"dontcare")
+ expect_handshake_success(conn)
+
+ # Check the user identity.
+ pq3.send(conn, pq3.types.Query, query=b"SELECT authn_id();")
+ resp = receive_until(conn, pq3.types.DataRow)
+
+ row = resp.payload
+ expected = oauth_ctx.user.encode("utf-8")
+ assert row.columns == [expected]
+
+
+def test_oauth_role_with_shell_unsafe_characters(oauth_ctx, set_validator, connect):
+ """
+ XXX This test pins undesirable behavior. We should be able to handle any
+ valid Postgres role name.
+ """
+ # Switch the validator implementation. This validator will reflect the
+ # PGUSER as the authenticated identity.
+ path = pathlib.Path(__file__).parent / "validate_reflect.py"
+ path = str(path.absolute())
+
+ set_validator(f"{shlex.quote(path)} '%r' <&%f")
+ conn = connect()
+
+ unsafe_username = "hello'there"
+ begin_oauth_handshake(conn, oauth_ctx, user=unsafe_username)
+
+ # The server should reject the handshake.
+ send_initial_response(conn, bearer=b"dontcare")
+ expect_handshake_failure(conn, oauth_ctx)
diff --git a/src/test/python/server/test_server.py b/src/test/python/server/test_server.py
new file mode 100644
index 0000000000..02126dba79
--- /dev/null
+++ b/src/test/python/server/test_server.py
@@ -0,0 +1,21 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import pq3
+
+
+def test_handshake(connect):
+ """Basic sanity check."""
+ conn = connect()
+
+ pq3.handshake(conn, user=pq3.pguser(), database=pq3.pgdatabase())
+
+ pq3.send(conn, pq3.types.Query, query=b"")
+
+ resp = pq3.recv1(conn)
+ assert resp.type == pq3.types.EmptyQueryResponse
+
+ resp = pq3.recv1(conn)
+ assert resp.type == pq3.types.ReadyForQuery
diff --git a/src/test/python/server/validate_bearer.py b/src/test/python/server/validate_bearer.py
new file mode 100755
index 0000000000..2cc73ff154
--- /dev/null
+++ b/src/test/python/server/validate_bearer.py
@@ -0,0 +1,101 @@
+#! /usr/bin/env python3
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+# DO NOT USE THIS OAUTH VALIDATOR IN PRODUCTION. It doesn't actually validate
+# anything, and it logs the bearer token data, which is sensitive.
+#
+# This executable is used as an oauth_validator_command in concert with
+# test_oauth.py. Memory is shared and communicated from that test module's
+# bearer_token() fixture.
+#
+# This script must run under the Postgres server environment; keep the
+# dependency list fairly standard.
+
+import base64
+import binascii
+import contextlib
+import struct
+import sys
+from multiprocessing import shared_memory
+
+MAX_UINT16 = 2 ** 16 - 1
+
+
+def remove_shm_from_resource_tracker():
+ """
+ Monkey-patch multiprocessing.resource_tracker so SharedMemory won't be
+ tracked. Pulled from this thread, where there are more details:
+
+ https://bugs.python.org/issue38119
+
+ TL;DR: all clients of shared memory segments automatically destroy them on
+ process exit, which makes shared memory segments much less useful. This
+ monkeypatch removes that behavior so that we can defer to the test to manage
+ the segment lifetime.
+
+ Ideally a future Python patch will pull in this fix and then the entire
+ function can go away.
+ """
+ from multiprocessing import resource_tracker
+
+ def fix_register(name, rtype):
+ if rtype == "shared_memory":
+ return
+ return resource_tracker._resource_tracker.register(self, name, rtype)
+
+ resource_tracker.register = fix_register
+
+ def fix_unregister(name, rtype):
+ if rtype == "shared_memory":
+ return
+ return resource_tracker._resource_tracker.unregister(self, name, rtype)
+
+ resource_tracker.unregister = fix_unregister
+
+ if "shared_memory" in resource_tracker._CLEANUP_FUNCS:
+ del resource_tracker._CLEANUP_FUNCS["shared_memory"]
+
+
+def main(args):
+ remove_shm_from_resource_tracker() # XXX remove some day
+
+ # Get the expected token from the currently running test.
+ shared_mem_name = args[0]
+
+ mem = shared_memory.SharedMemory(shared_mem_name)
+ with contextlib.closing(mem):
+ # First two bytes are the token length.
+ size = struct.unpack("H", mem.buf[:2])[0]
+
+ if size == MAX_UINT16:
+ # Special case: the test wants us to accept any token.
+ sys.stderr.write("accepting token without validation\n")
+ return
+
+ # The remainder of the buffer contains the expected token.
+ assert size <= (mem.size - 2)
+ expected_token = mem.buf[2 : size + 2].tobytes()
+
+ mem.buf[:] = b"\0" * mem.size # scribble over the token
+
+ token = sys.stdin.buffer.read()
+ if token != expected_token:
+ sys.exit(f"failed to match Bearer token ({token!r} != {expected_token!r})")
+
+ # See if the test wants us to print anything. If so, it will have encoded
+ # the desired output in the token with an "output=" prefix.
+ try:
+ # altchars="-_" corresponds to the urlsafe alphabet.
+ data = base64.b64decode(token, altchars="-_", validate=True)
+
+ if data.startswith(b"output="):
+ sys.stdout.buffer.write(data[7:])
+
+ except binascii.Error:
+ pass
+
+
+if __name__ == "__main__":
+ main(sys.argv[1:])
diff --git a/src/test/python/server/validate_reflect.py b/src/test/python/server/validate_reflect.py
new file mode 100755
index 0000000000..24c3a7e715
--- /dev/null
+++ b/src/test/python/server/validate_reflect.py
@@ -0,0 +1,34 @@
+#! /usr/bin/env python3
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+# DO NOT USE THIS OAUTH VALIDATOR IN PRODUCTION. It ignores the bearer token
+# entirely and automatically logs the user in.
+#
+# This executable is used as an oauth_validator_command in concert with
+# test_oauth.py. It expects the user's desired role name as an argument; the
+# actual token will be discarded and the user will be logged in with the role
+# name as the authenticated identity.
+#
+# This script must run under the Postgres server environment; keep the
+# dependency list fairly standard.
+
+import sys
+
+
+def main(args):
+ # We have to read the entire token as our first action to unblock the
+ # server, but we won't actually use it.
+ _ = sys.stdin.buffer.read()
+
+ if len(args) != 1:
+ sys.exit("usage: ./validate_reflect.py ROLE")
+
+ # Log the user in as the provided role.
+ role = args[0]
+ print(role)
+
+
+if __name__ == "__main__":
+ main(sys.argv[1:])
diff --git a/src/test/python/test_internals.py b/src/test/python/test_internals.py
new file mode 100644
index 0000000000..dee4855fc0
--- /dev/null
+++ b/src/test/python/test_internals.py
@@ -0,0 +1,138 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import io
+
+from pq3 import _DebugStream
+
+
+def test_DebugStream_read():
+ under = io.BytesIO(b"abcdefghijklmnopqrstuvwxyz")
+ out = io.StringIO()
+
+ stream = _DebugStream(under, out)
+
+ res = stream.read(5)
+ assert res == b"abcde"
+
+ res = stream.read(16)
+ assert res == b"fghijklmnopqrstu"
+
+ stream.flush_debug()
+
+ res = stream.read()
+ assert res == b"vwxyz"
+
+ stream.flush_debug()
+
+ expected = (
+ "< 0000:\t61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70\tabcdefghijklmnop\n"
+ "< 0010:\t71 72 73 74 75 \tqrstu\n"
+ "\n"
+ "< 0000:\t76 77 78 79 7a \tvwxyz\n"
+ "\n"
+ )
+ assert out.getvalue() == expected
+
+
+def test_DebugStream_write():
+ under = io.BytesIO()
+ out = io.StringIO()
+
+ stream = _DebugStream(under, out)
+
+ stream.write(b"\x00\x01\x02")
+ stream.flush()
+
+ assert under.getvalue() == b"\x00\x01\x02"
+
+ stream.write(b"\xc0\xc1\xc2")
+ stream.flush()
+
+ assert under.getvalue() == b"\x00\x01\x02\xc0\xc1\xc2"
+
+ stream.flush_debug()
+
+ expected = "> 0000:\t00 01 02 c0 c1 c2 \t......\n\n"
+ assert out.getvalue() == expected
+
+
+def test_DebugStream_read_write():
+ under = io.BytesIO(b"abcdefghijklmnopqrstuvwxyz")
+ out = io.StringIO()
+ stream = _DebugStream(under, out)
+
+ res = stream.read(5)
+ assert res == b"abcde"
+
+ stream.write(b"xxxxx")
+ stream.flush()
+
+ assert under.getvalue() == b"abcdexxxxxklmnopqrstuvwxyz"
+
+ res = stream.read(5)
+ assert res == b"klmno"
+
+ stream.write(b"xxxxx")
+ stream.flush()
+
+ assert under.getvalue() == b"abcdexxxxxklmnoxxxxxuvwxyz"
+
+ stream.flush_debug()
+
+ expected = (
+ "< 0000:\t61 62 63 64 65 6b 6c 6d 6e 6f \tabcdeklmno\n"
+ "\n"
+ "> 0000:\t78 78 78 78 78 78 78 78 78 78 \txxxxxxxxxx\n"
+ "\n"
+ )
+ assert out.getvalue() == expected
+
+
+def test_DebugStream_end_packet():
+ under = io.BytesIO(b"abcdefghijklmnopqrstuvwxyz")
+ out = io.StringIO()
+ stream = _DebugStream(under, out)
+
+ stream.read(5)
+ stream.end_packet("read description", read=True, indent=" ")
+
+ stream.write(b"xxxxx")
+ stream.flush()
+ stream.end_packet("write description", indent=" ")
+
+ stream.read(5)
+ stream.write(b"xxxxx")
+ stream.flush()
+ stream.end_packet("read/write combo for read", read=True, indent=" ")
+
+ stream.read(5)
+ stream.write(b"xxxxx")
+ stream.flush()
+ stream.end_packet("read/write combo for write", indent=" ")
+
+ expected = (
+ " < 0000:\t61 62 63 64 65 \tabcde\n"
+ "\n"
+ "< read description\n"
+ "\n"
+ "> write description\n"
+ "\n"
+ " > 0000:\t78 78 78 78 78 \txxxxx\n"
+ "\n"
+ " < 0000:\t6b 6c 6d 6e 6f \tklmno\n"
+ "\n"
+ " > 0000:\t78 78 78 78 78 \txxxxx\n"
+ "\n"
+ "< read/write combo for read\n"
+ "\n"
+ "> read/write combo for write\n"
+ "\n"
+ " < 0000:\t75 76 77 78 79 \tuvwxy\n"
+ "\n"
+ " > 0000:\t78 78 78 78 78 \txxxxx\n"
+ "\n"
+ )
+ assert out.getvalue() == expected
diff --git a/src/test/python/test_pq3.py b/src/test/python/test_pq3.py
new file mode 100644
index 0000000000..e0c0e0568d
--- /dev/null
+++ b/src/test/python/test_pq3.py
@@ -0,0 +1,558 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import contextlib
+import getpass
+import io
+import struct
+import sys
+
+import pytest
+from construct import Container, PaddingError, StreamError, TerminatedError
+
+import pq3
+
+
[email protected](
+ "raw,expected,extra",
+ [
+ pytest.param(
+ b"\x00\x00\x00\x10\x00\x04\x00\x00abcdefgh",
+ Container(len=16, proto=0x40000, payload=b"abcdefgh"),
+ b"",
+ id="8-byte payload",
+ ),
+ pytest.param(
+ b"\x00\x00\x00\x08\x00\x04\x00\x00",
+ Container(len=8, proto=0x40000, payload=b""),
+ b"",
+ id="no payload",
+ ),
+ pytest.param(
+ b"\x00\x00\x00\x09\x00\x04\x00\x00abcde",
+ Container(len=9, proto=0x40000, payload=b"a"),
+ b"bcde",
+ id="1-byte payload and extra padding",
+ ),
+ pytest.param(
+ b"\x00\x00\x00\x0B\x00\x03\x00\x00hi\x00",
+ Container(len=11, proto=pq3.protocol(3, 0), payload=[b"hi"]),
+ b"",
+ id="implied parameter list when using proto version 3.0",
+ ),
+ ],
+)
+def test_Startup_parse(raw, expected, extra):
+ with io.BytesIO(raw) as stream:
+ actual = pq3.Startup.parse_stream(stream)
+
+ assert actual == expected
+ assert stream.read() == extra
+
+
[email protected](
+ "packet,expected_bytes",
+ [
+ pytest.param(
+ dict(),
+ b"\x00\x00\x00\x08\x00\x00\x00\x00",
+ id="nothing set",
+ ),
+ pytest.param(
+ dict(len=10, proto=0x12345678),
+ b"\x00\x00\x00\x0A\x12\x34\x56\x78\x00\x00",
+ id="len and proto set explicitly",
+ ),
+ pytest.param(
+ dict(proto=0x12345678),
+ b"\x00\x00\x00\x08\x12\x34\x56\x78",
+ id="implied len with no payload",
+ ),
+ pytest.param(
+ dict(proto=0x12345678, payload=b"abcd"),
+ b"\x00\x00\x00\x0C\x12\x34\x56\x78abcd",
+ id="implied len with payload",
+ ),
+ pytest.param(
+ dict(payload=[b""]),
+ b"\x00\x00\x00\x09\x00\x03\x00\x00\x00",
+ id="implied proto version 3 when sending parameters",
+ ),
+ pytest.param(
+ dict(payload=[b"hi", b""]),
+ b"\x00\x00\x00\x0C\x00\x03\x00\x00hi\x00\x00",
+ id="implied proto version 3 and len when sending more than one parameter",
+ ),
+ pytest.param(
+ dict(payload=dict(user="jsmith", database="postgres")),
+ b"\x00\x00\x00\x27\x00\x03\x00\x00user\x00jsmith\x00database\x00postgres\x00\x00",
+ id="auto-serialization of dict parameters",
+ ),
+ ],
+)
+def test_Startup_build(packet, expected_bytes):
+ actual = pq3.Startup.build(packet)
+ assert actual == expected_bytes
+
+
[email protected](
+ "raw,expected,extra",
+ [
+ pytest.param(
+ b"*\x00\x00\x00\x08abcd",
+ dict(type=b"*", len=8, payload=b"abcd"),
+ b"",
+ id="4-byte payload",
+ ),
+ pytest.param(
+ b"*\x00\x00\x00\x04",
+ dict(type=b"*", len=4, payload=b""),
+ b"",
+ id="no payload",
+ ),
+ pytest.param(
+ b"*\x00\x00\x00\x05xabcd",
+ dict(type=b"*", len=5, payload=b"x"),
+ b"abcd",
+ id="1-byte payload with extra padding",
+ ),
+ pytest.param(
+ b"R\x00\x00\x00\x08\x00\x00\x00\x00",
+ dict(
+ type=pq3.types.AuthnRequest,
+ len=8,
+ payload=dict(type=pq3.authn.OK, body=None),
+ ),
+ b"",
+ id="AuthenticationOk",
+ ),
+ pytest.param(
+ b"R\x00\x00\x00\x12\x00\x00\x00\x0AEXTERNAL\x00\x00",
+ dict(
+ type=pq3.types.AuthnRequest,
+ len=18,
+ payload=dict(type=pq3.authn.SASL, body=[b"EXTERNAL", b""]),
+ ),
+ b"",
+ id="AuthenticationSASL",
+ ),
+ pytest.param(
+ b"R\x00\x00\x00\x0D\x00\x00\x00\x0B12345",
+ dict(
+ type=pq3.types.AuthnRequest,
+ len=13,
+ payload=dict(type=pq3.authn.SASLContinue, body=b"12345"),
+ ),
+ b"",
+ id="AuthenticationSASLContinue",
+ ),
+ pytest.param(
+ b"R\x00\x00\x00\x0D\x00\x00\x00\x0C12345",
+ dict(
+ type=pq3.types.AuthnRequest,
+ len=13,
+ payload=dict(type=pq3.authn.SASLFinal, body=b"12345"),
+ ),
+ b"",
+ id="AuthenticationSASLFinal",
+ ),
+ pytest.param(
+ b"p\x00\x00\x00\x0Bhunter2",
+ dict(
+ type=pq3.types.PasswordMessage,
+ len=11,
+ payload=b"hunter2",
+ ),
+ b"",
+ id="PasswordMessage",
+ ),
+ pytest.param(
+ b"K\x00\x00\x00\x0C\x00\x00\x00\x00\x12\x34\x56\x78",
+ dict(
+ type=pq3.types.BackendKeyData,
+ len=12,
+ payload=dict(pid=0, key=0x12345678),
+ ),
+ b"",
+ id="BackendKeyData",
+ ),
+ pytest.param(
+ b"C\x00\x00\x00\x08SET\x00",
+ dict(
+ type=pq3.types.CommandComplete,
+ len=8,
+ payload=dict(tag=b"SET"),
+ ),
+ b"",
+ id="CommandComplete",
+ ),
+ pytest.param(
+ b"E\x00\x00\x00\x11Mbad!\x00Mdog!\x00\x00",
+ dict(type=b"E", len=17, payload=dict(fields=[b"Mbad!", b"Mdog!", b""])),
+ b"",
+ id="ErrorResponse",
+ ),
+ pytest.param(
+ b"S\x00\x00\x00\x08a\x00b\x00",
+ dict(
+ type=pq3.types.ParameterStatus,
+ len=8,
+ payload=dict(name=b"a", value=b"b"),
+ ),
+ b"",
+ id="ParameterStatus",
+ ),
+ pytest.param(
+ b"Z\x00\x00\x00\x05x",
+ dict(type=b"Z", len=5, payload=dict(status=b"x")),
+ b"",
+ id="ReadyForQuery",
+ ),
+ pytest.param(
+ b"Q\x00\x00\x00\x06!\x00",
+ dict(type=pq3.types.Query, len=6, payload=dict(query=b"!")),
+ b"",
+ id="Query",
+ ),
+ pytest.param(
+ b"D\x00\x00\x00\x0B\x00\x01\x00\x00\x00\x01!",
+ dict(type=pq3.types.DataRow, len=11, payload=dict(columns=[b"!"])),
+ b"",
+ id="DataRow",
+ ),
+ pytest.param(
+ b"D\x00\x00\x00\x06\x00\x00extra",
+ dict(type=pq3.types.DataRow, len=6, payload=dict(columns=[])),
+ b"extra",
+ id="DataRow with extra data",
+ ),
+ pytest.param(
+ b"I\x00\x00\x00\x04",
+ dict(type=pq3.types.EmptyQueryResponse, len=4, payload=None),
+ b"",
+ id="EmptyQueryResponse",
+ ),
+ pytest.param(
+ b"I\x00\x00\x00\x04\xFF",
+ dict(type=b"I", len=4, payload=None),
+ b"\xFF",
+ id="EmptyQueryResponse with extra bytes",
+ ),
+ pytest.param(
+ b"X\x00\x00\x00\x04",
+ dict(type=pq3.types.Terminate, len=4, payload=None),
+ b"",
+ id="Terminate",
+ ),
+ ],
+)
+def test_Pq3_parse(raw, expected, extra):
+ with io.BytesIO(raw) as stream:
+ actual = pq3.Pq3.parse_stream(stream)
+
+ assert actual == expected
+ assert stream.read() == extra
+
+
[email protected](
+ "fields,expected",
+ [
+ pytest.param(
+ dict(type=b"*", len=5),
+ b"*\x00\x00\x00\x05\x00",
+ id="type and len set explicitly",
+ ),
+ pytest.param(
+ dict(type=b"*"),
+ b"*\x00\x00\x00\x04",
+ id="implied len with no payload",
+ ),
+ pytest.param(
+ dict(type=b"*", payload=b"1234"),
+ b"*\x00\x00\x00\x081234",
+ id="implied len with payload",
+ ),
+ pytest.param(
+ dict(type=pq3.types.AuthnRequest, payload=dict(type=pq3.authn.OK)),
+ b"R\x00\x00\x00\x08\x00\x00\x00\x00",
+ id="implied len/type for AuthenticationOK",
+ ),
+ pytest.param(
+ dict(
+ type=pq3.types.AuthnRequest,
+ payload=dict(
+ type=pq3.authn.SASL,
+ body=[b"SCRAM-SHA-256-PLUS", b"SCRAM-SHA-256", b""],
+ ),
+ ),
+ b"R\x00\x00\x00\x2A\x00\x00\x00\x0ASCRAM-SHA-256-PLUS\x00SCRAM-SHA-256\x00\x00",
+ id="implied len/type for AuthenticationSASL",
+ ),
+ pytest.param(
+ dict(
+ type=pq3.types.AuthnRequest,
+ payload=dict(type=pq3.authn.SASLContinue, body=b"12345"),
+ ),
+ b"R\x00\x00\x00\x0D\x00\x00\x00\x0B12345",
+ id="implied len/type for AuthenticationSASLContinue",
+ ),
+ pytest.param(
+ dict(
+ type=pq3.types.AuthnRequest,
+ payload=dict(type=pq3.authn.SASLFinal, body=b"12345"),
+ ),
+ b"R\x00\x00\x00\x0D\x00\x00\x00\x0C12345",
+ id="implied len/type for AuthenticationSASLFinal",
+ ),
+ pytest.param(
+ dict(
+ type=pq3.types.PasswordMessage,
+ payload=b"hunter2",
+ ),
+ b"p\x00\x00\x00\x0Bhunter2",
+ id="implied len/type for PasswordMessage",
+ ),
+ pytest.param(
+ dict(type=pq3.types.BackendKeyData, payload=dict(pid=1, key=7)),
+ b"K\x00\x00\x00\x0C\x00\x00\x00\x01\x00\x00\x00\x07",
+ id="implied len/type for BackendKeyData",
+ ),
+ pytest.param(
+ dict(type=pq3.types.CommandComplete, payload=dict(tag=b"SET")),
+ b"C\x00\x00\x00\x08SET\x00",
+ id="implied len/type for CommandComplete",
+ ),
+ pytest.param(
+ dict(type=pq3.types.ErrorResponse, payload=dict(fields=[b"error", b""])),
+ b"E\x00\x00\x00\x0Berror\x00\x00",
+ id="implied len/type for ErrorResponse",
+ ),
+ pytest.param(
+ dict(type=pq3.types.ParameterStatus, payload=dict(name=b"a", value=b"b")),
+ b"S\x00\x00\x00\x08a\x00b\x00",
+ id="implied len/type for ParameterStatus",
+ ),
+ pytest.param(
+ dict(type=pq3.types.ReadyForQuery, payload=dict(status=b"I")),
+ b"Z\x00\x00\x00\x05I",
+ id="implied len/type for ReadyForQuery",
+ ),
+ pytest.param(
+ dict(type=pq3.types.Query, payload=dict(query=b"SELECT 1;")),
+ b"Q\x00\x00\x00\x0eSELECT 1;\x00",
+ id="implied len/type for Query",
+ ),
+ pytest.param(
+ dict(type=pq3.types.DataRow, payload=dict(columns=[b"abcd"])),
+ b"D\x00\x00\x00\x0E\x00\x01\x00\x00\x00\x04abcd",
+ id="implied len/type for DataRow",
+ ),
+ pytest.param(
+ dict(type=pq3.types.EmptyQueryResponse),
+ b"I\x00\x00\x00\x04",
+ id="implied len for EmptyQueryResponse",
+ ),
+ pytest.param(
+ dict(type=pq3.types.Terminate),
+ b"X\x00\x00\x00\x04",
+ id="implied len for Terminate",
+ ),
+ ],
+)
+def test_Pq3_build(fields, expected):
+ actual = pq3.Pq3.build(fields)
+ assert actual == expected
+
+
[email protected](
+ "raw,expected,extra",
+ [
+ pytest.param(
+ b"\x00\x00",
+ dict(columns=[]),
+ b"",
+ id="no columns",
+ ),
+ pytest.param(
+ b"\x00\x01\x00\x00\x00\x04abcd",
+ dict(columns=[b"abcd"]),
+ b"",
+ id="one column",
+ ),
+ pytest.param(
+ b"\x00\x02\x00\x00\x00\x04abcd\x00\x00\x00\x01x",
+ dict(columns=[b"abcd", b"x"]),
+ b"",
+ id="multiple columns",
+ ),
+ pytest.param(
+ b"\x00\x02\x00\x00\x00\x00\x00\x00\x00\x01x",
+ dict(columns=[b"", b"x"]),
+ b"",
+ id="empty column value",
+ ),
+ pytest.param(
+ b"\x00\x02\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
+ dict(columns=[None, None]),
+ b"",
+ id="null columns",
+ ),
+ ],
+)
+def test_DataRow_parse(raw, expected, extra):
+ pkt = b"D" + struct.pack("!i", len(raw) + 4) + raw
+ with io.BytesIO(pkt) as stream:
+ actual = pq3.Pq3.parse_stream(stream)
+
+ assert actual.type == pq3.types.DataRow
+ assert actual.payload == expected
+ assert stream.read() == extra
+
+
[email protected](
+ "fields,expected",
+ [
+ pytest.param(
+ dict(),
+ b"\x00\x00",
+ id="no columns",
+ ),
+ pytest.param(
+ dict(columns=[None, None]),
+ b"\x00\x02\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
+ id="null columns",
+ ),
+ ],
+)
+def test_DataRow_build(fields, expected):
+ actual = pq3.Pq3.build(dict(type=pq3.types.DataRow, payload=fields))
+
+ expected = b"D" + struct.pack("!i", len(expected) + 4) + expected
+ assert actual == expected
+
+
[email protected](
+ "raw,expected,exception",
+ [
+ pytest.param(
+ b"EXTERNAL\x00\xFF\xFF\xFF\xFF",
+ dict(name=b"EXTERNAL", len=-1, data=None),
+ None,
+ id="no initial response",
+ ),
+ pytest.param(
+ b"EXTERNAL\x00\x00\x00\x00\x02me",
+ dict(name=b"EXTERNAL", len=2, data=b"me"),
+ None,
+ id="initial response",
+ ),
+ pytest.param(
+ b"EXTERNAL\x00\x00\x00\x00\x02meextra",
+ None,
+ TerminatedError,
+ id="extra data",
+ ),
+ pytest.param(
+ b"EXTERNAL\x00\x00\x00\x00\xFFme",
+ None,
+ StreamError,
+ id="underflow",
+ ),
+ ],
+)
+def test_SASLInitialResponse_parse(raw, expected, exception):
+ ctx = contextlib.nullcontext()
+ if exception:
+ ctx = pytest.raises(exception)
+
+ with ctx:
+ actual = pq3.SASLInitialResponse.parse(raw)
+ assert actual == expected
+
+
[email protected](
+ "fields,expected",
+ [
+ pytest.param(
+ dict(name=b"EXTERNAL"),
+ b"EXTERNAL\x00\xFF\xFF\xFF\xFF",
+ id="no initial response",
+ ),
+ pytest.param(
+ dict(name=b"EXTERNAL", data=None),
+ b"EXTERNAL\x00\xFF\xFF\xFF\xFF",
+ id="no initial response (explicit None)",
+ ),
+ pytest.param(
+ dict(name=b"EXTERNAL", data=b""),
+ b"EXTERNAL\x00\x00\x00\x00\x00",
+ id="empty response",
+ ),
+ pytest.param(
+ dict(name=b"EXTERNAL", data=b"[email protected]"),
+ b"EXTERNAL\x00\x00\x00\x00\[email protected]",
+ id="initial response",
+ ),
+ pytest.param(
+ dict(name=b"EXTERNAL", len=2, data=b"[email protected]"),
+ b"EXTERNAL\x00\x00\x00\x00\[email protected]",
+ id="data overflow",
+ ),
+ pytest.param(
+ dict(name=b"EXTERNAL", len=14, data=b"me"),
+ b"EXTERNAL\x00\x00\x00\x00\x0Eme",
+ id="data underflow",
+ ),
+ ],
+)
+def test_SASLInitialResponse_build(fields, expected):
+ actual = pq3.SASLInitialResponse.build(fields)
+ assert actual == expected
+
+
[email protected](
+ "version,expected_bytes",
+ [
+ pytest.param((3, 0), b"\x00\x03\x00\x00", id="version 3"),
+ pytest.param((1234, 5679), b"\x04\xd2\x16\x2f", id="SSLRequest"),
+ ],
+)
+def test_protocol(version, expected_bytes):
+ # Make sure the integer returned by protocol is correctly serialized on the
+ # wire.
+ assert struct.pack("!i", pq3.protocol(*version)) == expected_bytes
+
+
[email protected](
+ "envvar,func,expected",
+ [
+ ("PGHOST", pq3.pghost, "localhost"),
+ ("PGPORT", pq3.pgport, 5432),
+ ("PGUSER", pq3.pguser, getpass.getuser()),
+ ("PGDATABASE", pq3.pgdatabase, "postgres"),
+ ],
+)
+def test_env_defaults(monkeypatch, envvar, func, expected):
+ monkeypatch.delenv(envvar, raising=False)
+
+ actual = func()
+ assert actual == expected
+
+
[email protected](
+ "envvars,func,expected",
+ [
+ (dict(PGHOST="otherhost"), pq3.pghost, "otherhost"),
+ (dict(PGPORT="6789"), pq3.pgport, 6789),
+ (dict(PGUSER="postgres"), pq3.pguser, "postgres"),
+ (dict(PGDATABASE="template1"), pq3.pgdatabase, "template1"),
+ ],
+)
+def test_env(monkeypatch, envvars, func, expected):
+ for k, v in envvars.items():
+ monkeypatch.setenv(k, v)
+
+ actual = func()
+ assert actual == expected
diff --git a/src/test/python/tls.py b/src/test/python/tls.py
new file mode 100644
index 0000000000..075c02c1ca
--- /dev/null
+++ b/src/test/python/tls.py
@@ -0,0 +1,195 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+from construct import *
+
+#
+# TLS 1.3
+#
+# Most of the types below are transcribed from RFC 8446:
+#
+# https://tools.ietf.org/html/rfc8446
+#
+
+
+def _Vector(size_field, element):
+ return Prefixed(size_field, GreedyRange(element))
+
+
+# Alerts
+
+AlertLevel = Enum(
+ Byte,
+ warning=1,
+ fatal=2,
+)
+
+AlertDescription = Enum(
+ Byte,
+ close_notify=0,
+ unexpected_message=10,
+ bad_record_mac=20,
+ decryption_failed_RESERVED=21,
+ record_overflow=22,
+ decompression_failure=30,
+ handshake_failure=40,
+ no_certificate_RESERVED=41,
+ bad_certificate=42,
+ unsupported_certificate=43,
+ certificate_revoked=44,
+ certificate_expired=45,
+ certificate_unknown=46,
+ illegal_parameter=47,
+ unknown_ca=48,
+ access_denied=49,
+ decode_error=50,
+ decrypt_error=51,
+ export_restriction_RESERVED=60,
+ protocol_version=70,
+ insufficient_security=71,
+ internal_error=80,
+ user_canceled=90,
+ no_renegotiation=100,
+ unsupported_extension=110,
+)
+
+Alert = Struct(
+ "level" / AlertLevel,
+ "description" / AlertDescription,
+)
+
+
+# Extensions
+
+ExtensionType = Enum(
+ Int16ub,
+ server_name=0,
+ max_fragment_length=1,
+ status_request=5,
+ supported_groups=10,
+ signature_algorithms=13,
+ use_srtp=14,
+ heartbeat=15,
+ application_layer_protocol_negotiation=16,
+ signed_certificate_timestamp=18,
+ client_certificate_type=19,
+ server_certificate_type=20,
+ padding=21,
+ pre_shared_key=41,
+ early_data=42,
+ supported_versions=43,
+ cookie=44,
+ psk_key_exchange_modes=45,
+ certificate_authorities=47,
+ oid_filters=48,
+ post_handshake_auth=49,
+ signature_algorithms_cert=50,
+ key_share=51,
+)
+
+Extension = Struct(
+ "extension_type" / ExtensionType,
+ "extension_data" / Prefixed(Int16ub, GreedyBytes),
+)
+
+
+# ClientHello
+
+
+class _CipherSuiteAdapter(Adapter):
+ class _hextuple(tuple):
+ def __repr__(self):
+ return f"(0x{self[0]:02X}, 0x{self[1]:02X})"
+
+ def _encode(self, obj, context, path):
+ return bytes(obj)
+
+ def _decode(self, obj, context, path):
+ assert len(obj) == 2
+ return self._hextuple(obj)
+
+
+ProtocolVersion = Hex(Int16ub)
+
+Random = Hex(Bytes(32))
+
+CipherSuite = _CipherSuiteAdapter(Byte[2])
+
+ClientHello = Struct(
+ "legacy_version" / ProtocolVersion,
+ "random" / Random,
+ "legacy_session_id" / Prefixed(Byte, Hex(GreedyBytes)),
+ "cipher_suites" / _Vector(Int16ub, CipherSuite),
+ "legacy_compression_methods" / Prefixed(Byte, GreedyBytes),
+ "extensions" / _Vector(Int16ub, Extension),
+)
+
+# ServerHello
+
+ServerHello = Struct(
+ "legacy_version" / ProtocolVersion,
+ "random" / Random,
+ "legacy_session_id_echo" / Prefixed(Byte, Hex(GreedyBytes)),
+ "cipher_suite" / CipherSuite,
+ "legacy_compression_method" / Hex(Byte),
+ "extensions" / _Vector(Int16ub, Extension),
+)
+
+# Handshake
+
+HandshakeType = Enum(
+ Byte,
+ client_hello=1,
+ server_hello=2,
+ new_session_ticket=4,
+ end_of_early_data=5,
+ encrypted_extensions=8,
+ certificate=11,
+ certificate_request=13,
+ certificate_verify=15,
+ finished=20,
+ key_update=24,
+ message_hash=254,
+)
+
+Handshake = Struct(
+ "msg_type" / HandshakeType,
+ "length" / Int24ub,
+ "payload"
+ / Switch(
+ this.msg_type,
+ {
+ HandshakeType.client_hello: ClientHello,
+ HandshakeType.server_hello: ServerHello,
+ # HandshakeType.end_of_early_data: EndOfEarlyData,
+ # HandshakeType.encrypted_extensions: EncryptedExtensions,
+ # HandshakeType.certificate_request: CertificateRequest,
+ # HandshakeType.certificate: Certificate,
+ # HandshakeType.certificate_verify: CertificateVerify,
+ # HandshakeType.finished: Finished,
+ # HandshakeType.new_session_ticket: NewSessionTicket,
+ # HandshakeType.key_update: KeyUpdate,
+ },
+ default=FixedSized(this.length, GreedyBytes),
+ ),
+)
+
+# Records
+
+ContentType = Enum(
+ Byte,
+ invalid=0,
+ change_cipher_spec=20,
+ alert=21,
+ handshake=22,
+ application_data=23,
+)
+
+Plaintext = Struct(
+ "type" / ContentType,
+ "legacy_record_version" / ProtocolVersion,
+ "length" / Int16ub,
+ "fragment" / FixedSized(this.length, GreedyBytes),
+)
--
2.25.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2021-08-25 22:25 ` Zhihong Yu <[email protected]>
2021-08-25 23:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Zhihong Yu <[email protected]>
2 siblings, 1 reply; 194+ messages in thread
From: Zhihong Yu @ 2021-08-25 22:25 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
On Wed, Aug 25, 2021 at 11:42 AM Jacob Champion <[email protected]>
wrote:
> On Tue, 2021-06-22 at 23:22 +0000, Jacob Champion wrote:
> > On Fri, 2021-06-18 at 11:31 +0300, Heikki Linnakangas wrote:
> > >
> > > A few small things caught my eye in the backend oauth_exchange
> function:
> > >
> > > > + /* Handle the client's initial message. */
> > > > + p = strdup(input);
> > >
> > > this strdup() should be pstrdup().
> >
> > Thanks, I'll fix that in the next re-roll.
> >
> > > In the same function, there are a bunch of reports like this:
> > >
> > > > ereport(ERROR,
> > > > + (errcode(ERRCODE_PROTOCOL_VIOLATION),
> > > > + errmsg("malformed OAUTHBEARER message"),
> > > > + errdetail("Comma expected, but found
> character \"%s\".",
> > > > + sanitize_char(*p))));
> > >
> > > I don't think the double quotes are needed here, because sanitize_char
> > > will return quotes if it's a single character. So it would end up
> > > looking like this: ... found character "'x'".
> >
> > I'll fix this too. Thanks!
>
> v2, attached, incorporates Heikki's suggested fixes and also rebases on
> top of latest HEAD, which had the SASL refactoring changes committed
> last month.
>
> The biggest change from the last patchset is 0001, an attempt at
> enabling jsonapi in the frontend without the use of palloc(), based on
> suggestions by Michael and Tom from last commitfest. I've also made
> some improvements to the pytest suite. No major changes to the OAuth
> implementation yet.
>
> --Jacob
>
Hi,
For v2-0001-common-jsonapi-support-FRONTEND-clients.patch :
+ /* Clean up. */
+ termJsonLexContext(&lex);
At the end of termJsonLexContext(), empty is copied to lex. For stack
based JsonLexContext, the copy seems unnecessary.
Maybe introduce a boolean parameter for termJsonLexContext() to signal that
the copy can be omitted ?
+#ifdef FRONTEND
+ /* make sure initialization succeeded */
+ if (lex->strval == NULL)
+ return JSON_OUT_OF_MEMORY;
Should PQExpBufferBroken(lex->strval) be used for the check ?
Thanks
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 22:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Zhihong Yu <[email protected]>
@ 2021-08-25 23:24 ` Zhihong Yu <[email protected]>
2021-08-26 16:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Zhihong Yu @ 2021-08-25 23:24 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
On Wed, Aug 25, 2021 at 3:25 PM Zhihong Yu <[email protected]> wrote:
>
>
> On Wed, Aug 25, 2021 at 11:42 AM Jacob Champion <[email protected]>
> wrote:
>
>> On Tue, 2021-06-22 at 23:22 +0000, Jacob Champion wrote:
>> > On Fri, 2021-06-18 at 11:31 +0300, Heikki Linnakangas wrote:
>> > >
>> > > A few small things caught my eye in the backend oauth_exchange
>> function:
>> > >
>> > > > + /* Handle the client's initial message. */
>> > > > + p = strdup(input);
>> > >
>> > > this strdup() should be pstrdup().
>> >
>> > Thanks, I'll fix that in the next re-roll.
>> >
>> > > In the same function, there are a bunch of reports like this:
>> > >
>> > > > ereport(ERROR,
>> > > > + (errcode(ERRCODE_PROTOCOL_VIOLATION),
>> > > > + errmsg("malformed OAUTHBEARER message"),
>> > > > + errdetail("Comma expected, but found
>> character \"%s\".",
>> > > > + sanitize_char(*p))));
>> > >
>> > > I don't think the double quotes are needed here, because
>> sanitize_char
>> > > will return quotes if it's a single character. So it would end up
>> > > looking like this: ... found character "'x'".
>> >
>> > I'll fix this too. Thanks!
>>
>> v2, attached, incorporates Heikki's suggested fixes and also rebases on
>> top of latest HEAD, which had the SASL refactoring changes committed
>> last month.
>>
>> The biggest change from the last patchset is 0001, an attempt at
>> enabling jsonapi in the frontend without the use of palloc(), based on
>> suggestions by Michael and Tom from last commitfest. I've also made
>> some improvements to the pytest suite. No major changes to the OAuth
>> implementation yet.
>>
>> --Jacob
>>
> Hi,
> For v2-0001-common-jsonapi-support-FRONTEND-clients.patch :
>
> + /* Clean up. */
> + termJsonLexContext(&lex);
>
> At the end of termJsonLexContext(), empty is copied to lex. For stack
> based JsonLexContext, the copy seems unnecessary.
> Maybe introduce a boolean parameter for termJsonLexContext() to signal
> that the copy can be omitted ?
>
> +#ifdef FRONTEND
> + /* make sure initialization succeeded */
> + if (lex->strval == NULL)
> + return JSON_OUT_OF_MEMORY;
>
> Should PQExpBufferBroken(lex->strval) be used for the check ?
>
> Thanks
>
Hi,
For v2-0002-libpq-add-OAUTHBEARER-SASL-mechanism.patch :
+ i_init_session(&session);
+
+ if (!conn->oauth_client_id)
+ {
+ /* We can't talk to a server without a client identifier. */
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("no oauth_client_id is set for
the connection"));
+ goto cleanup;
Can conn->oauth_client_id check be performed ahead of i_init_session() ?
That way, ```goto cleanup``` can be replaced with return.
+ if (!error_code || (strcmp(error_code, "authorization_pending")
+ && strcmp(error_code, "slow_down")))
What if, in the future, there is error code different from the above two
which doesn't represent "OAuth token retrieval failed" scenario ?
For client_initial_response(),
+ token_buf = createPQExpBuffer();
+ if (!token_buf)
+ goto cleanup;
If token_buf is NULL, there doesn't seem to be anything to free. We can
return directly.
Cheers
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 22:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Zhihong Yu <[email protected]>
2021-08-25 23:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Zhihong Yu <[email protected]>
@ 2021-08-26 16:13 ` Jacob Champion <[email protected]>
2021-08-26 16:20 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Zhihong Yu <[email protected]>
2021-08-27 02:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Michael Paquier <[email protected]>
0 siblings, 2 replies; 194+ messages in thread
From: Jacob Champion @ 2021-08-26 16:13 UTC (permalink / raw)
To: [email protected] <[email protected]>; +Cc: [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
On Wed, 2021-08-25 at 15:25 -0700, Zhihong Yu wrote:
>
> Hi,
> For v2-0001-common-jsonapi-support-FRONTEND-clients.patch :
>
> + /* Clean up. */
> + termJsonLexContext(&lex);
>
> At the end of termJsonLexContext(), empty is copied to lex. For stack
> based JsonLexContext, the copy seems unnecessary.
> Maybe introduce a boolean parameter for termJsonLexContext() to
> signal that the copy can be omitted ?
Do you mean heap-based? i.e. destroyJsonLexContext() does an
unnecessary copy before free? Yeah, in that case it's not super useful,
but I think I'd want some evidence that the performance hit matters
before optimizing it.
Are there any other internal APIs that take a boolean parameter like
that? If not, I think we'd probably just want to remove the copy
entirely if it's a problem.
> +#ifdef FRONTEND
> + /* make sure initialization succeeded */
> + if (lex->strval == NULL)
> + return JSON_OUT_OF_MEMORY;
>
> Should PQExpBufferBroken(lex->strval) be used for the check ?
It should be okay to continue if the strval is broken but non-NULL,
since it's about to be reset. That has the fringe benefit of allowing
the function to go as far as possible without failing, though that's
probably a pretty weak justification.
In practice, do you think that the probability of success is low enough
that we should just short-circuit and be done with it?
On Wed, 2021-08-25 at 16:24 -0700, Zhihong Yu wrote:
>
> For v2-0002-libpq-add-OAUTHBEARER-SASL-mechanism.patch :
>
> + i_init_session(&session);
> +
> + if (!conn->oauth_client_id)
> + {
> + /* We can't talk to a server without a client identifier. */
> + appendPQExpBufferStr(&conn->errorMessage,
> + libpq_gettext("no oauth_client_id is set for the connection"));
> + goto cleanup;
>
> Can conn->oauth_client_id check be performed ahead
> of i_init_session() ? That way, ```goto cleanup``` can be replaced
> with return.
Yeah, I think that makes sense. FYI, this is probably one of the
functions that will be rewritten completely once iddawc is removed.
> + if (!error_code || (strcmp(error_code, "authorization_pending")
> + && strcmp(error_code, "slow_down")))
>
> What if, in the future, there is error code different from the above
> two which doesn't represent "OAuth token retrieval failed" scenario ?
We'd have to update our code; that would be a breaking change to the
Device Authorization spec. Here's what it says today [1]:
The "authorization_pending" and "slow_down" error codes define
particularly unique behavior, as they indicate that the OAuth client
should continue to poll the token endpoint by repeating the token
request (implementing the precise behavior defined above). If the
client receives an error response with any other error code, it MUST
stop polling and SHOULD react accordingly, for example, by displaying
an error to the user.
> For client_initial_response(),
>
> + token_buf = createPQExpBuffer();
> + if (!token_buf)
> + goto cleanup;
>
> If token_buf is NULL, there doesn't seem to be anything to free. We
> can return directly.
That's true today, but implementations have a habit of changing. I
personally prefer not to introduce too many exit points from a function
that's already using goto. In my experience, that makes future
maintenance harder.
Thanks for the reviews! Have you been able to give the patchset a try
with an OAuth deployment?
--Jacob
[1] https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 22:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Zhihong Yu <[email protected]>
2021-08-25 23:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Zhihong Yu <[email protected]>
2021-08-26 16:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2021-08-26 16:20 ` Zhihong Yu <[email protected]>
1 sibling, 0 replies; 194+ messages in thread
From: Zhihong Yu @ 2021-08-26 16:20 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
On Thu, Aug 26, 2021 at 9:13 AM Jacob Champion <[email protected]> wrote:
> On Wed, 2021-08-25 at 15:25 -0700, Zhihong Yu wrote:
> >
> > Hi,
> > For v2-0001-common-jsonapi-support-FRONTEND-clients.patch :
> >
> > + /* Clean up. */
> > + termJsonLexContext(&lex);
> >
> > At the end of termJsonLexContext(), empty is copied to lex. For stack
> > based JsonLexContext, the copy seems unnecessary.
> > Maybe introduce a boolean parameter for termJsonLexContext() to
> > signal that the copy can be omitted ?
>
> Do you mean heap-based? i.e. destroyJsonLexContext() does an
> unnecessary copy before free? Yeah, in that case it's not super useful,
> but I think I'd want some evidence that the performance hit matters
> before optimizing it.
>
> Are there any other internal APIs that take a boolean parameter like
> that? If not, I think we'd probably just want to remove the copy
> entirely if it's a problem.
>
> > +#ifdef FRONTEND
> > + /* make sure initialization succeeded */
> > + if (lex->strval == NULL)
> > + return JSON_OUT_OF_MEMORY;
> >
> > Should PQExpBufferBroken(lex->strval) be used for the check ?
>
> It should be okay to continue if the strval is broken but non-NULL,
> since it's about to be reset. That has the fringe benefit of allowing
> the function to go as far as possible without failing, though that's
> probably a pretty weak justification.
>
> In practice, do you think that the probability of success is low enough
> that we should just short-circuit and be done with it?
>
> On Wed, 2021-08-25 at 16:24 -0700, Zhihong Yu wrote:
> >
> > For v2-0002-libpq-add-OAUTHBEARER-SASL-mechanism.patch :
> >
> > + i_init_session(&session);
> > +
> > + if (!conn->oauth_client_id)
> > + {
> > + /* We can't talk to a server without a client identifier. */
> > + appendPQExpBufferStr(&conn->errorMessage,
> > + libpq_gettext("no oauth_client_id is set
> for the connection"));
> > + goto cleanup;
> >
> > Can conn->oauth_client_id check be performed ahead
> > of i_init_session() ? That way, ```goto cleanup``` can be replaced
> > with return.
>
> Yeah, I think that makes sense. FYI, this is probably one of the
> functions that will be rewritten completely once iddawc is removed.
>
> > + if (!error_code || (strcmp(error_code, "authorization_pending")
> > + && strcmp(error_code, "slow_down")))
> >
> > What if, in the future, there is error code different from the above
> > two which doesn't represent "OAuth token retrieval failed" scenario ?
>
> We'd have to update our code; that would be a breaking change to the
> Device Authorization spec. Here's what it says today [1]:
>
> The "authorization_pending" and "slow_down" error codes define
> particularly unique behavior, as they indicate that the OAuth client
> should continue to poll the token endpoint by repeating the token
> request (implementing the precise behavior defined above). If the
> client receives an error response with any other error code, it MUST
> stop polling and SHOULD react accordingly, for example, by displaying
> an error to the user.
>
> > For client_initial_response(),
> >
> > + token_buf = createPQExpBuffer();
> > + if (!token_buf)
> > + goto cleanup;
> >
> > If token_buf is NULL, there doesn't seem to be anything to free. We
> > can return directly.
>
> That's true today, but implementations have a habit of changing. I
> personally prefer not to introduce too many exit points from a function
> that's already using goto. In my experience, that makes future
> maintenance harder.
>
> Thanks for the reviews! Have you been able to give the patchset a try
> with an OAuth deployment?
>
> --Jacob
>
> [1] https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
Hi,
bq. destroyJsonLexContext() does an unnecessary copy before free? Yeah, in
that case it's not super useful,
but I think I'd want some evidence that the performance hit matters before
optimizing it.
Yes I agree.
bq. In practice, do you think that the probability of success is low enough
that we should just short-circuit and be done with it?
Haven't had a chance to try your patches out yet.
I will leave this to people who are more familiar with OAuth
implementation(s).
bq. I personally prefer not to introduce too many exit points from a
function that's already using goto.
Fair enough.
Cheers
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 22:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Zhihong Yu <[email protected]>
2021-08-25 23:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Zhihong Yu <[email protected]>
2021-08-26 16:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2021-08-27 02:32 ` Michael Paquier <[email protected]>
2021-08-31 20:48 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Michael Paquier @ 2021-08-27 02:32 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: [email protected] <[email protected]>; [email protected] <[email protected]>; pgsql-hackers
On Thu, Aug 26, 2021 at 04:13:08PM +0000, Jacob Champion wrote:
> Do you mean heap-based? i.e. destroyJsonLexContext() does an
> unnecessary copy before free? Yeah, in that case it's not super useful,
> but I think I'd want some evidence that the performance hit matters
> before optimizing it.
As an authentication code path, the impact is minimal and my take on
that would be to keep the code simple. Now if you'd really wish to
stress that without relying on the backend, one simple way is to use
pgbench -C -n with a mostly-empty script (one meta-command) coupled
with some profiling.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 22:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Zhihong Yu <[email protected]>
2021-08-25 23:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Zhihong Yu <[email protected]>
2021-08-26 16:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-27 02:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Michael Paquier <[email protected]>
@ 2021-08-31 20:48 ` Jacob Champion <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2021-08-31 20:48 UTC (permalink / raw)
To: [email protected] <[email protected]>; +Cc: [email protected] <[email protected]>; [email protected] <[email protected]>; pgsql-hackers
On Fri, 2021-08-27 at 11:32 +0900, Michael Paquier wrote:
> Now if you'd really wish to
> stress that without relying on the backend, one simple way is to use
> pgbench -C -n with a mostly-empty script (one meta-command) coupled
> with some profiling.
Ah, thanks! I'll add that to the toolbox.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2022-03-26 00:00 ` Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2022-03-26 00:00 UTC (permalink / raw)
To: pgsql-hackers; +Cc: [email protected] <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>
On Fri, 2022-03-04 at 19:13 +0000, Jacob Champion wrote:
> v3 rebases this patchset over the top of Samay's pluggable auth
> provider API [1], included here as patches 0001-3.
v4 rebases over the latest version of the pluggable auth patchset
(included as 0001-4). Note that there's a recent conflict as
of d4781d887; use an older commit as the base (or wait for the other
thread to be updated).
--Jacob
Attachments:
[text/x-patch] v4-0001-Add-support-for-custom-authentication-methods.patch (11.6K, ../../[email protected]/2-v4-0001-Add-support-for-custom-authentication-methods.patch)
download | inline diff:
From 575431b4e035c266b55a25414f802fbf8ba16b97 Mon Sep 17 00:00:00 2001
From: Samay Sharma <[email protected]>
Date: Tue, 15 Feb 2022 22:23:29 -0800
Subject: [PATCH v4 01/10] Add support for custom authentication methods
Currently, PostgreSQL supports only a set of pre-defined authentication
methods. This patch adds support for 2 hooks which allow users to add
their custom authentication methods by defining a check function and an
error function. Users can then use these methods by using a new "custom"
keyword in pg_hba.conf and specifying the authentication provider they
want to use.
---
src/backend/libpq/auth.c | 108 ++++++++++++++++++++++++++++++++-------
src/backend/libpq/hba.c | 44 ++++++++++++++++
src/include/libpq/auth.h | 37 ++++++++++++++
src/include/libpq/hba.h | 2 +
4 files changed, 172 insertions(+), 19 deletions(-)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index efc53f3135..375ee33892 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -47,8 +47,6 @@
*----------------------------------------------------------------
*/
static void auth_failed(Port *port, int status, const char *logdetail);
-static char *recv_password_packet(Port *port);
-static void set_authn_id(Port *port, const char *id);
/*----------------------------------------------------------------
@@ -206,22 +204,11 @@ static int pg_SSPI_make_upn(char *accountname,
static int CheckRADIUSAuth(Port *port);
static int PerformRadiusTransaction(const char *server, const char *secret, const char *portstr, const char *identifier, const char *user_name, const char *passwd);
-
-/*
- * Maximum accepted size of GSS and SSPI authentication tokens.
- * We also use this as a limit on ordinary password packet lengths.
- *
- * Kerberos tickets are usually quite small, but the TGTs issued by Windows
- * domain controllers include an authorization field known as the Privilege
- * Attribute Certificate (PAC), which contains the user's Windows permissions
- * (group memberships etc.). The PAC is copied into all tickets obtained on
- * the basis of this TGT (even those issued by Unix realms which the Windows
- * realm trusts), and can be several kB in size. The maximum token size
- * accepted by Windows systems is determined by the MaxAuthToken Windows
- * registry setting. Microsoft recommends that it is not set higher than
- * 65535 bytes, so that seems like a reasonable limit for us as well.
+/*----------------------------------------------------------------
+ * Custom Authentication
+ *----------------------------------------------------------------
*/
-#define PG_MAX_AUTH_TOKEN_LENGTH 65535
+static List *custom_auth_providers = NIL;
/*----------------------------------------------------------------
* Global authentication functions
@@ -311,6 +298,15 @@ auth_failed(Port *port, int status, const char *logdetail)
case uaRADIUS:
errstr = gettext_noop("RADIUS authentication failed for user \"%s\"");
break;
+ case uaCustom:
+ {
+ CustomAuthProvider *provider = get_provider_by_name(port->hba->custom_provider);
+ if (provider->auth_error_hook)
+ errstr = provider->auth_error_hook(port);
+ else
+ errstr = gettext_noop("Custom authentication failed for user \"%s\"");
+ break;
+ }
default:
errstr = gettext_noop("authentication failed for user \"%s\": invalid authentication method");
break;
@@ -345,7 +341,7 @@ auth_failed(Port *port, int status, const char *logdetail)
* lifetime of the Port, so it is safe to pass a string that is managed by an
* external library.
*/
-static void
+void
set_authn_id(Port *port, const char *id)
{
Assert(id);
@@ -630,6 +626,13 @@ ClientAuthentication(Port *port)
case uaTrust:
status = STATUS_OK;
break;
+ case uaCustom:
+ {
+ CustomAuthProvider *provider = get_provider_by_name(port->hba->custom_provider);
+ if (provider->auth_check_hook)
+ status = provider->auth_check_hook(port);
+ break;
+ }
}
if ((status == STATUS_OK && port->hba->clientcert == clientCertFull)
@@ -689,7 +692,7 @@ sendAuthRequest(Port *port, AuthRequest areq, const char *extradata, int extrale
*
* Returns NULL if couldn't get password, else palloc'd string.
*/
-static char *
+char *
recv_password_packet(Port *port)
{
StringInfoData buf;
@@ -3343,3 +3346,70 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por
}
} /* while (true) */
}
+
+/*----------------------------------------------------------------
+ * Custom authentication
+ *----------------------------------------------------------------
+ */
+
+/*
+ * RegisterAuthProvider registers a custom authentication provider to be
+ * used for authentication. It validates the inputs and adds the provider
+ * name and it's hooks to a list of loaded providers. The right provider's
+ * hooks can then be called based on the provider name specified in
+ * pg_hba.conf.
+ *
+ * This function should be called in _PG_init() by any extension looking to
+ * add a custom authentication method.
+ */
+void RegisterAuthProvider(const char *provider_name,
+ CustomAuthenticationCheck_hook_type AuthenticationCheckFunction,
+ CustomAuthenticationError_hook_type AuthenticationErrorFunction)
+{
+ CustomAuthProvider *provider = NULL;
+ MemoryContext old_context;
+
+ if (provider_name == NULL)
+ {
+ ereport(ERROR,
+ (errmsg("cannot register authentication provider without name")));
+ }
+
+ if (AuthenticationCheckFunction == NULL)
+ {
+ ereport(ERROR,
+ (errmsg("cannot register authentication provider without a check function")));
+ }
+
+ /*
+ * Allocate in top memory context as we need to read this whenever
+ * we parse pg_hba.conf
+ */
+ old_context = MemoryContextSwitchTo(TopMemoryContext);
+ provider = palloc(sizeof(CustomAuthProvider));
+ provider->name = MemoryContextStrdup(TopMemoryContext,provider_name);
+ provider->auth_check_hook = AuthenticationCheckFunction;
+ provider->auth_error_hook = AuthenticationErrorFunction;
+ custom_auth_providers = lappend(custom_auth_providers, provider);
+ MemoryContextSwitchTo(old_context);
+}
+
+/*
+ * Returns the authentication provider (which includes it's
+ * callback functions) based on name specified.
+ */
+CustomAuthProvider *get_provider_by_name(const char *name)
+{
+ ListCell *lc;
+
+ foreach(lc, custom_auth_providers)
+ {
+ CustomAuthProvider *provider = (CustomAuthProvider *) lfirst(lc);
+ if (strcmp(provider->name,name) == 0)
+ {
+ return provider;
+ }
+ }
+
+ return NULL;
+}
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 90953c38f3..9f15252789 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -31,6 +31,7 @@
#include "common/ip.h"
#include "common/string.h"
#include "funcapi.h"
+#include "libpq/auth.h"
#include "libpq/ifaddr.h"
#include "libpq/libpq.h"
#include "miscadmin.h"
@@ -134,6 +135,7 @@ static const char *const UserAuthName[] =
"ldap",
"cert",
"radius",
+ "custom",
"peer"
};
@@ -1399,6 +1401,8 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
#endif
else if (strcmp(token->string, "radius") == 0)
parsedline->auth_method = uaRADIUS;
+ else if (strcmp(token->string, "custom") == 0)
+ parsedline->auth_method = uaCustom;
else
{
ereport(elevel,
@@ -1691,6 +1695,14 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
parsedline->clientcert = clientCertFull;
}
+ /*
+ * Ensure that the provider name is specified for custom authentication method.
+ */
+ if (parsedline->auth_method == uaCustom)
+ {
+ MANDATORY_AUTH_ARG(parsedline->custom_provider, "provider", "custom");
+ }
+
return parsedline;
}
@@ -2102,6 +2114,31 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
hbaline->radiusidentifiers = parsed_identifiers;
hbaline->radiusidentifiers_s = pstrdup(val);
}
+ else if (strcmp(name, "provider") == 0)
+ {
+ REQUIRE_AUTH_OPTION(uaCustom, "provider", "custom");
+
+ /*
+ * Verify that the provider mentioned is loaded via shared_preload_libraries.
+ */
+
+ if (get_provider_by_name(val) == NULL)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("cannot use authentication provider %s",val),
+ errhint("Load authentication provider via shared_preload_libraries."),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, HbaFileName)));
+ *err_msg = psprintf("cannot use authentication provider %s", val);
+
+ return false;
+ }
+ else
+ {
+ hbaline->custom_provider = pstrdup(val);
+ }
+ }
else
{
ereport(elevel,
@@ -2442,6 +2479,13 @@ gethba_options(HbaLine *hba)
CStringGetTextDatum(psprintf("radiusports=%s", hba->radiusports_s));
}
+ if (hba->auth_method == uaCustom)
+ {
+ if (hba->custom_provider)
+ options[noptions++] =
+ CStringGetTextDatum(psprintf("provider=%s",hba->custom_provider));
+ }
+
/* If you add more options, consider increasing MAX_HBA_OPTIONS. */
Assert(noptions <= MAX_HBA_OPTIONS);
diff --git a/src/include/libpq/auth.h b/src/include/libpq/auth.h
index 6d7ee1acb9..7aff98d919 100644
--- a/src/include/libpq/auth.h
+++ b/src/include/libpq/auth.h
@@ -23,9 +23,46 @@ extern char *pg_krb_realm;
extern void ClientAuthentication(Port *port);
extern void sendAuthRequest(Port *port, AuthRequest areq, const char *extradata,
int extralen);
+extern void set_authn_id(Port *port, const char *id);
+extern char *recv_password_packet(Port *port);
/* Hook for plugins to get control in ClientAuthentication() */
+typedef int (*CustomAuthenticationCheck_hook_type) (Port *);
typedef void (*ClientAuthentication_hook_type) (Port *, int);
extern PGDLLIMPORT ClientAuthentication_hook_type ClientAuthentication_hook;
+/* Hook for plugins to report error messages in auth_failed() */
+typedef const char * (*CustomAuthenticationError_hook_type) (Port *);
+
+extern void RegisterAuthProvider
+ (const char *provider_name,
+ CustomAuthenticationCheck_hook_type CustomAuthenticationCheck_hook,
+ CustomAuthenticationError_hook_type CustomAuthenticationError_hook);
+
+/* Declarations for custom authentication providers */
+typedef struct CustomAuthProvider
+{
+ const char *name;
+ CustomAuthenticationCheck_hook_type auth_check_hook;
+ CustomAuthenticationError_hook_type auth_error_hook;
+} CustomAuthProvider;
+
+extern CustomAuthProvider *get_provider_by_name(const char *name);
+
+/*
+ * Maximum accepted size of GSS and SSPI authentication tokens.
+ * We also use this as a limit on ordinary password packet lengths.
+ *
+ * Kerberos tickets are usually quite small, but the TGTs issued by Windows
+ * domain controllers include an authorization field known as the Privilege
+ * Attribute Certificate (PAC), which contains the user's Windows permissions
+ * (group memberships etc.). The PAC is copied into all tickets obtained on
+ * the basis of this TGT (even those issued by Unix realms which the Windows
+ * realm trusts), and can be several kB in size. The maximum token size
+ * accepted by Windows systems is determined by the MaxAuthToken Windows
+ * registry setting. Microsoft recommends that it is not set higher than
+ * 65535 bytes, so that seems like a reasonable limit for us as well.
+ */
+#define PG_MAX_AUTH_TOKEN_LENGTH 65535
+
#endif /* AUTH_H */
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 8d9f3821b1..48490c44ed 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -38,6 +38,7 @@ typedef enum UserAuth
uaLDAP,
uaCert,
uaRADIUS,
+ uaCustom,
uaPeer
#define USER_AUTH_LAST uaPeer /* Must be last value of this enum */
} UserAuth;
@@ -120,6 +121,7 @@ typedef struct HbaLine
char *radiusidentifiers_s;
List *radiusports;
char *radiusports_s;
+ char *custom_provider;
} HbaLine;
typedef struct IdentLine
--
2.25.1
[text/x-patch] v4-0002-Add-sample-extension-to-test-custom-auth-provider.patch (4.4K, ../../[email protected]/3-v4-0002-Add-sample-extension-to-test-custom-auth-provider.patch)
download | inline diff:
From cb4131d8424861826e443708bc0f4e6baa76c871 Mon Sep 17 00:00:00 2001
From: Samay Sharma <[email protected]>
Date: Tue, 15 Feb 2022 22:28:40 -0800
Subject: [PATCH v4 02/10] Add sample extension to test custom auth provider
hooks
This change adds a new extension to src/test/modules to
test the custom authentication provider hooks. In this
extension, we use an array to define which users to
authenticate and what passwords to use. We then get
encrypted passwords from the client and match them with
the encrypted version of the password in the array.
---
src/include/libpq/scram.h | 2 +-
src/test/modules/test_auth_provider/Makefile | 16 ++++
.../test_auth_provider/test_auth_provider.c | 86 +++++++++++++++++++
3 files changed, 103 insertions(+), 1 deletion(-)
create mode 100644 src/test/modules/test_auth_provider/Makefile
create mode 100644 src/test/modules/test_auth_provider/test_auth_provider.c
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
index e60992a0d2..c51e848c24 100644
--- a/src/include/libpq/scram.h
+++ b/src/include/libpq/scram.h
@@ -18,7 +18,7 @@
#include "libpq/sasl.h"
/* SASL implementation callbacks */
-extern const pg_be_sasl_mech pg_be_scram_mech;
+extern PGDLLIMPORT const pg_be_sasl_mech pg_be_scram_mech;
/* Routines to handle and check SCRAM-SHA-256 secret */
extern char *pg_be_scram_build_secret(const char *password);
diff --git a/src/test/modules/test_auth_provider/Makefile b/src/test/modules/test_auth_provider/Makefile
new file mode 100644
index 0000000000..17971a5c7a
--- /dev/null
+++ b/src/test/modules/test_auth_provider/Makefile
@@ -0,0 +1,16 @@
+# src/test/modules/test_auth_provider/Makefile
+
+MODULE_big = test_auth_provider
+OBJS = test_auth_provider.o
+PGFILEDESC = "test_auth_provider - provider to test auth hooks"
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_auth_provider
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_auth_provider/test_auth_provider.c b/src/test/modules/test_auth_provider/test_auth_provider.c
new file mode 100644
index 0000000000..7c4b1f3500
--- /dev/null
+++ b/src/test/modules/test_auth_provider/test_auth_provider.c
@@ -0,0 +1,86 @@
+/* -------------------------------------------------------------------------
+ *
+ * test_auth_provider.c
+ * example authentication provider plugin
+ *
+ * Copyright (c) 2022, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/test_auth_provider/test_auth_provider.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "fmgr.h"
+#include "libpq/auth.h"
+#include "libpq/libpq.h"
+#include "libpq/scram.h"
+
+PG_MODULE_MAGIC;
+
+void _PG_init(void);
+
+static char *get_encrypted_password_for_user(char *user_name);
+
+/*
+ * List of usernames / passwords to approve. Here we are not
+ * getting passwords from Postgres but from this list. In a more real-life
+ * extension, you can fetch valid credentials and authentication tokens /
+ * passwords from an external authentication provider.
+ */
+char credentials[3][3][50] = {
+ {"bob","alice","carol"},
+ {"bob123","alice123","carol123"}
+};
+
+static int TestAuthenticationCheck(Port *port)
+{
+ int result = STATUS_ERROR;
+ char *real_pass;
+ const char *logdetail = NULL;
+
+ real_pass = get_encrypted_password_for_user(port->user_name);
+ if (real_pass)
+ {
+ result = CheckSASLAuth(&pg_be_scram_mech, port, real_pass, &logdetail);
+ pfree(real_pass);
+ }
+
+ if (result == STATUS_OK)
+ set_authn_id(port, port->user_name);
+
+ return result;
+}
+
+/*
+ * Get SCRAM encrypted version of the password for user.
+ */
+static char *
+get_encrypted_password_for_user(char *user_name)
+{
+ char *password = NULL;
+ int i;
+ for (i=0; i<3; i++)
+ {
+ if (strcmp(user_name, credentials[0][i]) == 0)
+ {
+ password = pstrdup(pg_be_scram_build_secret(credentials[1][i]));
+ }
+ }
+
+ return password;
+}
+
+static const char *TestAuthenticationError(Port *port)
+{
+ char *error_message = (char *)palloc (100);
+ sprintf(error_message, "Test authentication failed for user %s", port->user_name);
+ return error_message;
+}
+
+void
+_PG_init(void)
+{
+ RegisterAuthProvider("test", TestAuthenticationCheck, TestAuthenticationError);
+}
--
2.25.1
[text/x-patch] v4-0003-Add-tests-for-test_auth_provider-extension.patch (6.1K, ../../[email protected]/4-v4-0003-Add-tests-for-test_auth_provider-extension.patch)
download | inline diff:
From 24702486bfaca691d6ca9388544fb23e6d765055 Mon Sep 17 00:00:00 2001
From: Samay Sharma <[email protected]>
Date: Wed, 16 Feb 2022 12:28:36 -0800
Subject: [PATCH v4 03/10] Add tests for test_auth_provider extension
Add tap tests for test_auth_provider extension allow make check in
src/test/modules to run them.
---
src/test/modules/Makefile | 1 +
src/test/modules/test_auth_provider/Makefile | 2 +
.../test_auth_provider/t/001_custom_auth.pl | 125 ++++++++++++++++++
3 files changed, 128 insertions(+)
create mode 100644 src/test/modules/test_auth_provider/t/001_custom_auth.pl
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 9090226daa..d0d461ef9e 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -14,6 +14,7 @@ SUBDIRS = \
plsample \
snapshot_too_old \
spgist_name_ops \
+ test_auth_provider \
test_bloomfilter \
test_ddl_deparse \
test_extensions \
diff --git a/src/test/modules/test_auth_provider/Makefile b/src/test/modules/test_auth_provider/Makefile
index 17971a5c7a..7d601cf7d5 100644
--- a/src/test/modules/test_auth_provider/Makefile
+++ b/src/test/modules/test_auth_provider/Makefile
@@ -4,6 +4,8 @@ MODULE_big = test_auth_provider
OBJS = test_auth_provider.o
PGFILEDESC = "test_auth_provider - provider to test auth hooks"
+TAP_TESTS = 1
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/src/test/modules/test_auth_provider/t/001_custom_auth.pl b/src/test/modules/test_auth_provider/t/001_custom_auth.pl
new file mode 100644
index 0000000000..3b7472dc7f
--- /dev/null
+++ b/src/test/modules/test_auth_provider/t/001_custom_auth.pl
@@ -0,0 +1,125 @@
+
+# Copyright (c) 2021-2022, PostgreSQL Global Development Group
+
+# Set of tests for testing custom authentication hooks.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Delete pg_hba.conf from the given node, add a new entry to it
+# and then execute a reload to refresh it.
+sub reset_pg_hba
+{
+ my $node = shift;
+ my $hba_method = shift;
+
+ unlink($node->data_dir . '/pg_hba.conf');
+ # just for testing purposes, use a continuation line
+ $node->append_conf('pg_hba.conf', "local all all\\\n $hba_method");
+ $node->reload;
+ return;
+}
+
+# Test if you get expected results in pg_hba_file_rules error column after
+# changing pg_hba.conf and reloading it.
+sub test_hba_reload
+{
+ my ($node, $method, $expected_res) = @_;
+ my $status_string = 'failed';
+ $status_string = 'success' if ($expected_res eq 0);
+ my $testname = "pg_hba.conf reload $status_string for method $method";
+
+ reset_pg_hba($node, $method);
+
+ my ($cmdret, $stdout, $stderr) = $node->psql("postgres",
+ "select count(*) from pg_hba_file_rules where error is not null",extra_params => ['-U','bob']);
+
+ is($stdout, $expected_res, $testname);
+}
+
+# Test access for a single role, useful to wrap all tests into one. Extra
+# named parameters are passed to connect_ok/fails as-is.
+sub test_role
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($node, $role, $method, $expected_res, %params) = @_;
+ my $status_string = 'failed';
+ $status_string = 'success' if ($expected_res eq 0);
+
+ my $connstr = "user=$role";
+ my $testname =
+ "authentication $status_string for method $method, role $role";
+
+ if ($expected_res eq 0)
+ {
+ $node->connect_ok($connstr, $testname, %params);
+ }
+ else
+ {
+ # No checks of the error message, only the status code.
+ $node->connect_fails($connstr, $testname, %params);
+ }
+}
+
+# Initialize server node
+my $node = PostgreSQL::Test::Cluster->new('server');
+$node->init;
+$node->append_conf('postgresql.conf', "log_connections = on\n");
+$node->append_conf('postgresql.conf', "shared_preload_libraries = 'test_auth_provider.so'\n");
+$node->start;
+
+$node->safe_psql('postgres', "CREATE ROLE bob SUPERUSER LOGIN;");
+$node->safe_psql('postgres', "CREATE ROLE alice LOGIN;");
+$node->safe_psql('postgres', "CREATE ROLE test LOGIN;");
+
+# Add custom auth method to pg_hba.conf
+reset_pg_hba($node, 'custom provider=test');
+
+# Test that users are able to login with correct passwords.
+$ENV{"PGPASSWORD"} = 'bob123';
+test_role($node, 'bob', 'custom', 0, log_like => [qr/connection authorized: user=bob/]);
+$ENV{"PGPASSWORD"} = 'alice123';
+test_role($node, 'alice', 'custom', 0, log_like => [qr/connection authorized: user=alice/]);
+
+# Test that bad passwords are rejected.
+$ENV{"PGPASSWORD"} = 'badpassword';
+test_role($node, 'bob', 'custom', 2, log_unlike => [qr/connection authorized:/]);
+test_role($node, 'alice', 'custom', 2, log_unlike => [qr/connection authorized:/]);
+
+# Test that users not in authentication list are rejected.
+test_role($node, 'test', 'custom', 2, log_unlike => [qr/connection authorized:/]);
+
+$ENV{"PGPASSWORD"} = 'bob123';
+
+# Tests for invalid auth options
+
+# Test that an incorrect provider name is not accepted.
+test_hba_reload($node, 'custom provider=wrong', 1);
+
+# Test that specifying provider option with different auth method is not allowed.
+test_hba_reload($node, 'trust provider=test', 1);
+
+# Test that provider name is a mandatory option for custom auth.
+test_hba_reload($node, 'custom', 1);
+
+# Test that correct provider name allows reload to succeed.
+test_hba_reload($node, 'custom provider=test', 0);
+
+# Custom auth modules require mentioning extension in shared_preload_libraries.
+
+# Remove extension from shared_preload_libraries and try to restart.
+$node->adjust_conf('postgresql.conf', 'shared_preload_libraries', "''");
+command_fails(['pg_ctl', '-w', '-D', $node->data_dir, '-l', $node->logfile, 'restart'],'restart with empty shared_preload_libraries failed');
+
+# Fix shared_preload_libraries and confirm that you can now restart.
+$node->adjust_conf('postgresql.conf', 'shared_preload_libraries', "'test_auth_provider.so'");
+command_ok(['pg_ctl', '-w', '-D', $node->data_dir, '-l', $node->logfile,'start'],'restart with correct shared_preload_libraries succeeded');
+
+# Test that we can connect again
+test_role($node, 'bob', 'custom', 0, log_like => [qr/connection authorized: user=bob/]);
+
+done_testing();
--
2.25.1
[text/x-patch] v4-0004-Add-support-for-map-and-custom-auth-options.patch (11.7K, ../../[email protected]/5-v4-0004-Add-support-for-map-and-custom-auth-options.patch)
download | inline diff:
From c30970a354b23f26eaf3e1db7c7d7759f2f828b3 Mon Sep 17 00:00:00 2001
From: Samay Sharma <[email protected]>
Date: Mon, 14 Mar 2022 14:54:08 -0700
Subject: [PATCH v4 04/10] Add support for "map" and custom auth options
This commit allows extensions to now specify, validate and use
custom options for their custom auth methods. This is done by
exposing a validation function hook which can be defined by
extensions. The valid options are then stored as key / value
pairs which can be used while checking authentication. We also
allow custom auth providers to use the "map" option to use
usermaps.
The test module was updated to use custom options and new tests
were added.
---
src/backend/libpq/auth.c | 4 +-
src/backend/libpq/hba.c | 76 +++++++++++++++----
src/include/libpq/auth.h | 17 +++--
src/include/libpq/hba.h | 8 ++
.../test_auth_provider/t/001_custom_auth.pl | 22 ++++++
.../test_auth_provider/test_auth_provider.c | 50 +++++++++++-
6 files changed, 157 insertions(+), 20 deletions(-)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 375ee33892..4a8a63922a 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -3364,7 +3364,8 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por
*/
void RegisterAuthProvider(const char *provider_name,
CustomAuthenticationCheck_hook_type AuthenticationCheckFunction,
- CustomAuthenticationError_hook_type AuthenticationErrorFunction)
+ CustomAuthenticationError_hook_type AuthenticationErrorFunction,
+ CustomAuthenticationValidateOptions_hook_type AuthenticationOptionsFunction)
{
CustomAuthProvider *provider = NULL;
MemoryContext old_context;
@@ -3390,6 +3391,7 @@ void RegisterAuthProvider(const char *provider_name,
provider->name = MemoryContextStrdup(TopMemoryContext,provider_name);
provider->auth_check_hook = AuthenticationCheckFunction;
provider->auth_error_hook = AuthenticationErrorFunction;
+ provider->auth_options_hook = AuthenticationOptionsFunction;
custom_auth_providers = lappend(custom_auth_providers, provider);
MemoryContextSwitchTo(old_context);
}
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 9f15252789..42cb1ce51d 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1729,8 +1729,9 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
hbaline->auth_method != uaPeer &&
hbaline->auth_method != uaGSS &&
hbaline->auth_method != uaSSPI &&
- hbaline->auth_method != uaCert)
- INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, gssapi, sspi, and cert"));
+ hbaline->auth_method != uaCert &&
+ hbaline->auth_method != uaCustom)
+ INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, gssapi, sspi, cert and custom"));
hbaline->usermap = pstrdup(val);
}
else if (strcmp(name, "clientcert") == 0)
@@ -2121,7 +2122,6 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
/*
* Verify that the provider mentioned is loaded via shared_preload_libraries.
*/
-
if (get_provider_by_name(val) == NULL)
{
ereport(elevel,
@@ -2129,7 +2129,7 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
errmsg("cannot use authentication provider %s",val),
errhint("Load authentication provider via shared_preload_libraries."),
errcontext("line %d of configuration file \"%s\"",
- line_num, HbaFileName)));
+ line_num, HbaFileName)));
*err_msg = psprintf("cannot use authentication provider %s", val);
return false;
@@ -2141,15 +2141,55 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
}
else
{
- ereport(elevel,
- (errcode(ERRCODE_CONFIG_FILE_ERROR),
- errmsg("unrecognized authentication option name: \"%s\"",
- name),
- errcontext("line %d of configuration file \"%s\"",
- line_num, HbaFileName)));
- *err_msg = psprintf("unrecognized authentication option name: \"%s\"",
- name);
- return false;
+ /*
+ * Allow custom providers to validate their options if they have an
+ * option validation function defined.
+ */
+ if (hbaline->auth_method == uaCustom && (hbaline->custom_provider != NULL))
+ {
+ bool valid_option = false;
+ CustomAuthProvider *provider = get_provider_by_name(hbaline->custom_provider);
+ if (provider->auth_options_hook)
+ {
+ valid_option = provider->auth_options_hook(name, val, hbaline, err_msg);
+ if (valid_option)
+ {
+ CustomOption *option = palloc(sizeof(CustomOption));
+ option->name = pstrdup(name);
+ option->value = pstrdup(val);
+ hbaline->custom_auth_options = lappend(hbaline->custom_auth_options,
+ option);
+ }
+ }
+ else
+ {
+ *err_msg = psprintf("unrecognized authentication option name: \"%s\"",
+ name);
+ }
+
+ /* Report the error returned by the provider as it is */
+ if (!valid_option)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("%s", *err_msg),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, HbaFileName)));
+ return false;
+ }
+ }
+ else
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("unrecognized authentication option name: \"%s\"",
+ name),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, HbaFileName)));
+ *err_msg = psprintf("unrecognized authentication option name: \"%s\"",
+ name);
+ return false;
+ }
}
return true;
}
@@ -2484,6 +2524,16 @@ gethba_options(HbaLine *hba)
if (hba->custom_provider)
options[noptions++] =
CStringGetTextDatum(psprintf("provider=%s",hba->custom_provider));
+ if (hba->custom_auth_options)
+ {
+ ListCell *lc;
+ foreach(lc, hba->custom_auth_options)
+ {
+ CustomOption *option = (CustomOption *)lfirst(lc);
+ options[noptions++] =
+ CStringGetTextDatum(psprintf("%s=%s",option->name, option->value));
+ }
+ }
}
/* If you add more options, consider increasing MAX_HBA_OPTIONS. */
diff --git a/src/include/libpq/auth.h b/src/include/libpq/auth.h
index 7aff98d919..cbdc63b4df 100644
--- a/src/include/libpq/auth.h
+++ b/src/include/libpq/auth.h
@@ -31,22 +31,29 @@ typedef int (*CustomAuthenticationCheck_hook_type) (Port *);
typedef void (*ClientAuthentication_hook_type) (Port *, int);
extern PGDLLIMPORT ClientAuthentication_hook_type ClientAuthentication_hook;
+/* Declarations for custom authentication providers */
+
/* Hook for plugins to report error messages in auth_failed() */
typedef const char * (*CustomAuthenticationError_hook_type) (Port *);
-extern void RegisterAuthProvider
- (const char *provider_name,
- CustomAuthenticationCheck_hook_type CustomAuthenticationCheck_hook,
- CustomAuthenticationError_hook_type CustomAuthenticationError_hook);
+/* Hook for plugins to validate custom authentication options */
+typedef bool (*CustomAuthenticationValidateOptions_hook_type)
+ (char *, char *, HbaLine *, char **);
-/* Declarations for custom authentication providers */
typedef struct CustomAuthProvider
{
const char *name;
CustomAuthenticationCheck_hook_type auth_check_hook;
CustomAuthenticationError_hook_type auth_error_hook;
+ CustomAuthenticationValidateOptions_hook_type auth_options_hook;
} CustomAuthProvider;
+extern void RegisterAuthProvider
+ (const char *provider_name,
+ CustomAuthenticationCheck_hook_type CustomAuthenticationCheck_hook,
+ CustomAuthenticationError_hook_type CustomAuthenticationError_hook,
+ CustomAuthenticationValidateOptions_hook_type CustomAuthenticationOptions_hook);
+
extern CustomAuthProvider *get_provider_by_name(const char *name);
/*
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 48490c44ed..31a00c4b71 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -78,6 +78,13 @@ typedef enum ClientCertName
clientCertDN
} ClientCertName;
+/* Struct for custom options defined by custom auth plugins */
+typedef struct CustomOption
+{
+ char *name;
+ char *value;
+}CustomOption;
+
typedef struct HbaLine
{
int linenumber;
@@ -122,6 +129,7 @@ typedef struct HbaLine
List *radiusports;
char *radiusports_s;
char *custom_provider;
+ List *custom_auth_options;
} HbaLine;
typedef struct IdentLine
diff --git a/src/test/modules/test_auth_provider/t/001_custom_auth.pl b/src/test/modules/test_auth_provider/t/001_custom_auth.pl
index 3b7472dc7f..e964c2f723 100644
--- a/src/test/modules/test_auth_provider/t/001_custom_auth.pl
+++ b/src/test/modules/test_auth_provider/t/001_custom_auth.pl
@@ -109,6 +109,28 @@ test_hba_reload($node, 'custom', 1);
# Test that correct provider name allows reload to succeed.
test_hba_reload($node, 'custom provider=test', 0);
+# Tests for custom auth options
+
+# Test that a custom option doesn't work without a provider.
+test_hba_reload($node, 'custom allow=bob', 1);
+
+# Test that options other than allowed ones are not accepted.
+test_hba_reload($node, 'custom provider=test wrong=true', 1);
+
+# Test that only valid values are accepted for allowed options.
+test_hba_reload($node, 'custom provider=test allow=wrong', 1);
+
+# Test that setting allow option for a user doesn't look at the password.
+test_hba_reload($node, 'custom provider=test allow=bob', 0);
+$ENV{"PGPASSWORD"} = 'bad123';
+test_role($node, 'bob', 'custom', 0, log_like => [qr/connection authorized: user=bob/]);
+
+# Password is still checked for other users.
+test_role($node, 'alice', 'custom', 2, log_unlike => [qr/connection authorized:/]);
+
+# Reset the password for future tests.
+$ENV{"PGPASSWORD"} = 'bob123';
+
# Custom auth modules require mentioning extension in shared_preload_libraries.
# Remove extension from shared_preload_libraries and try to restart.
diff --git a/src/test/modules/test_auth_provider/test_auth_provider.c b/src/test/modules/test_auth_provider/test_auth_provider.c
index 7c4b1f3500..5ac425f5b6 100644
--- a/src/test/modules/test_auth_provider/test_auth_provider.c
+++ b/src/test/modules/test_auth_provider/test_auth_provider.c
@@ -39,7 +39,27 @@ static int TestAuthenticationCheck(Port *port)
int result = STATUS_ERROR;
char *real_pass;
const char *logdetail = NULL;
+ ListCell *lc;
+ /*
+ * If user's name is in the the "allow" list, do not request password
+ * for them and allow them to authenticate.
+ */
+ foreach(lc,port->hba->custom_auth_options)
+ {
+ CustomOption *option = (CustomOption *) lfirst(lc);
+ if (strcmp(option->name, "allow") == 0 &&
+ strcmp(option->value, port->user_name) == 0)
+ {
+ set_authn_id(port, port->user_name);
+ return STATUS_OK;
+ }
+ }
+
+ /*
+ * Encrypt the password and validate that it's the same as the one
+ * returned by the client.
+ */
real_pass = get_encrypted_password_for_user(port->user_name);
if (real_pass)
{
@@ -79,8 +99,36 @@ static const char *TestAuthenticationError(Port *port)
return error_message;
}
+/*
+ * Returns if the options passed are supported by the extension
+ * and are valid. Currently only "allow" is supported.
+ */
+static bool TestAuthenticationOptions(char *name, char *val, HbaLine *hbaline, char **err_msg)
+{
+ /* Validate that an actual user is in the "allow" list. */
+ if (strcmp(name,"allow") == 0)
+ {
+ for (int i=0;i<3;i++)
+ {
+ if (strcmp(val,credentials[0][i]) == 0)
+ {
+ return true;
+ }
+ }
+
+ *err_msg = psprintf("\"%s\" is not valid value for option \"%s\"", val, name);
+ return false;
+ }
+ else
+ {
+ *err_msg = psprintf("option \"%s\" not recognized by \"%s\" provider", val, hbaline->custom_provider);
+ return false;
+ }
+}
+
void
_PG_init(void)
{
- RegisterAuthProvider("test", TestAuthenticationCheck, TestAuthenticationError);
+ RegisterAuthProvider("test", TestAuthenticationCheck,
+ TestAuthenticationError,TestAuthenticationOptions);
}
--
2.25.1
[text/x-patch] v4-0005-common-jsonapi-support-FRONTEND-clients.patch (20.4K, ../../[email protected]/6-v4-0005-common-jsonapi-support-FRONTEND-clients.patch)
download | inline diff:
From 0ca324b52c94760e799974e5661fe29c3912d2d8 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Mon, 3 May 2021 15:38:26 -0700
Subject: [PATCH v4 05/10] common/jsonapi: support FRONTEND clients
Based on a patch by Michael Paquier.
For frontend code, use PQExpBuffer instead of StringInfo. This requires
us to track allocation failures so that we can return JSON_OUT_OF_MEMORY
as needed. json_errdetail() now allocates its error message inside
memory owned by the JsonLexContext, so clients don't need to worry about
freeing it.
For convenience, the backend now has destroyJsonLexContext() to mirror
other create/destroy APIs. The frontend has init/term versions of the
API to handle stack-allocated JsonLexContexts.
We can now partially revert b44669b2ca, now that json_errdetail() works
correctly.
---
src/backend/utils/adt/jsonfuncs.c | 4 +-
src/bin/pg_verifybackup/parse_manifest.c | 13 +-
src/bin/pg_verifybackup/t/005_bad_manifest.pl | 2 +-
src/common/Makefile | 2 +-
src/common/jsonapi.c | 290 +++++++++++++-----
src/include/common/jsonapi.h | 47 ++-
6 files changed, 270 insertions(+), 88 deletions(-)
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 29664aa6e4..7d32a99d8c 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -723,9 +723,7 @@ json_object_keys(PG_FUNCTION_ARGS)
pg_parse_json_or_ereport(lex, sem);
/* keys are now in state->result */
- pfree(lex->strval->data);
- pfree(lex->strval);
- pfree(lex);
+ destroyJsonLexContext(lex);
pfree(sem);
MemoryContextSwitchTo(oldcontext);
diff --git a/src/bin/pg_verifybackup/parse_manifest.c b/src/bin/pg_verifybackup/parse_manifest.c
index 6364b01282..4b38fd3963 100644
--- a/src/bin/pg_verifybackup/parse_manifest.c
+++ b/src/bin/pg_verifybackup/parse_manifest.c
@@ -119,7 +119,7 @@ void
json_parse_manifest(JsonManifestParseContext *context, char *buffer,
size_t size)
{
- JsonLexContext *lex;
+ JsonLexContext lex = {0};
JsonParseErrorType json_error;
JsonSemAction sem;
JsonManifestParseState parse;
@@ -129,8 +129,8 @@ json_parse_manifest(JsonManifestParseContext *context, char *buffer,
parse.state = JM_EXPECT_TOPLEVEL_START;
parse.saw_version_field = false;
- /* Create a JSON lexing context. */
- lex = makeJsonLexContextCstringLen(buffer, size, PG_UTF8, true);
+ /* Initialize a JSON lexing context. */
+ initJsonLexContextCstringLen(&lex, buffer, size, PG_UTF8, true);
/* Set up semantic actions. */
sem.semstate = &parse;
@@ -145,14 +145,17 @@ json_parse_manifest(JsonManifestParseContext *context, char *buffer,
sem.scalar = json_manifest_scalar;
/* Run the actual JSON parser. */
- json_error = pg_parse_json(lex, &sem);
+ json_error = pg_parse_json(&lex, &sem);
if (json_error != JSON_SUCCESS)
- json_manifest_parse_failure(context, "parsing failed");
+ json_manifest_parse_failure(context, json_errdetail(json_error, &lex));
if (parse.state != JM_EXPECT_EOF)
json_manifest_parse_failure(context, "manifest ended unexpectedly");
/* Verify the manifest checksum. */
verify_manifest_checksum(&parse, buffer, size);
+
+ /* Clean up. */
+ termJsonLexContext(&lex);
}
/*
diff --git a/src/bin/pg_verifybackup/t/005_bad_manifest.pl b/src/bin/pg_verifybackup/t/005_bad_manifest.pl
index 118beb53d7..f2692972fe 100644
--- a/src/bin/pg_verifybackup/t/005_bad_manifest.pl
+++ b/src/bin/pg_verifybackup/t/005_bad_manifest.pl
@@ -16,7 +16,7 @@ my $tempdir = PostgreSQL::Test::Utils::tempdir;
test_bad_manifest(
'input string ended unexpectedly',
- qr/could not parse backup manifest: parsing failed/,
+ qr/could not parse backup manifest: The input string ended unexpectedly/,
<<EOM);
{
EOM
diff --git a/src/common/Makefile b/src/common/Makefile
index f627349835..694da03658 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -40,7 +40,7 @@ override CPPFLAGS += -DVAL_LDFLAGS_EX="\"$(LDFLAGS_EX)\""
override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
-override CPPFLAGS := -DFRONTEND -I. -I$(top_srcdir)/src/common $(CPPFLAGS)
+override CPPFLAGS := -DFRONTEND -I. -I$(top_srcdir)/src/common -I$(libpq_srcdir) $(CPPFLAGS)
LIBS += $(PTHREAD_LIBS)
# If you add objects here, see also src/tools/msvc/Mkvcbuild.pm
diff --git a/src/common/jsonapi.c b/src/common/jsonapi.c
index 6666077a93..7fc5eaf460 100644
--- a/src/common/jsonapi.c
+++ b/src/common/jsonapi.c
@@ -20,10 +20,39 @@
#include "common/jsonapi.h"
#include "mb/pg_wchar.h"
-#ifndef FRONTEND
+#ifdef FRONTEND
+#include "pqexpbuffer.h"
+#else
+#include "lib/stringinfo.h"
#include "miscadmin.h"
#endif
+/*
+ * In backend, we will use palloc/pfree along with StringInfo. In frontend, use
+ * malloc and PQExpBuffer, and return JSON_OUT_OF_MEMORY on out-of-memory.
+ */
+#ifdef FRONTEND
+
+#define STRDUP(s) strdup(s)
+#define ALLOC(size) malloc(size)
+
+#define appendStrVal appendPQExpBuffer
+#define appendStrValChar appendPQExpBufferChar
+#define createStrVal createPQExpBuffer
+#define resetStrVal resetPQExpBuffer
+
+#else /* !FRONTEND */
+
+#define STRDUP(s) pstrdup(s)
+#define ALLOC(size) palloc(size)
+
+#define appendStrVal appendStringInfo
+#define appendStrValChar appendStringInfoChar
+#define createStrVal makeStringInfo
+#define resetStrVal resetStringInfo
+
+#endif
+
/*
* The context of the parser is maintained by the recursive descent
* mechanism, but is passed explicitly to the error reporting routine
@@ -132,10 +161,12 @@ IsValidJsonNumber(const char *str, int len)
return (!numeric_error) && (total_len == dummy_lex.input_length);
}
+#ifndef FRONTEND
+
/*
* makeJsonLexContextCstringLen
*
- * lex constructor, with or without StringInfo object for de-escaped lexemes.
+ * lex constructor, with or without a string object for de-escaped lexemes.
*
* Without is better as it makes the processing faster, so only make one
* if really required.
@@ -145,13 +176,66 @@ makeJsonLexContextCstringLen(char *json, int len, int encoding, bool need_escape
{
JsonLexContext *lex = palloc0(sizeof(JsonLexContext));
+ initJsonLexContextCstringLen(lex, json, len, encoding, need_escapes);
+
+ return lex;
+}
+
+void
+destroyJsonLexContext(JsonLexContext *lex)
+{
+ termJsonLexContext(lex);
+ pfree(lex);
+}
+
+#endif /* !FRONTEND */
+
+void
+initJsonLexContextCstringLen(JsonLexContext *lex, char *json, int len, int encoding, bool need_escapes)
+{
lex->input = lex->token_terminator = lex->line_start = json;
lex->line_number = 1;
lex->input_length = len;
lex->input_encoding = encoding;
- if (need_escapes)
- lex->strval = makeStringInfo();
- return lex;
+ lex->parse_strval = need_escapes;
+ if (lex->parse_strval)
+ {
+ /*
+ * This call can fail in FRONTEND code. We defer error handling to time
+ * of use (json_lex_string()) since there's no way to signal failure
+ * here, and we might not need to parse any strings anyway.
+ */
+ lex->strval = createStrVal();
+ }
+ lex->errormsg = NULL;
+}
+
+void
+termJsonLexContext(JsonLexContext *lex)
+{
+ static const JsonLexContext empty = {0};
+
+ if (lex->strval)
+ {
+#ifdef FRONTEND
+ destroyPQExpBuffer(lex->strval);
+#else
+ pfree(lex->strval->data);
+ pfree(lex->strval);
+#endif
+ }
+
+ if (lex->errormsg)
+ {
+#ifdef FRONTEND
+ destroyPQExpBuffer(lex->errormsg);
+#else
+ pfree(lex->errormsg->data);
+ pfree(lex->errormsg);
+#endif
+ }
+
+ *lex = empty;
}
/*
@@ -217,7 +301,7 @@ json_count_array_elements(JsonLexContext *lex, int *elements)
* etc, so doing this with a copy makes that safe.
*/
memcpy(©lex, lex, sizeof(JsonLexContext));
- copylex.strval = NULL; /* not interested in values here */
+ copylex.parse_strval = false; /* not interested in values here */
copylex.lex_level++;
count = 0;
@@ -279,14 +363,21 @@ parse_scalar(JsonLexContext *lex, JsonSemAction *sem)
/* extract the de-escaped string value, or the raw lexeme */
if (lex_peek(lex) == JSON_TOKEN_STRING)
{
- if (lex->strval != NULL)
- val = pstrdup(lex->strval->data);
+ if (lex->parse_strval)
+ {
+ val = STRDUP(lex->strval->data);
+ if (val == NULL)
+ return JSON_OUT_OF_MEMORY;
+ }
}
else
{
int len = (lex->token_terminator - lex->token_start);
- val = palloc(len + 1);
+ val = ALLOC(len + 1);
+ if (val == NULL)
+ return JSON_OUT_OF_MEMORY;
+
memcpy(val, lex->token_start, len);
val[len] = '\0';
}
@@ -320,8 +411,12 @@ parse_object_field(JsonLexContext *lex, JsonSemAction *sem)
if (lex_peek(lex) != JSON_TOKEN_STRING)
return report_parse_error(JSON_PARSE_STRING, lex);
- if ((ostart != NULL || oend != NULL) && lex->strval != NULL)
- fname = pstrdup(lex->strval->data);
+ if ((ostart != NULL || oend != NULL) && lex->parse_strval)
+ {
+ fname = STRDUP(lex->strval->data);
+ if (fname == NULL)
+ return JSON_OUT_OF_MEMORY;
+ }
result = json_lex(lex);
if (result != JSON_SUCCESS)
return result;
@@ -368,6 +463,10 @@ parse_object(JsonLexContext *lex, JsonSemAction *sem)
JsonParseErrorType result;
#ifndef FRONTEND
+ /*
+ * TODO: clients need some way to put a bound on stack growth. Parse level
+ * limits maybe?
+ */
check_stack_depth();
#endif
@@ -676,8 +775,15 @@ json_lex_string(JsonLexContext *lex)
int len;
int hi_surrogate = -1;
- if (lex->strval != NULL)
- resetStringInfo(lex->strval);
+ if (lex->parse_strval)
+ {
+#ifdef FRONTEND
+ /* make sure initialization succeeded */
+ if (lex->strval == NULL)
+ return JSON_OUT_OF_MEMORY;
+#endif
+ resetStrVal(lex->strval);
+ }
Assert(lex->input_length > 0);
s = lex->token_start;
@@ -737,7 +843,7 @@ json_lex_string(JsonLexContext *lex)
return JSON_UNICODE_ESCAPE_FORMAT;
}
}
- if (lex->strval != NULL)
+ if (lex->parse_strval)
{
/*
* Combine surrogate pairs.
@@ -797,19 +903,19 @@ json_lex_string(JsonLexContext *lex)
unicode_to_utf8(ch, (unsigned char *) utf8str);
utf8len = pg_utf_mblen((unsigned char *) utf8str);
- appendBinaryStringInfo(lex->strval, utf8str, utf8len);
+ appendBinaryPQExpBuffer(lex->strval, utf8str, utf8len);
}
else if (ch <= 0x007f)
{
/* The ASCII range is the same in all encodings */
- appendStringInfoChar(lex->strval, (char) ch);
+ appendPQExpBufferChar(lex->strval, (char) ch);
}
else
return JSON_UNICODE_HIGH_ESCAPE;
#endif /* FRONTEND */
}
}
- else if (lex->strval != NULL)
+ else if (lex->parse_strval)
{
if (hi_surrogate != -1)
return JSON_UNICODE_LOW_SURROGATE;
@@ -819,22 +925,22 @@ json_lex_string(JsonLexContext *lex)
case '"':
case '\\':
case '/':
- appendStringInfoChar(lex->strval, *s);
+ appendStrValChar(lex->strval, *s);
break;
case 'b':
- appendStringInfoChar(lex->strval, '\b');
+ appendStrValChar(lex->strval, '\b');
break;
case 'f':
- appendStringInfoChar(lex->strval, '\f');
+ appendStrValChar(lex->strval, '\f');
break;
case 'n':
- appendStringInfoChar(lex->strval, '\n');
+ appendStrValChar(lex->strval, '\n');
break;
case 'r':
- appendStringInfoChar(lex->strval, '\r');
+ appendStrValChar(lex->strval, '\r');
break;
case 't':
- appendStringInfoChar(lex->strval, '\t');
+ appendStrValChar(lex->strval, '\t');
break;
default:
/* Not a valid string escape, so signal error. */
@@ -858,12 +964,12 @@ json_lex_string(JsonLexContext *lex)
}
}
- else if (lex->strval != NULL)
+ else if (lex->parse_strval)
{
if (hi_surrogate != -1)
return JSON_UNICODE_LOW_SURROGATE;
- appendStringInfoChar(lex->strval, *s);
+ appendStrValChar(lex->strval, *s);
}
}
@@ -871,6 +977,11 @@ json_lex_string(JsonLexContext *lex)
if (hi_surrogate != -1)
return JSON_UNICODE_LOW_SURROGATE;
+#ifdef FRONTEND
+ if (lex->parse_strval && PQExpBufferBroken(lex->strval))
+ return JSON_OUT_OF_MEMORY;
+#endif
+
/* Hooray, we found the end of the string! */
lex->prev_token_terminator = lex->token_terminator;
lex->token_terminator = s + 1;
@@ -1043,72 +1154,93 @@ report_parse_error(JsonParseContext ctx, JsonLexContext *lex)
return JSON_SUCCESS; /* silence stupider compilers */
}
-
-#ifndef FRONTEND
-/*
- * Extract the current token from a lexing context, for error reporting.
- */
-static char *
-extract_token(JsonLexContext *lex)
-{
- int toklen = lex->token_terminator - lex->token_start;
- char *token = palloc(toklen + 1);
-
- memcpy(token, lex->token_start, toklen);
- token[toklen] = '\0';
- return token;
-}
-
/*
* Construct a detail message for a JSON error.
*
- * Note that the error message generated by this routine may not be
- * palloc'd, making it unsafe for frontend code as there is no way to
- * know if this can be safery pfree'd or not.
+ * The returned allocation is either static or owned by the JsonLexContext and
+ * should not be freed.
*/
char *
json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
{
+ int toklen = lex->token_terminator - lex->token_start;
+
+ if (error == JSON_OUT_OF_MEMORY)
+ {
+ /* Short circuit. Allocating anything for this case is unhelpful. */
+ return _("out of memory");
+ }
+
+ if (lex->errormsg)
+ resetStrVal(lex->errormsg);
+ else
+ lex->errormsg = createStrVal();
+
switch (error)
{
case JSON_SUCCESS:
/* fall through to the error code after switch */
break;
case JSON_ESCAPING_INVALID:
- return psprintf(_("Escape sequence \"\\%s\" is invalid."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Escape sequence \"\\%.*s\" is invalid."),
+ toklen, lex->token_start);
+ break;
case JSON_ESCAPING_REQUIRED:
- return psprintf(_("Character with value 0x%02x must be escaped."),
- (unsigned char) *(lex->token_terminator));
+ appendStrVal(lex->errormsg,
+ _("Character with value 0x%02x must be escaped."),
+ (unsigned char) *(lex->token_terminator));
+ break;
case JSON_EXPECTED_END:
- return psprintf(_("Expected end of input, but found \"%s\"."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Expected end of input, but found \"%.*s\"."),
+ toklen, lex->token_start);
+ break;
case JSON_EXPECTED_ARRAY_FIRST:
- return psprintf(_("Expected array element or \"]\", but found \"%s\"."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Expected array element or \"]\", but found \"%.*s\"."),
+ toklen, lex->token_start);
+ break;
case JSON_EXPECTED_ARRAY_NEXT:
- return psprintf(_("Expected \",\" or \"]\", but found \"%s\"."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Expected \",\" or \"]\", but found \"%.*s\"."),
+ toklen, lex->token_start);
+ break;
case JSON_EXPECTED_COLON:
- return psprintf(_("Expected \":\", but found \"%s\"."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Expected \":\", but found \"%.*s\"."),
+ toklen, lex->token_start);
+ break;
case JSON_EXPECTED_JSON:
- return psprintf(_("Expected JSON value, but found \"%s\"."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Expected JSON value, but found \"%.*s\"."),
+ toklen, lex->token_start);
+ break;
case JSON_EXPECTED_MORE:
return _("The input string ended unexpectedly.");
case JSON_EXPECTED_OBJECT_FIRST:
- return psprintf(_("Expected string or \"}\", but found \"%s\"."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Expected string or \"}\", but found \"%.*s\"."),
+ toklen, lex->token_start);
+ break;
case JSON_EXPECTED_OBJECT_NEXT:
- return psprintf(_("Expected \",\" or \"}\", but found \"%s\"."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Expected \",\" or \"}\", but found \"%.*s\"."),
+ toklen, lex->token_start);
+ break;
case JSON_EXPECTED_STRING:
- return psprintf(_("Expected string, but found \"%s\"."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Expected string, but found \"%.*s\"."),
+ toklen, lex->token_start);
+ break;
case JSON_INVALID_TOKEN:
- return psprintf(_("Token \"%s\" is invalid."),
- extract_token(lex));
+ appendStrVal(lex->errormsg,
+ _("Token \"%.*s\" is invalid."),
+ toklen, lex->token_start);
+ break;
+ case JSON_OUT_OF_MEMORY:
+ /* should have been handled above; use the error path */
+ break;
case JSON_UNICODE_CODE_POINT_ZERO:
return _("\\u0000 cannot be converted to text.");
case JSON_UNICODE_ESCAPE_FORMAT:
@@ -1122,12 +1254,22 @@ json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
return _("Unicode low surrogate must follow a high surrogate.");
}
- /*
- * We don't use a default: case, so that the compiler will warn about
- * unhandled enum values. But this needs to be here anyway to cover the
- * possibility of an incorrect input.
- */
- elog(ERROR, "unexpected json parse error type: %d", (int) error);
- return NULL;
-}
+ /* Note that lex->errormsg can be NULL in FRONTEND code. */
+ if (lex->errormsg && !lex->errormsg->data[0])
+ {
+ /*
+ * We don't use a default: case, so that the compiler will warn about
+ * unhandled enum values. But this needs to be here anyway to cover the
+ * possibility of an incorrect input.
+ */
+ appendStrVal(lex->errormsg,
+ "unexpected json parse error type: %d", (int) error);
+ }
+
+#ifdef FRONTEND
+ if (PQExpBufferBroken(lex->errormsg))
+ return _("out of memory while constructing error description");
#endif
+
+ return lex->errormsg->data;
+}
diff --git a/src/include/common/jsonapi.h b/src/include/common/jsonapi.h
index 52cb4a9339..d7cafc84fe 100644
--- a/src/include/common/jsonapi.h
+++ b/src/include/common/jsonapi.h
@@ -14,8 +14,6 @@
#ifndef JSONAPI_H
#define JSONAPI_H
-#include "lib/stringinfo.h"
-
typedef enum
{
JSON_TOKEN_INVALID,
@@ -48,6 +46,7 @@ typedef enum
JSON_EXPECTED_OBJECT_NEXT,
JSON_EXPECTED_STRING,
JSON_INVALID_TOKEN,
+ JSON_OUT_OF_MEMORY,
JSON_UNICODE_CODE_POINT_ZERO,
JSON_UNICODE_ESCAPE_FORMAT,
JSON_UNICODE_HIGH_ESCAPE,
@@ -55,6 +54,17 @@ typedef enum
JSON_UNICODE_LOW_SURROGATE
} JsonParseErrorType;
+/*
+ * Don't depend on the internal type header for strval; if callers need access
+ * then they can include the appropriate header themselves.
+ */
+#ifdef FRONTEND
+#define StrValType PQExpBufferData
+#else
+#define StrValType StringInfoData
+#endif
+
+typedef struct StrValType StrValType;
/*
* All the fields in this structure should be treated as read-only.
@@ -81,7 +91,9 @@ typedef struct JsonLexContext
int lex_level;
int line_number; /* line number, starting from 1 */
char *line_start; /* where that line starts within input */
- StringInfo strval;
+ bool parse_strval;
+ StrValType *strval; /* only used if parse_strval == true */
+ StrValType *errormsg;
} JsonLexContext;
typedef void (*json_struct_action) (void *state);
@@ -141,9 +153,10 @@ extern JsonSemAction nullSemAction;
*/
extern JsonParseErrorType json_count_array_elements(JsonLexContext *lex,
int *elements);
+#ifndef FRONTEND
/*
- * constructor for JsonLexContext, with or without strval element.
+ * allocating constructor for JsonLexContext, with or without strval element.
* If supplied, the strval element will contain a de-escaped version of
* the lexeme. However, doing this imposes a performance penalty, so
* it should be avoided if the de-escaped lexeme is not required.
@@ -153,6 +166,32 @@ extern JsonLexContext *makeJsonLexContextCstringLen(char *json,
int encoding,
bool need_escapes);
+/*
+ * Counterpart to makeJsonLexContextCstringLen(): clears and deallocates lex.
+ * The context pointer should not be used after this call.
+ */
+extern void destroyJsonLexContext(JsonLexContext *lex);
+
+#endif /* !FRONTEND */
+
+/*
+ * stack constructor for JsonLexContext, with or without strval element.
+ * If supplied, the strval element will contain a de-escaped version of
+ * the lexeme. However, doing this imposes a performance penalty, so
+ * it should be avoided if the de-escaped lexeme is not required.
+ */
+extern void initJsonLexContextCstringLen(JsonLexContext *lex,
+ char *json,
+ int len,
+ int encoding,
+ bool need_escapes);
+
+/*
+ * Counterpart to initJsonLexContextCstringLen(): clears the contents of lex,
+ * but does not deallocate lex itself.
+ */
+extern void termJsonLexContext(JsonLexContext *lex);
+
/* lex one token */
extern JsonParseErrorType json_lex(JsonLexContext *lex);
--
2.25.1
[text/x-patch] v4-0006-libpq-add-OAUTHBEARER-SASL-mechanism.patch (35.6K, ../../[email protected]/7-v4-0006-libpq-add-OAUTHBEARER-SASL-mechanism.patch)
download | inline diff:
From f4752b6daea9b519ff3482094a1f445a4731cc15 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Tue, 13 Apr 2021 10:27:27 -0700
Subject: [PATCH v4 06/10] libpq: add OAUTHBEARER SASL mechanism
DO NOT USE THIS PROOF OF CONCEPT IN PRODUCTION.
Implement OAUTHBEARER (RFC 7628) and OAuth 2.0 Device Authorization
Grants (RFC 8628) on the client side. When speaking to a OAuth-enabled
server, it looks a bit like this:
$ psql 'host=example.org oauth_client_id=f02c6361-0635-...'
Visit https://oauth.example.org/login and enter the code: FPQ2-M4BG
The OAuth issuer must support device authorization. No other OAuth flows
are currently implemented.
The client implementation requires libiddawc and its development
headers. Configure --with-oauth (and --with-includes/--with-libraries to
point at the iddawc installation, if it's in a custom location).
Several TODOs:
- don't retry forever if the server won't accept our token
- perform several sanity checks on the OAuth issuer's responses
- handle cases where the client has been set up with an issuer and
scope, but the Postgres server wants to use something different
- improve error debuggability during the OAuth handshake
- ...and more.
---
configure | 100 ++++
configure.ac | 19 +
src/Makefile.global.in | 1 +
src/include/common/oauth-common.h | 19 +
src/include/pg_config.h.in | 6 +
src/interfaces/libpq/Makefile | 7 +-
src/interfaces/libpq/fe-auth-oauth.c | 744 +++++++++++++++++++++++++++
src/interfaces/libpq/fe-auth-sasl.h | 5 +-
src/interfaces/libpq/fe-auth-scram.c | 6 +-
src/interfaces/libpq/fe-auth.c | 42 +-
src/interfaces/libpq/fe-auth.h | 3 +
src/interfaces/libpq/fe-connect.c | 38 ++
src/interfaces/libpq/libpq-int.h | 8 +
13 files changed, 979 insertions(+), 19 deletions(-)
create mode 100644 src/include/common/oauth-common.h
create mode 100644 src/interfaces/libpq/fe-auth-oauth.c
diff --git a/configure b/configure
index e066cbe2c8..42a3304681 100755
--- a/configure
+++ b/configure
@@ -718,6 +718,7 @@ with_uuid
with_readline
with_systemd
with_selinux
+with_oauth
with_ldap
with_krb_srvnam
krb_srvtab
@@ -861,6 +862,7 @@ with_krb_srvnam
with_pam
with_bsd_auth
with_ldap
+with_oauth
with_bonjour
with_selinux
with_systemd
@@ -1570,6 +1572,7 @@ Optional Packages:
--with-pam build with PAM support
--with-bsd-auth build with BSD Authentication support
--with-ldap build with LDAP support
+ --with-oauth build with OAuth 2.0 support
--with-bonjour build with Bonjour support
--with-selinux build with SELinux support
--with-systemd build with systemd support
@@ -8377,6 +8380,42 @@ $as_echo "$with_ldap" >&6; }
+#
+# OAuth 2.0
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with OAuth support" >&5
+$as_echo_n "checking whether to build with OAuth support... " >&6; }
+
+
+
+# Check whether --with-oauth was given.
+if test "${with_oauth+set}" = set; then :
+ withval=$with_oauth;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_OAUTH 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-oauth option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_oauth=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_oauth" >&5
+$as_echo "$with_oauth" >&6; }
+
+
+
#
# Bonjour
#
@@ -13503,6 +13542,56 @@ fi
+if test "$with_oauth" = yes ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for i_init_session in -liddawc" >&5
+$as_echo_n "checking for i_init_session in -liddawc... " >&6; }
+if ${ac_cv_lib_iddawc_i_init_session+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-liddawc $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char i_init_session ();
+int
+main ()
+{
+return i_init_session ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_iddawc_i_init_session=yes
+else
+ ac_cv_lib_iddawc_i_init_session=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_iddawc_i_init_session" >&5
+$as_echo "$ac_cv_lib_iddawc_i_init_session" >&6; }
+if test "x$ac_cv_lib_iddawc_i_init_session" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBIDDAWC 1
+_ACEOF
+
+ LIBS="-liddawc $LIBS"
+
+else
+ as_fn_error $? "library 'iddawc' is required for OAuth support" "$LINENO" 5
+fi
+
+fi
+
# for contrib/sepgsql
if test "$with_selinux" = yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for security_compute_create_name in -lselinux" >&5
@@ -14516,6 +14605,17 @@ fi
done
+fi
+
+if test "$with_oauth" != no; then
+ ac_fn_c_check_header_mongrel "$LINENO" "iddawc.h" "ac_cv_header_iddawc_h" "$ac_includes_default"
+if test "x$ac_cv_header_iddawc_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <iddawc.h> is required for OAuth" "$LINENO" 5
+fi
+
+
fi
if test "$PORTNAME" = "win32" ; then
diff --git a/configure.ac b/configure.ac
index 078381e568..4050f91dbd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -887,6 +887,17 @@ AC_MSG_RESULT([$with_ldap])
AC_SUBST(with_ldap)
+#
+# OAuth 2.0
+#
+AC_MSG_CHECKING([whether to build with OAuth support])
+PGAC_ARG_BOOL(with, oauth, no,
+ [build with OAuth 2.0 support],
+ [AC_DEFINE([USE_OAUTH], 1, [Define to 1 to build with OAuth 2.0 support. (--with-oauth)])])
+AC_MSG_RESULT([$with_oauth])
+AC_SUBST(with_oauth)
+
+
#
# Bonjour
#
@@ -1388,6 +1399,10 @@ fi
AC_SUBST(LDAP_LIBS_FE)
AC_SUBST(LDAP_LIBS_BE)
+if test "$with_oauth" = yes ; then
+ AC_CHECK_LIB(iddawc, i_init_session, [], [AC_MSG_ERROR([library 'iddawc' is required for OAuth support])])
+fi
+
# for contrib/sepgsql
if test "$with_selinux" = yes; then
AC_CHECK_LIB(selinux, security_compute_create_name, [],
@@ -1606,6 +1621,10 @@ elif test "$with_uuid" = ossp ; then
[AC_MSG_ERROR([header file <ossp/uuid.h> or <uuid.h> is required for OSSP UUID])])])
fi
+if test "$with_oauth" != no; then
+ AC_CHECK_HEADER(iddawc.h, [], [AC_MSG_ERROR([header file <iddawc.h> is required for OAuth])])
+fi
+
if test "$PORTNAME" = "win32" ; then
AC_CHECK_HEADERS(crtdefs.h)
fi
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index bbdc1c4bda..c9c61a9c99 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -193,6 +193,7 @@ with_ldap = @with_ldap@
with_libxml = @with_libxml@
with_libxslt = @with_libxslt@
with_llvm = @with_llvm@
+with_oauth = @with_oauth@
with_system_tzdata = @with_system_tzdata@
with_uuid = @with_uuid@
with_zlib = @with_zlib@
diff --git a/src/include/common/oauth-common.h b/src/include/common/oauth-common.h
new file mode 100644
index 0000000000..3fa95ac7e8
--- /dev/null
+++ b/src/include/common/oauth-common.h
@@ -0,0 +1,19 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-common.h
+ * Declarations for helper functions used for OAuth/OIDC authentication
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/oauth-common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef OAUTH_COMMON_H
+#define OAUTH_COMMON_H
+
+/* Name of SASL mechanism per IANA */
+#define OAUTHBEARER_NAME "OAUTHBEARER"
+
+#endif /* OAUTH_COMMON_H */
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 635fbb2181..1b3332601e 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -319,6 +319,9 @@
/* Define to 1 if you have the `crypto' library (-lcrypto). */
#undef HAVE_LIBCRYPTO
+/* Define to 1 if you have the `iddawc' library (-liddawc). */
+#undef HAVE_LIBIDDAWC
+
/* Define to 1 if you have the `ldap' library (-lldap). */
#undef HAVE_LIBLDAP
@@ -922,6 +925,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 to build with OAuth 2.0 support. (--with-oauth) */
+#undef USE_OAUTH
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 3c53393fa4..727305c578 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -62,6 +62,11 @@ OBJS += \
fe-secure-gssapi.o
endif
+ifeq ($(with_oauth),yes)
+OBJS += \
+ fe-auth-oauth.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
@@ -83,7 +88,7 @@ endif
# that are built correctly for use in a shlib.
SHLIB_LINK_INTERNAL = -lpgcommon_shlib -lpgport_shlib
ifneq ($(PORTNAME), win32)
-SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
+SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -liddawc -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
else
SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi32 -lssl -lsocket -lnsl -lresolv -lintl -lm $(PTHREAD_LIBS), $(LIBS)) $(LDAP_LIBS_FE)
endif
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
new file mode 100644
index 0000000000..383c9d4bdb
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -0,0 +1,744 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-oauth.c
+ * The front-end (client) implementation of OAuth/OIDC authentication.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-auth-oauth.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include <iddawc.h>
+
+#include "postgres_fe.h"
+
+#include "common/base64.h"
+#include "common/hmac.h"
+#include "common/jsonapi.h"
+#include "common/oauth-common.h"
+#include "fe-auth.h"
+#include "mb/pg_wchar.h"
+
+/* The exported OAuth callback mechanism. */
+static void *oauth_init(PGconn *conn, const char *password,
+ const char *sasl_mechanism);
+static void oauth_exchange(void *opaq, bool final,
+ char *input, int inputlen,
+ char **output, int *outputlen,
+ bool *done, bool *success);
+static bool oauth_channel_bound(void *opaq);
+static void oauth_free(void *opaq);
+
+const pg_fe_sasl_mech pg_oauth_mech = {
+ oauth_init,
+ oauth_exchange,
+ oauth_channel_bound,
+ oauth_free,
+};
+
+typedef enum
+{
+ FE_OAUTH_INIT,
+ FE_OAUTH_BEARER_SENT,
+ FE_OAUTH_SERVER_ERROR,
+} fe_oauth_state_enum;
+
+typedef struct
+{
+ fe_oauth_state_enum state;
+
+ PGconn *conn;
+} fe_oauth_state;
+
+static void *
+oauth_init(PGconn *conn, const char *password,
+ const char *sasl_mechanism)
+{
+ fe_oauth_state *state;
+
+ /*
+ * We only support one SASL mechanism here; anything else is programmer
+ * error.
+ */
+ Assert(sasl_mechanism != NULL);
+ Assert(!strcmp(sasl_mechanism, OAUTHBEARER_NAME));
+
+ state = malloc(sizeof(*state));
+ if (!state)
+ return NULL;
+
+ state->state = FE_OAUTH_INIT;
+ state->conn = conn;
+
+ return state;
+}
+
+static const char *
+iddawc_error_string(int errcode)
+{
+ switch (errcode)
+ {
+ case I_OK:
+ return "I_OK";
+
+ case I_ERROR:
+ return "I_ERROR";
+
+ case I_ERROR_PARAM:
+ return "I_ERROR_PARAM";
+
+ case I_ERROR_MEMORY:
+ return "I_ERROR_MEMORY";
+
+ case I_ERROR_UNAUTHORIZED:
+ return "I_ERROR_UNAUTHORIZED";
+
+ case I_ERROR_SERVER:
+ return "I_ERROR_SERVER";
+ }
+
+ return "<unknown>";
+}
+
+static void
+iddawc_error(PGconn *conn, int errcode, const char *msg)
+{
+ appendPQExpBufferStr(&conn->errorMessage, libpq_gettext(msg));
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext(" (iddawc error %s)\n"),
+ iddawc_error_string(errcode));
+}
+
+static void
+iddawc_request_error(PGconn *conn, struct _i_session *i, int err, const char *msg)
+{
+ const char *error_code;
+ const char *desc;
+
+ appendPQExpBuffer(&conn->errorMessage, "%s: ", libpq_gettext(msg));
+
+ error_code = i_get_str_parameter(i, I_OPT_ERROR);
+ if (!error_code)
+ {
+ /*
+ * The server didn't give us any useful information, so just print the
+ * error code.
+ */
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("(iddawc error %s)\n"),
+ iddawc_error_string(err));
+ return;
+ }
+
+ /* If the server gave a string description, print that too. */
+ desc = i_get_str_parameter(i, I_OPT_ERROR_DESCRIPTION);
+ if (desc)
+ appendPQExpBuffer(&conn->errorMessage, "%s ", desc);
+
+ appendPQExpBuffer(&conn->errorMessage, "(%s)\n", error_code);
+}
+
+static char *
+get_auth_token(PGconn *conn)
+{
+ PQExpBuffer token_buf = NULL;
+ struct _i_session session;
+ int err;
+ int auth_method;
+ bool user_prompted = false;
+ const char *verification_uri;
+ const char *user_code;
+ const char *access_token;
+ const char *token_type;
+ char *token = NULL;
+
+ if (!conn->oauth_discovery_uri)
+ return strdup(""); /* ask the server for one */
+
+ if (!conn->oauth_client_id)
+ {
+ /* We can't talk to a server without a client identifier. */
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("no oauth_client_id is set for the connection"));
+ return NULL;
+ }
+
+ i_init_session(&session);
+
+ token_buf = createPQExpBuffer();
+ if (!token_buf)
+ goto cleanup;
+
+ err = i_set_str_parameter(&session, I_OPT_OPENID_CONFIG_ENDPOINT, conn->oauth_discovery_uri);
+ if (err)
+ {
+ iddawc_error(conn, err, "failed to set OpenID config endpoint");
+ goto cleanup;
+ }
+
+ err = i_get_openid_config(&session);
+ if (err)
+ {
+ iddawc_error(conn, err, "failed to fetch OpenID discovery document");
+ goto cleanup;
+ }
+
+ if (!i_get_str_parameter(&session, I_OPT_TOKEN_ENDPOINT))
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("issuer has no token endpoint"));
+ goto cleanup;
+ }
+
+ if (!i_get_str_parameter(&session, I_OPT_DEVICE_AUTHORIZATION_ENDPOINT))
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("issuer does not support device authorization"));
+ goto cleanup;
+ }
+
+ err = i_set_response_type(&session, I_RESPONSE_TYPE_DEVICE_CODE);
+ if (err)
+ {
+ iddawc_error(conn, err, "failed to set device code response type");
+ goto cleanup;
+ }
+
+ auth_method = I_TOKEN_AUTH_METHOD_NONE;
+ if (conn->oauth_client_secret && *conn->oauth_client_secret)
+ auth_method = I_TOKEN_AUTH_METHOD_SECRET_BASIC;
+
+ err = i_set_parameter_list(&session,
+ I_OPT_CLIENT_ID, conn->oauth_client_id,
+ I_OPT_CLIENT_SECRET, conn->oauth_client_secret,
+ I_OPT_TOKEN_METHOD, auth_method,
+ I_OPT_SCOPE, conn->oauth_scope,
+ I_OPT_NONE
+ );
+ if (err)
+ {
+ iddawc_error(conn, err, "failed to set client identifier");
+ goto cleanup;
+ }
+
+ err = i_run_device_auth_request(&session);
+ if (err)
+ {
+ iddawc_request_error(conn, &session, err,
+ "failed to obtain device authorization");
+ goto cleanup;
+ }
+
+ verification_uri = i_get_str_parameter(&session, I_OPT_DEVICE_AUTH_VERIFICATION_URI);
+ if (!verification_uri)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("issuer did not provide a verification URI"));
+ goto cleanup;
+ }
+
+ user_code = i_get_str_parameter(&session, I_OPT_DEVICE_AUTH_USER_CODE);
+ if (!user_code)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("issuer did not provide a user code"));
+ goto cleanup;
+ }
+
+ /*
+ * Poll the token endpoint until either the user logs in and authorizes the
+ * use of a token, or a hard failure occurs. We perform one ping _before_
+ * prompting the user, so that we don't make them do the work of logging in
+ * only to find that the token endpoint is completely unreachable.
+ */
+ err = i_run_token_request(&session);
+ while (err)
+ {
+ const char *error_code;
+ uint interval;
+
+ error_code = i_get_str_parameter(&session, I_OPT_ERROR);
+
+ /*
+ * authorization_pending and slow_down are the only acceptable errors;
+ * anything else and we bail.
+ */
+ if (!error_code || (strcmp(error_code, "authorization_pending")
+ && strcmp(error_code, "slow_down")))
+ {
+ iddawc_request_error(conn, &session, err,
+ "OAuth token retrieval failed");
+ goto cleanup;
+ }
+
+ if (!user_prompted)
+ {
+ /*
+ * Now that we know the token endpoint isn't broken, give the user
+ * the login instructions.
+ */
+ pqInternalNotice(&conn->noticeHooks,
+ "Visit %s and enter the code: %s",
+ verification_uri, user_code);
+
+ user_prompted = true;
+ }
+
+ /*
+ * We are required to wait between polls; the server tells us how long.
+ * TODO: if interval's not set, we need to default to five seconds
+ * TODO: sanity check the interval
+ */
+ interval = i_get_int_parameter(&session, I_OPT_DEVICE_AUTH_INTERVAL);
+
+ /*
+ * A slow_down error requires us to permanently increase our retry
+ * interval by five seconds. RFC 8628, Sec. 3.5.
+ */
+ if (!strcmp(error_code, "slow_down"))
+ {
+ interval += 5;
+ i_set_int_parameter(&session, I_OPT_DEVICE_AUTH_INTERVAL, interval);
+ }
+
+ sleep(interval);
+
+ /*
+ * XXX Reset the error code before every call, because iddawc won't do
+ * that for us. This matters if the server first sends a "pending" error
+ * code, then later hard-fails without sending an error code to
+ * overwrite the first one.
+ *
+ * That we have to do this at all seems like a bug in iddawc.
+ */
+ i_set_str_parameter(&session, I_OPT_ERROR, NULL);
+
+ err = i_run_token_request(&session);
+ }
+
+ access_token = i_get_str_parameter(&session, I_OPT_ACCESS_TOKEN);
+ token_type = i_get_str_parameter(&session, I_OPT_TOKEN_TYPE);
+
+ if (!access_token || !token_type || strcasecmp(token_type, "Bearer"))
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("issuer did not provide a bearer token"));
+ goto cleanup;
+ }
+
+ appendPQExpBufferStr(token_buf, "Bearer ");
+ appendPQExpBufferStr(token_buf, access_token);
+
+ if (PQExpBufferBroken(token_buf))
+ goto cleanup;
+
+ token = strdup(token_buf->data);
+
+cleanup:
+ if (token_buf)
+ destroyPQExpBuffer(token_buf);
+ i_clean_session(&session);
+
+ return token;
+}
+
+#define kvsep "\x01"
+
+static char *
+client_initial_response(PGconn *conn)
+{
+ static const char * const resp_format = "n,," kvsep "auth=%s" kvsep kvsep;
+
+ PQExpBuffer token_buf;
+ PQExpBuffer discovery_buf = NULL;
+ char *token = NULL;
+ char *response = NULL;
+
+ token_buf = createPQExpBuffer();
+ if (!token_buf)
+ goto cleanup;
+
+ /*
+ * If we don't yet have a discovery URI, but the user gave us an explicit
+ * issuer, use the .well-known discovery URI for that issuer.
+ */
+ if (!conn->oauth_discovery_uri && conn->oauth_issuer)
+ {
+ discovery_buf = createPQExpBuffer();
+ if (!discovery_buf)
+ goto cleanup;
+
+ appendPQExpBufferStr(discovery_buf, conn->oauth_issuer);
+ appendPQExpBufferStr(discovery_buf, "/.well-known/openid-configuration");
+
+ if (PQExpBufferBroken(discovery_buf))
+ goto cleanup;
+
+ conn->oauth_discovery_uri = strdup(discovery_buf->data);
+ }
+
+ token = get_auth_token(conn);
+ if (!token)
+ goto cleanup;
+
+ appendPQExpBuffer(token_buf, resp_format, token);
+ if (PQExpBufferBroken(token_buf))
+ goto cleanup;
+
+ response = strdup(token_buf->data);
+
+cleanup:
+ if (token)
+ free(token);
+ if (discovery_buf)
+ destroyPQExpBuffer(discovery_buf);
+ if (token_buf)
+ destroyPQExpBuffer(token_buf);
+
+ return response;
+}
+
+#define ERROR_STATUS_FIELD "status"
+#define ERROR_SCOPE_FIELD "scope"
+#define ERROR_OPENID_CONFIGURATION_FIELD "openid-configuration"
+
+struct json_ctx
+{
+ char *errmsg; /* any non-NULL value stops all processing */
+ PQExpBufferData errbuf; /* backing memory for errmsg */
+ int nested; /* nesting level (zero is the top) */
+
+ const char *target_field_name; /* points to a static allocation */
+ char **target_field; /* see below */
+
+ /* target_field, if set, points to one of the following: */
+ char *status;
+ char *scope;
+ char *discovery_uri;
+};
+
+#define oauth_json_has_error(ctx) \
+ (PQExpBufferDataBroken((ctx)->errbuf) || (ctx)->errmsg)
+
+#define oauth_json_set_error(ctx, ...) \
+ do { \
+ appendPQExpBuffer(&(ctx)->errbuf, __VA_ARGS__); \
+ (ctx)->errmsg = (ctx)->errbuf.data; \
+ } while (0)
+
+static void
+oauth_json_object_start(void *state)
+{
+ struct json_ctx *ctx = state;
+
+ if (oauth_json_has_error(ctx))
+ return; /* short-circuit */
+
+ if (ctx->target_field)
+ {
+ Assert(ctx->nested == 1);
+
+ oauth_json_set_error(ctx,
+ libpq_gettext("field \"%s\" must be a string"),
+ ctx->target_field_name);
+ }
+
+ ++ctx->nested;
+}
+
+static void
+oauth_json_object_end(void *state)
+{
+ struct json_ctx *ctx = state;
+
+ if (oauth_json_has_error(ctx))
+ return; /* short-circuit */
+
+ --ctx->nested;
+}
+
+static void
+oauth_json_object_field_start(void *state, char *name, bool isnull)
+{
+ struct json_ctx *ctx = state;
+
+ if (oauth_json_has_error(ctx))
+ {
+ /* short-circuit */
+ free(name);
+ return;
+ }
+
+ if (ctx->nested == 1)
+ {
+ if (!strcmp(name, ERROR_STATUS_FIELD))
+ {
+ ctx->target_field_name = ERROR_STATUS_FIELD;
+ ctx->target_field = &ctx->status;
+ }
+ else if (!strcmp(name, ERROR_SCOPE_FIELD))
+ {
+ ctx->target_field_name = ERROR_SCOPE_FIELD;
+ ctx->target_field = &ctx->scope;
+ }
+ else if (!strcmp(name, ERROR_OPENID_CONFIGURATION_FIELD))
+ {
+ ctx->target_field_name = ERROR_OPENID_CONFIGURATION_FIELD;
+ ctx->target_field = &ctx->discovery_uri;
+ }
+ }
+
+ free(name);
+}
+
+static void
+oauth_json_array_start(void *state)
+{
+ struct json_ctx *ctx = state;
+
+ if (oauth_json_has_error(ctx))
+ return; /* short-circuit */
+
+ if (!ctx->nested)
+ {
+ ctx->errmsg = libpq_gettext("top-level element must be an object");
+ }
+ else if (ctx->target_field)
+ {
+ Assert(ctx->nested == 1);
+
+ oauth_json_set_error(ctx,
+ libpq_gettext("field \"%s\" must be a string"),
+ ctx->target_field_name);
+ }
+}
+
+static void
+oauth_json_scalar(void *state, char *token, JsonTokenType type)
+{
+ struct json_ctx *ctx = state;
+
+ if (oauth_json_has_error(ctx))
+ {
+ /* short-circuit */
+ free(token);
+ return;
+ }
+
+ if (!ctx->nested)
+ {
+ ctx->errmsg = libpq_gettext("top-level element must be an object");
+ }
+ else if (ctx->target_field)
+ {
+ Assert(ctx->nested == 1);
+
+ if (type == JSON_TOKEN_STRING)
+ {
+ *ctx->target_field = token;
+
+ ctx->target_field = NULL;
+ ctx->target_field_name = NULL;
+
+ return; /* don't free the token we're using */
+ }
+
+ oauth_json_set_error(ctx,
+ libpq_gettext("field \"%s\" must be a string"),
+ ctx->target_field_name);
+ }
+
+ free(token);
+}
+
+static bool
+handle_oauth_sasl_error(PGconn *conn, char *msg, int msglen)
+{
+ JsonLexContext lex = {0};
+ JsonSemAction sem = {0};
+ JsonParseErrorType err;
+ struct json_ctx ctx = {0};
+ char *errmsg = NULL;
+
+ /* Sanity check. */
+ if (strlen(msg) != msglen)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("server's error message contained an embedded NULL"));
+ return false;
+ }
+
+ initJsonLexContextCstringLen(&lex, msg, msglen, PG_UTF8, true);
+
+ initPQExpBuffer(&ctx.errbuf);
+ sem.semstate = &ctx;
+
+ sem.object_start = oauth_json_object_start;
+ sem.object_end = oauth_json_object_end;
+ sem.object_field_start = oauth_json_object_field_start;
+ sem.array_start = oauth_json_array_start;
+ sem.scalar = oauth_json_scalar;
+
+ err = pg_parse_json(&lex, &sem);
+
+ if (err != JSON_SUCCESS)
+ {
+ errmsg = json_errdetail(err, &lex);
+ }
+ else if (PQExpBufferDataBroken(ctx.errbuf))
+ {
+ errmsg = libpq_gettext("out of memory");
+ }
+ else if (ctx.errmsg)
+ {
+ errmsg = ctx.errmsg;
+ }
+
+ if (errmsg)
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("failed to parse server's error response: %s"),
+ errmsg);
+
+ /* Don't need the error buffer or the JSON lexer anymore. */
+ termPQExpBuffer(&ctx.errbuf);
+ termJsonLexContext(&lex);
+
+ if (errmsg)
+ return false;
+
+ /* TODO: what if these override what the user already specified? */
+ if (ctx.discovery_uri)
+ {
+ if (conn->oauth_discovery_uri)
+ free(conn->oauth_discovery_uri);
+
+ conn->oauth_discovery_uri = ctx.discovery_uri;
+ }
+
+ if (ctx.scope)
+ {
+ if (conn->oauth_scope)
+ free(conn->oauth_scope);
+
+ conn->oauth_scope = ctx.scope;
+ }
+ /* TODO: missing error scope should clear any existing connection scope */
+
+ if (!ctx.status)
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("server sent error response without a status"));
+ return false;
+ }
+
+ if (!strcmp(ctx.status, "invalid_token"))
+ {
+ /*
+ * invalid_token is the only error code we'll automatically retry for,
+ * but only if we have enough information to do so.
+ */
+ if (conn->oauth_discovery_uri)
+ conn->oauth_want_retry = true;
+ }
+ /* TODO: include status in hard failure message */
+
+ return true;
+}
+
+static void
+oauth_exchange(void *opaq, bool final,
+ char *input, int inputlen,
+ char **output, int *outputlen,
+ bool *done, bool *success)
+{
+ fe_oauth_state *state = opaq;
+ PGconn *conn = state->conn;
+
+ *done = false;
+ *success = false;
+ *output = NULL;
+ *outputlen = 0;
+
+ switch (state->state)
+ {
+ case FE_OAUTH_INIT:
+ Assert(inputlen == -1);
+
+ *output = client_initial_response(conn);
+ if (!*output)
+ goto error;
+
+ *outputlen = strlen(*output);
+ state->state = FE_OAUTH_BEARER_SENT;
+
+ break;
+
+ case FE_OAUTH_BEARER_SENT:
+ if (final)
+ {
+ /* TODO: ensure there is no message content here. */
+ *done = true;
+ *success = true;
+
+ break;
+ }
+
+ /*
+ * Error message sent by the server.
+ */
+ if (!handle_oauth_sasl_error(conn, input, inputlen))
+ goto error;
+
+ /*
+ * Respond with the required dummy message (RFC 7628, sec. 3.2.3).
+ */
+ *output = strdup(kvsep);
+ *outputlen = strlen(*output); /* == 1 */
+
+ state->state = FE_OAUTH_SERVER_ERROR;
+ break;
+
+ case FE_OAUTH_SERVER_ERROR:
+ /*
+ * After an error, the server should send an error response to fail
+ * the SASL handshake, which is handled in higher layers.
+ *
+ * If we get here, the server either sent *another* challenge which
+ * isn't defined in the RFC, or completed the handshake successfully
+ * after telling us it was going to fail. Neither is acceptable.
+ */
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("server sent additional OAuth data after error\n"));
+ goto error;
+
+ default:
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("invalid OAuth exchange state\n"));
+ goto error;
+ }
+
+ return;
+
+error:
+ *done = true;
+ *success = false;
+}
+
+static bool
+oauth_channel_bound(void *opaq)
+{
+ /* This mechanism does not support channel binding. */
+ return false;
+}
+
+static void
+oauth_free(void *opaq)
+{
+ fe_oauth_state *state = opaq;
+
+ free(state);
+}
diff --git a/src/interfaces/libpq/fe-auth-sasl.h b/src/interfaces/libpq/fe-auth-sasl.h
index da3c30b87b..b1bb382f70 100644
--- a/src/interfaces/libpq/fe-auth-sasl.h
+++ b/src/interfaces/libpq/fe-auth-sasl.h
@@ -65,6 +65,8 @@ typedef struct pg_fe_sasl_mech
*
* state: The opaque mechanism state returned by init()
*
+ * final: true if the server has sent a final exchange outcome
+ *
* input: The challenge data sent by the server, or NULL when
* generating a client-first initial response (that is, when
* the server expects the client to send a message to start
@@ -92,7 +94,8 @@ typedef struct pg_fe_sasl_mech
* Ignored if *done is false.
*--------
*/
- void (*exchange) (void *state, char *input, int inputlen,
+ void (*exchange) (void *state, bool final,
+ char *input, int inputlen,
char **output, int *outputlen,
bool *done, bool *success);
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
index e616200704..681b76adbe 100644
--- a/src/interfaces/libpq/fe-auth-scram.c
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -24,7 +24,8 @@
/* The exported SCRAM callback mechanism. */
static void *scram_init(PGconn *conn, const char *password,
const char *sasl_mechanism);
-static void scram_exchange(void *opaq, char *input, int inputlen,
+static void scram_exchange(void *opaq, bool final,
+ char *input, int inputlen,
char **output, int *outputlen,
bool *done, bool *success);
static bool scram_channel_bound(void *opaq);
@@ -206,7 +207,8 @@ scram_free(void *opaq)
* Exchange a SCRAM message with backend.
*/
static void
-scram_exchange(void *opaq, char *input, int inputlen,
+scram_exchange(void *opaq, bool final,
+ char *input, int inputlen,
char **output, int *outputlen,
bool *done, bool *success)
{
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 6fceff561b..2567a34023 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -38,6 +38,7 @@
#endif
#include "common/md5.h"
+#include "common/oauth-common.h"
#include "common/scram-common.h"
#include "fe-auth.h"
#include "fe-auth-sasl.h"
@@ -422,7 +423,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
bool success;
const char *selected_mechanism;
PQExpBufferData mechanism_buf;
- char *password;
+ char *password = NULL;
initPQExpBuffer(&mechanism_buf);
@@ -444,8 +445,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
/*
* Parse the list of SASL authentication mechanisms in the
* AuthenticationSASL message, and select the best mechanism that we
- * support. SCRAM-SHA-256-PLUS and SCRAM-SHA-256 are the only ones
- * supported at the moment, listed by order of decreasing importance.
+ * support. Mechanisms are listed by order of decreasing importance.
*/
selected_mechanism = NULL;
for (;;)
@@ -485,6 +485,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
{
selected_mechanism = SCRAM_SHA_256_PLUS_NAME;
conn->sasl = &pg_scram_mech;
+ conn->password_needed = true;
}
#else
/*
@@ -522,7 +523,17 @@ pg_SASL_init(PGconn *conn, int payloadlen)
{
selected_mechanism = SCRAM_SHA_256_NAME;
conn->sasl = &pg_scram_mech;
+ conn->password_needed = true;
}
+#ifdef USE_OAUTH
+ else if (strcmp(mechanism_buf.data, OAUTHBEARER_NAME) == 0 &&
+ !selected_mechanism)
+ {
+ selected_mechanism = OAUTHBEARER_NAME;
+ conn->sasl = &pg_oauth_mech;
+ conn->password_needed = false;
+ }
+#endif
}
if (!selected_mechanism)
@@ -547,18 +558,19 @@ pg_SASL_init(PGconn *conn, int payloadlen)
/*
* First, select the password to use for the exchange, complaining if
- * there isn't one. Currently, all supported SASL mechanisms require a
- * password, so we can just go ahead here without further distinction.
+ * there isn't one and the SASL mechanism needs it.
*/
- conn->password_needed = true;
- password = conn->connhost[conn->whichhost].password;
- if (password == NULL)
- password = conn->pgpass;
- if (password == NULL || password[0] == '\0')
+ if (conn->password_needed)
{
- appendPQExpBufferStr(&conn->errorMessage,
- PQnoPasswordSupplied);
- goto error;
+ password = conn->connhost[conn->whichhost].password;
+ if (password == NULL)
+ password = conn->pgpass;
+ if (password == NULL || password[0] == '\0')
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ PQnoPasswordSupplied);
+ goto error;
+ }
}
Assert(conn->sasl);
@@ -576,7 +588,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
goto oom_error;
/* Get the mechanism-specific Initial Client Response, if any */
- conn->sasl->exchange(conn->sasl_state,
+ conn->sasl->exchange(conn->sasl_state, false,
NULL, -1,
&initialresponse, &initialresponselen,
&done, &success);
@@ -657,7 +669,7 @@ pg_SASL_continue(PGconn *conn, int payloadlen, bool final)
/* For safety and convenience, ensure the buffer is NULL-terminated. */
challenge[payloadlen] = '\0';
- conn->sasl->exchange(conn->sasl_state,
+ conn->sasl->exchange(conn->sasl_state, final,
challenge, payloadlen,
&output, &outputlen,
&done, &success);
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index 049a8bb1a1..2a56774019 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -28,4 +28,7 @@ extern const pg_fe_sasl_mech pg_scram_mech;
extern char *pg_fe_scram_build_secret(const char *password,
const char **errstr);
+/* Mechanisms in fe-auth-oauth.c */
+extern const pg_fe_sasl_mech pg_oauth_mech;
+
#endif /* FE_AUTH_H */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index cf554d389f..fdd30d71de 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,23 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ /* OAuth v2 */
+ {"oauth_issuer", NULL, NULL, NULL,
+ "OAuth-Issuer", "", 40,
+ offsetof(struct pg_conn, oauth_issuer)},
+
+ {"oauth_client_id", NULL, NULL, NULL,
+ "OAuth-Client-ID", "", 40,
+ offsetof(struct pg_conn, oauth_client_id)},
+
+ {"oauth_client_secret", NULL, NULL, NULL,
+ "OAuth-Client-Secret", "", 40,
+ offsetof(struct pg_conn, oauth_client_secret)},
+
+ {"oauth_scope", NULL, NULL, NULL,
+ "OAuth-Scope", "", 15,
+ offsetof(struct pg_conn, oauth_scope)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
@@ -606,6 +623,7 @@ pqDropServerData(PGconn *conn)
conn->write_err_msg = NULL;
conn->be_pid = 0;
conn->be_key = 0;
+ /* conn->oauth_want_retry = false; TODO */
}
@@ -3386,6 +3404,16 @@ keep_going: /* We will come back to here until there is
/* Check to see if we should mention pgpassfile */
pgpassfileWarning(conn);
+#ifdef USE_OAUTH
+ if (conn->sasl == &pg_oauth_mech
+ && conn->oauth_want_retry)
+ {
+ /* TODO: only allow retry once */
+ need_new_connection = true;
+ goto keep_going;
+ }
+#endif
+
#ifdef ENABLE_GSS
/*
@@ -4166,6 +4194,16 @@ freePGconn(PGconn *conn)
free(conn->rowBuf);
if (conn->target_session_attrs)
free(conn->target_session_attrs);
+ if (conn->oauth_issuer)
+ free(conn->oauth_issuer);
+ if (conn->oauth_discovery_uri)
+ free(conn->oauth_discovery_uri);
+ if (conn->oauth_client_id)
+ free(conn->oauth_client_id);
+ if (conn->oauth_client_secret)
+ free(conn->oauth_client_secret);
+ if (conn->oauth_scope)
+ free(conn->oauth_scope);
termPQExpBuffer(&conn->errorMessage);
termPQExpBuffer(&conn->workBuffer);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index e0cee4b142..0dff13505a 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -394,6 +394,14 @@ struct pg_conn
char *ssl_max_protocol_version; /* maximum TLS protocol version */
char *target_session_attrs; /* desired session properties */
+ /* OAuth v2 */
+ char *oauth_issuer; /* token issuer URL */
+ char *oauth_discovery_uri; /* URI of the issuer's discovery document */
+ char *oauth_client_id; /* client identifier */
+ char *oauth_client_secret; /* client secret */
+ char *oauth_scope; /* access token scope */
+ bool oauth_want_retry; /* should we retry on failure? */
+
/* Optional file to write trace info to */
FILE *Pfdebug;
int traceFlags;
--
2.25.1
[text/x-patch] v4-0007-backend-add-OAUTHBEARER-SASL-mechanism.patch (35.6K, ../../[email protected]/8-v4-0007-backend-add-OAUTHBEARER-SASL-mechanism.patch)
download | inline diff:
From b3ceda62e9cc6cbbc24c63c05c5ce072ae771c1b Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Tue, 4 May 2021 16:21:11 -0700
Subject: [PATCH v4 07/10] backend: add OAUTHBEARER SASL mechanism
DO NOT USE THIS PROOF OF CONCEPT IN PRODUCTION.
Implement OAUTHBEARER (RFC 7628) on the server side. This adds a new
auth method, oauth, to pg_hba.
Because OAuth implementations vary so wildly, and bearer token
validation is heavily dependent on the issuing party, authn/z is done by
communicating with an external program: the oauth_validator_command.
This command must do the following:
1. Receive the bearer token by reading its contents from a file
descriptor passed from the server. (The numeric value of this
descriptor may be inserted into the oauth_validator_command using the
%f specifier.)
This MUST be the first action the command performs. The server will
not begin reading stdout from the command until the token has been
read in full, so if the command tries to print anything and hits a
buffer limit, the backend will deadlock and time out.
2. Validate the bearer token. The correct way to do this depends on the
issuer, but it generally involves either cryptographic operations to
prove that the token was issued by a trusted party, or the
presentation of the bearer token to some other party so that _it_ can
perform validation.
The command MUST maintain confidentiality of the bearer token, since
in most cases it can be used just like a password. (There are ways to
cryptographically bind tokens to client certificates, but they are
way beyond the scope of this commit message.)
If the token cannot be validated, the command must exit with a
non-zero status. Further authentication/authorization is pointless if
the bearer token wasn't issued by someone you trust.
3. Authenticate the user, authorize the user, or both:
a. To authenticate the user, use the bearer token to retrieve some
trusted identifier string for the end user. The exact process for
this is, again, issuer-dependent. The command should print the
authenticated identity string to stdout, followed by a newline.
If the user cannot be authenticated, the validator should not
print anything to stdout. It should also exit with a non-zero
status, unless the token may be used to authorize the connection
through some other means (see below).
On a success, the command may then exit with a zero success code.
By default, the server will then check to make sure the identity
string matches the role that is being used (or matches a usermap
entry, if one is in use).
b. To optionally authorize the user, in combination with the HBA
option trust_validator_authz=1 (see below), the validator simply
returns a zero exit code if the client should be allowed to
connect with its presented role (which can be passed to the
command using the %r specifier), or a non-zero code otherwise.
The hard part is in determining whether the given token truly
authorizes the client to use the given role, which must
unfortunately be left as an exercise to the reader.
This obviously requires some care, as a poorly implemented token
validator may silently open the entire database to anyone with a
bearer token. But it may be a more portable approach, since OAuth
is designed as an authorization framework, not an authentication
framework. For example, the user's bearer token could carry an
"allow_superuser_access" claim, which would authorize pseudonymous
database access as any role. It's then up to the OAuth system
administrators to ensure that allow_superuser_access is doled out
only to the proper users.
c. It's possible that the user can be successfully authenticated but
isn't authorized to connect. In this case, the command may print
the authenticated ID and then fail with a non-zero exit code.
(This makes it easier to see what's going on in the Postgres
logs.)
4. Token validators may optionally log to stderr. This will be printed
verbatim into the Postgres server logs.
The oauth method supports the following HBA options (but note that two
of them are not optional, since we have no way of choosing sensible
defaults):
issuer: Required. The URL of the OAuth issuing party, which the client
must contact to receive a bearer token.
Some real-world examples as of time of writing:
- https://accounts.google.com
- https://login.microsoft.com/[tenant-id]/v2.0
scope: Required. The OAuth scope(s) required for the server to
authenticate and/or authorize the user. This is heavily
deployment-specific, but a simple example is "openid email".
map: Optional. Specify a standard PostgreSQL user map; this works
the same as with other auth methods such as peer. If a map is
not specified, the user ID returned by the token validator
must exactly match the role that's being requested (but see
trust_validator_authz, below).
trust_validator_authz:
Optional. When set to 1, this allows the token validator to
take full control of the authorization process. Standard user
mapping is skipped: if the validator command succeeds, the
client is allowed to connect under its desired role and no
further checks are done.
Unlike the client, servers support OAuth without needing to be built
against libiddawc (since the responsibility for "speaking" OAuth/OIDC
correctly is delegated entirely to the oauth_validator_command).
Several TODOs:
- port to platforms other than "modern Linux"
- overhaul the communication with oauth_validator_command, which is
currently a bad hack on OpenPipeStream()
- implement more sanity checks on the OAUTHBEARER message format and
tokens sent by the client
- implement more helpful handling of HBA misconfigurations
- properly interpolate JSON when generating error responses
- use logdetail during auth failures
- deal with role names that can't be safely passed to system() without
shell-escaping
- allow passing the configured issuer to the oauth_validator_command, to
deal with multi-issuer setups
- ...and more.
---
src/backend/libpq/Makefile | 1 +
src/backend/libpq/auth-oauth.c | 797 +++++++++++++++++++++++++++++++++
src/backend/libpq/auth-sasl.c | 10 +-
src/backend/libpq/auth-scram.c | 4 +-
src/backend/libpq/auth.c | 7 +
src/backend/libpq/hba.c | 29 +-
src/backend/utils/misc/guc.c | 12 +
src/include/libpq/hba.h | 8 +-
src/include/libpq/oauth.h | 24 +
src/include/libpq/sasl.h | 11 +
10 files changed, 889 insertions(+), 14 deletions(-)
create mode 100644 src/backend/libpq/auth-oauth.c
create mode 100644 src/include/libpq/oauth.h
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a4..98eb2a8242 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -15,6 +15,7 @@ include $(top_builddir)/src/Makefile.global
# be-fsstubs is here for historical reasons, probably belongs elsewhere
OBJS = \
+ auth-oauth.o \
auth-sasl.o \
auth-scram.o \
auth.o \
diff --git a/src/backend/libpq/auth-oauth.c b/src/backend/libpq/auth-oauth.c
new file mode 100644
index 0000000000..c1232a31a0
--- /dev/null
+++ b/src/backend/libpq/auth-oauth.c
@@ -0,0 +1,797 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-oauth.c
+ * Server-side implementation of the SASL OAUTHBEARER mechanism.
+ *
+ * See the following RFC for more details:
+ * - RFC 7628: https://tools.ietf.org/html/rfc7628
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/libpq/auth-oauth.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "common/oauth-common.h"
+#include "lib/stringinfo.h"
+#include "libpq/auth.h"
+#include "libpq/hba.h"
+#include "libpq/oauth.h"
+#include "libpq/sasl.h"
+#include "storage/fd.h"
+
+/* GUC */
+char *oauth_validator_command;
+
+static void oauth_get_mechanisms(Port *port, StringInfo buf);
+static void *oauth_init(Port *port, const char *selected_mech, const char *shadow_pass);
+static int oauth_exchange(void *opaq, const char *input, int inputlen,
+ char **output, int *outputlen, const char **logdetail);
+
+/* Mechanism declaration */
+const pg_be_sasl_mech pg_be_oauth_mech = {
+ oauth_get_mechanisms,
+ oauth_init,
+ oauth_exchange,
+
+ PG_MAX_AUTH_TOKEN_LENGTH,
+};
+
+
+typedef enum
+{
+ OAUTH_STATE_INIT = 0,
+ OAUTH_STATE_ERROR,
+ OAUTH_STATE_FINISHED,
+} oauth_state;
+
+struct oauth_ctx
+{
+ oauth_state state;
+ Port *port;
+ const char *issuer;
+ const char *scope;
+};
+
+static char *sanitize_char(char c);
+static char *parse_kvpairs_for_auth(char **input);
+static void generate_error_response(struct oauth_ctx *ctx, char **output, int *outputlen);
+static bool validate(Port *port, const char *auth, const char **logdetail);
+static bool run_validator_command(Port *port, const char *token);
+static bool check_exit(FILE **fh, const char *command);
+static bool unset_cloexec(int fd);
+static bool username_ok_for_shell(const char *username);
+
+#define KVSEP 0x01
+#define AUTH_KEY "auth"
+#define BEARER_SCHEME "Bearer "
+
+static void
+oauth_get_mechanisms(Port *port, StringInfo buf)
+{
+ /* Only OAUTHBEARER is supported. */
+ appendStringInfoString(buf, OAUTHBEARER_NAME);
+ appendStringInfoChar(buf, '\0');
+}
+
+static void *
+oauth_init(Port *port, const char *selected_mech, const char *shadow_pass)
+{
+ struct oauth_ctx *ctx;
+
+ if (strcmp(selected_mech, OAUTHBEARER_NAME))
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("client selected an invalid SASL authentication mechanism")));
+
+ ctx = palloc0(sizeof(*ctx));
+
+ ctx->state = OAUTH_STATE_INIT;
+ ctx->port = port;
+
+ Assert(port->hba);
+ ctx->issuer = port->hba->oauth_issuer;
+ ctx->scope = port->hba->oauth_scope;
+
+ return ctx;
+}
+
+static int
+oauth_exchange(void *opaq, const char *input, int inputlen,
+ char **output, int *outputlen, const char **logdetail)
+{
+ char *p;
+ char cbind_flag;
+ char *auth;
+
+ struct oauth_ctx *ctx = opaq;
+
+ *output = NULL;
+ *outputlen = -1;
+
+ /*
+ * If the client didn't include an "Initial Client Response" in the
+ * SASLInitialResponse message, send an empty challenge, to which the
+ * client will respond with the same data that usually comes in the
+ * Initial Client Response.
+ */
+ if (input == NULL)
+ {
+ Assert(ctx->state == OAUTH_STATE_INIT);
+
+ *output = pstrdup("");
+ *outputlen = 0;
+ return PG_SASL_EXCHANGE_CONTINUE;
+ }
+
+ /*
+ * Check that the input length agrees with the string length of the input.
+ */
+ if (inputlen == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("The message is empty.")));
+ if (inputlen != strlen(input))
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message length does not match input length.")));
+
+ switch (ctx->state)
+ {
+ case OAUTH_STATE_INIT:
+ /* Handle this case below. */
+ break;
+
+ case OAUTH_STATE_ERROR:
+ /*
+ * Only one response is valid for the client during authentication
+ * failure: a single kvsep.
+ */
+ if (inputlen != 1 || *input != KVSEP)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Client did not send a kvsep response.")));
+
+ /* The (failed) handshake is now complete. */
+ ctx->state = OAUTH_STATE_FINISHED;
+ return PG_SASL_EXCHANGE_FAILURE;
+
+ default:
+ elog(ERROR, "invalid OAUTHBEARER exchange state");
+ return PG_SASL_EXCHANGE_FAILURE;
+ }
+
+ /* Handle the client's initial message. */
+ p = pstrdup(input);
+
+ /*
+ * OAUTHBEARER does not currently define a channel binding (so there is no
+ * OAUTHBEARER-PLUS, and we do not accept a 'p' specifier). We accept a 'y'
+ * specifier purely for the remote chance that a future specification could
+ * define one; then future clients can still interoperate with this server
+ * implementation. 'n' is the expected case.
+ */
+ cbind_flag = *p;
+ switch (cbind_flag)
+ {
+ case 'p':
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("The server does not support channel binding for OAuth, but the client message includes channel binding data.")));
+ break;
+
+ case 'y': /* fall through */
+ case 'n':
+ p++;
+ if (*p != ',')
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Comma expected, but found character %s.",
+ sanitize_char(*p))));
+ p++;
+ break;
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Unexpected channel-binding flag %s.",
+ sanitize_char(cbind_flag))));
+ }
+
+ /*
+ * Forbid optional authzid (authorization identity). We don't support it.
+ */
+ if (*p == 'a')
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("client uses authorization identity, but it is not supported")));
+ if (*p != ',')
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Unexpected attribute %s in client-first-message.",
+ sanitize_char(*p))));
+ p++;
+
+ /* All remaining fields are separated by the RFC's kvsep (\x01). */
+ if (*p != KVSEP)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Key-value separator expected, but found character %s.",
+ sanitize_char(*p))));
+ p++;
+
+ auth = parse_kvpairs_for_auth(&p);
+ if (!auth)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message does not contain an auth value.")));
+
+ /* We should be at the end of our message. */
+ if (*p)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message contains additional data after the final terminator.")));
+
+ if (!validate(ctx->port, auth, logdetail))
+ {
+ generate_error_response(ctx, output, outputlen);
+
+ ctx->state = OAUTH_STATE_ERROR;
+ return PG_SASL_EXCHANGE_CONTINUE;
+ }
+
+ ctx->state = OAUTH_STATE_FINISHED;
+ return PG_SASL_EXCHANGE_SUCCESS;
+}
+
+/*
+ * Convert an arbitrary byte to printable form. For error messages.
+ *
+ * If it's a printable ASCII character, print it as a single character.
+ * otherwise, print it in hex.
+ *
+ * The returned pointer points to a static buffer.
+ */
+static char *
+sanitize_char(char c)
+{
+ static char buf[5];
+
+ if (c >= 0x21 && c <= 0x7E)
+ snprintf(buf, sizeof(buf), "'%c'", c);
+ else
+ snprintf(buf, sizeof(buf), "0x%02x", (unsigned char) c);
+ return buf;
+}
+
+/*
+ * Consumes all kvpairs in an OAUTHBEARER exchange message. If the "auth" key is
+ * found, its value is returned.
+ */
+static char *
+parse_kvpairs_for_auth(char **input)
+{
+ char *pos = *input;
+ char *auth = NULL;
+
+ /*
+ * The relevant ABNF, from Sec. 3.1:
+ *
+ * kvsep = %x01
+ * key = 1*(ALPHA)
+ * value = *(VCHAR / SP / HTAB / CR / LF )
+ * kvpair = key "=" value kvsep
+ * ;;gs2-header = See RFC 5801
+ * client-resp = (gs2-header kvsep *kvpair kvsep) / kvsep
+ *
+ * By the time we reach this code, the gs2-header and initial kvsep have
+ * already been validated. We start at the beginning of the first kvpair.
+ */
+
+ while (*pos)
+ {
+ char *end;
+ char *sep;
+ char *key;
+ char *value;
+
+ /*
+ * Find the end of this kvpair. Note that input is null-terminated by
+ * the SASL code, so the strchr() is bounded.
+ */
+ end = strchr(pos, KVSEP);
+ if (!end)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message contains an unterminated key/value pair.")));
+ *end = '\0';
+
+ if (pos == end)
+ {
+ /* Empty kvpair, signifying the end of the list. */
+ *input = pos + 1;
+ return auth;
+ }
+
+ /*
+ * Find the end of the key name.
+ *
+ * TODO further validate the key/value grammar? empty keys, bad chars...
+ */
+ sep = strchr(pos, '=');
+ if (!sep)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message contains a key without a value.")));
+ *sep = '\0';
+
+ /* Both key and value are now safely terminated. */
+ key = pos;
+ value = sep + 1;
+
+ if (!strcmp(key, AUTH_KEY))
+ {
+ if (auth)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message contains multiple auth values.")));
+
+ auth = value;
+ }
+ else
+ {
+ /*
+ * The RFC also defines the host and port keys, but they are not
+ * required for OAUTHBEARER and we do not use them. Also, per
+ * Sec. 3.1, any key/value pairs we don't recognize must be ignored.
+ */
+ }
+
+ /* Move to the next pair. */
+ pos = end + 1;
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Message did not contain a final terminator.")));
+
+ return NULL; /* unreachable */
+}
+
+static void
+generate_error_response(struct oauth_ctx *ctx, char **output, int *outputlen)
+{
+ StringInfoData buf;
+
+ /*
+ * The admin needs to set an issuer and scope for OAuth to work. There's not
+ * really a way to hide this from the user, either, because we can't choose
+ * a "default" issuer, so be honest in the failure message.
+ *
+ * TODO: see if there's a better place to fail, earlier than this.
+ */
+ if (!ctx->issuer || !ctx->scope)
+ ereport(FATAL,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("OAuth is not properly configured for this user"),
+ errdetail_log("The issuer and scope parameters must be set in pg_hba.conf.")));
+
+
+ initStringInfo(&buf);
+
+ /*
+ * TODO: JSON escaping
+ */
+ appendStringInfo(&buf,
+ "{ "
+ "\"status\": \"invalid_token\", "
+ "\"openid-configuration\": \"%s/.well-known/openid-configuration\","
+ "\"scope\": \"%s\" "
+ "}",
+ ctx->issuer, ctx->scope);
+
+ *output = buf.data;
+ *outputlen = buf.len;
+}
+
+static bool
+validate(Port *port, const char *auth, const char **logdetail)
+{
+ static const char * const b64_set = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789-._~+/";
+
+ const char *token;
+ size_t span;
+ int ret;
+
+ /* TODO: handle logdetail when the test framework can check it */
+
+ /*
+ * Only Bearer tokens are accepted. The ABNF is defined in RFC 6750, Sec.
+ * 2.1:
+ *
+ * b64token = 1*( ALPHA / DIGIT /
+ * "-" / "." / "_" / "~" / "+" / "/" ) *"="
+ * credentials = "Bearer" 1*SP b64token
+ *
+ * The "credentials" construction is what we receive in our auth value.
+ *
+ * Since that spec is subordinate to HTTP (i.e. the HTTP Authorization
+ * header format; RFC 7235 Sec. 2), the "Bearer" scheme string must be
+ * compared case-insensitively. (This is not mentioned in RFC 6750, but it's
+ * pointed out in RFC 7628 Sec. 4.)
+ *
+ * TODO: handle the Authorization spec, RFC 7235 Sec. 2.1.
+ */
+ if (strncasecmp(auth, BEARER_SCHEME, strlen(BEARER_SCHEME)))
+ return false;
+
+ /* Pull the bearer token out of the auth value. */
+ token = auth + strlen(BEARER_SCHEME);
+
+ /* Swallow any additional spaces. */
+ while (*token == ' ')
+ token++;
+
+ /*
+ * Before invoking the validator command, sanity-check the token format to
+ * avoid any injection attacks later in the chain. Invalid formats are
+ * technically a protocol violation, but don't reflect any information about
+ * the sensitive Bearer token back to the client; log at COMMERROR instead.
+ */
+
+ /* Tokens must not be empty. */
+ if (!*token)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Bearer token is empty.")));
+ return false;
+ }
+
+ /*
+ * Make sure the token contains only allowed characters. Tokens may end with
+ * any number of '=' characters.
+ */
+ span = strspn(token, b64_set);
+ while (token[span] == '=')
+ span++;
+
+ if (token[span] != '\0')
+ {
+ /*
+ * This error message could be more helpful by printing the problematic
+ * character(s), but that'd be a bit like printing a piece of someone's
+ * password into the logs.
+ */
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed OAUTHBEARER message"),
+ errdetail("Bearer token is not in the correct format.")));
+ return false;
+ }
+
+ /* Have the validator check the token. */
+ if (!run_validator_command(port, token))
+ return false;
+
+ if (port->hba->oauth_skip_usermap)
+ {
+ /*
+ * If the validator is our authorization authority, we're done.
+ * Authentication may or may not have been performed depending on the
+ * validator implementation; all that matters is that the validator says
+ * the user can log in with the target role.
+ */
+ return true;
+ }
+
+ /* Make sure the validator authenticated the user. */
+ if (!port->authn_id)
+ {
+ /* TODO: use logdetail; reduce message duplication */
+ ereport(LOG,
+ (errmsg("OAuth bearer authentication failed for user \"%s\": validator provided no identity",
+ port->user_name)));
+ return false;
+ }
+
+ /* Finally, check the user map. */
+ ret = check_usermap(port->hba->usermap, port->user_name, port->authn_id,
+ false);
+ return (ret == STATUS_OK);
+}
+
+static bool
+run_validator_command(Port *port, const char *token)
+{
+ bool success = false;
+ int rc;
+ int pipefd[2];
+ int rfd = -1;
+ int wfd = -1;
+
+ StringInfoData command = { 0 };
+ char *p;
+ FILE *fh = NULL;
+
+ ssize_t written;
+ char *line = NULL;
+ size_t size = 0;
+ ssize_t len;
+
+ Assert(oauth_validator_command);
+
+ if (!oauth_validator_command[0])
+ {
+ ereport(COMMERROR,
+ (errmsg("oauth_validator_command is not set"),
+ errhint("To allow OAuth authenticated connections, set "
+ "oauth_validator_command in postgresql.conf.")));
+ return false;
+ }
+
+ /*
+ * Since popen() is unidirectional, open up a pipe for the other direction.
+ * Use CLOEXEC to ensure that our write end doesn't accidentally get copied
+ * into child processes, which would prevent us from closing it cleanly.
+ *
+ * XXX this is ugly. We should just read from the child process's stdout,
+ * but that's a lot more code.
+ * XXX by bypassing the popen API, we open the potential of process
+ * deadlock. Clearly document child process requirements (i.e. the child
+ * MUST read all data off of the pipe before writing anything).
+ * TODO: port to Windows using _pipe().
+ */
+ rc = pipe2(pipefd, O_CLOEXEC);
+ if (rc < 0)
+ {
+ ereport(COMMERROR,
+ (errcode_for_file_access(),
+ errmsg("could not create child pipe: %m")));
+ return false;
+ }
+
+ rfd = pipefd[0];
+ wfd = pipefd[1];
+
+ /* Allow the read pipe be passed to the child. */
+ if (!unset_cloexec(rfd))
+ {
+ /* error message was already logged */
+ goto cleanup;
+ }
+
+ /*
+ * Construct the command, substituting any recognized %-specifiers:
+ *
+ * %f: the file descriptor of the input pipe
+ * %r: the role that the client wants to assume (port->user_name)
+ * %%: a literal '%'
+ */
+ initStringInfo(&command);
+
+ for (p = oauth_validator_command; *p; p++)
+ {
+ if (p[0] == '%')
+ {
+ switch (p[1])
+ {
+ case 'f':
+ appendStringInfo(&command, "%d", rfd);
+ p++;
+ break;
+ case 'r':
+ /*
+ * TODO: decide how this string should be escaped. The role
+ * is controlled by the client, so if we don't escape it,
+ * command injections are inevitable.
+ *
+ * This is probably an indication that the role name needs
+ * to be communicated to the validator process in some other
+ * way. For this proof of concept, just be incredibly strict
+ * about the characters that are allowed in user names.
+ */
+ if (!username_ok_for_shell(port->user_name))
+ goto cleanup;
+
+ appendStringInfoString(&command, port->user_name);
+ p++;
+ break;
+ case '%':
+ appendStringInfoChar(&command, '%');
+ p++;
+ break;
+ default:
+ appendStringInfoChar(&command, p[0]);
+ }
+ }
+ else
+ appendStringInfoChar(&command, p[0]);
+ }
+
+ /* Execute the command. */
+ fh = OpenPipeStream(command.data, "re");
+ /* TODO: handle failures */
+
+ /* We don't need the read end of the pipe anymore. */
+ close(rfd);
+ rfd = -1;
+
+ /* Give the command the token to validate. */
+ written = write(wfd, token, strlen(token));
+ if (written != strlen(token))
+ {
+ /* TODO must loop for short writes, EINTR et al */
+ ereport(COMMERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write token to child pipe: %m")));
+ goto cleanup;
+ }
+
+ close(wfd);
+ wfd = -1;
+
+ /*
+ * Read the command's response.
+ *
+ * TODO: getline() is probably too new to use, unfortunately.
+ * TODO: loop over all lines
+ */
+ if ((len = getline(&line, &size, fh)) >= 0)
+ {
+ /* TODO: fail if the authn_id doesn't end with a newline */
+ if (len > 0)
+ line[len - 1] = '\0';
+
+ set_authn_id(port, line);
+ }
+ else if (ferror(fh))
+ {
+ ereport(COMMERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read from command \"%s\": %m",
+ command.data)));
+ goto cleanup;
+ }
+
+ /* Make sure the command exits cleanly. */
+ if (!check_exit(&fh, command.data))
+ {
+ /* error message already logged */
+ goto cleanup;
+ }
+
+ /* Done. */
+ success = true;
+
+cleanup:
+ if (line)
+ free(line);
+
+ /*
+ * In the successful case, the pipe fds are already closed. For the error
+ * case, always close out the pipe before waiting for the command, to
+ * prevent deadlock.
+ */
+ if (rfd >= 0)
+ close(rfd);
+ if (wfd >= 0)
+ close(wfd);
+
+ if (fh)
+ {
+ Assert(!success);
+ check_exit(&fh, command.data);
+ }
+
+ if (command.data)
+ pfree(command.data);
+
+ return success;
+}
+
+static bool
+check_exit(FILE **fh, const char *command)
+{
+ int rc;
+
+ rc = ClosePipeStream(*fh);
+ *fh = NULL;
+
+ if (rc == -1)
+ {
+ /* pclose() itself failed. */
+ ereport(COMMERROR,
+ (errcode_for_file_access(),
+ errmsg("could not close pipe to command \"%s\": %m",
+ command)));
+ }
+ else if (rc != 0)
+ {
+ char *reason = wait_result_to_str(rc);
+
+ ereport(COMMERROR,
+ (errmsg("failed to execute command \"%s\": %s",
+ command, reason)));
+
+ pfree(reason);
+ }
+
+ return (rc == 0);
+}
+
+static bool
+unset_cloexec(int fd)
+{
+ int flags;
+ int rc;
+
+ flags = fcntl(fd, F_GETFD);
+ if (flags == -1)
+ {
+ ereport(COMMERROR,
+ (errcode_for_file_access(),
+ errmsg("could not get fd flags for child pipe: %m")));
+ return false;
+ }
+
+ rc = fcntl(fd, F_SETFD, flags & ~FD_CLOEXEC);
+ if (rc < 0)
+ {
+ ereport(COMMERROR,
+ (errcode_for_file_access(),
+ errmsg("could not unset FD_CLOEXEC for child pipe: %m")));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * XXX This should go away eventually and be replaced with either a proper
+ * escape or a different strategy for communication with the validator command.
+ */
+static bool
+username_ok_for_shell(const char *username)
+{
+ /* This set is borrowed from fe_utils' appendShellStringNoError(). */
+ static const char * const allowed = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789-_./:";
+ size_t span;
+
+ Assert(username && username[0]); /* should have already been checked */
+
+ span = strspn(username, allowed);
+ if (username[span] != '\0')
+ {
+ ereport(COMMERROR,
+ (errmsg("PostgreSQL user name contains unsafe characters and cannot be passed to the OAuth validator")));
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/backend/libpq/auth-sasl.c b/src/backend/libpq/auth-sasl.c
index a1d7dbb6d5..0f461a6696 100644
--- a/src/backend/libpq/auth-sasl.c
+++ b/src/backend/libpq/auth-sasl.c
@@ -20,14 +20,6 @@
#include "libpq/pqformat.h"
#include "libpq/sasl.h"
-/*
- * Maximum accepted size of SASL messages.
- *
- * The messages that the server or libpq generate are much smaller than this,
- * but have some headroom.
- */
-#define PG_MAX_SASL_MESSAGE_LENGTH 1024
-
/*
* Perform a SASL exchange with a libpq client, using a specific mechanism
* implementation.
@@ -103,7 +95,7 @@ CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
/* Get the actual SASL message */
initStringInfo(&buf);
- if (pq_getmessage(&buf, PG_MAX_SASL_MESSAGE_LENGTH))
+ if (pq_getmessage(&buf, mech->max_message_length))
{
/* EOF - pq_getmessage already logged error */
pfree(buf.data);
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index ee7f52218a..4049ace470 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -118,7 +118,9 @@ static int scram_exchange(void *opaq, const char *input, int inputlen,
const pg_be_sasl_mech pg_be_scram_mech = {
scram_get_mechanisms,
scram_init,
- scram_exchange
+ scram_exchange,
+
+ PG_MAX_SASL_MESSAGE_LENGTH
};
/*
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 4a8a63922a..17042d84ad 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -30,6 +30,7 @@
#include "libpq/auth.h"
#include "libpq/crypt.h"
#include "libpq/libpq.h"
+#include "libpq/oauth.h"
#include "libpq/pqformat.h"
#include "libpq/sasl.h"
#include "libpq/scram.h"
@@ -298,6 +299,9 @@ auth_failed(Port *port, int status, const char *logdetail)
case uaRADIUS:
errstr = gettext_noop("RADIUS authentication failed for user \"%s\"");
break;
+ case uaOAuth:
+ errstr = gettext_noop("OAuth bearer authentication failed for user \"%s\"");
+ break;
case uaCustom:
{
CustomAuthProvider *provider = get_provider_by_name(port->hba->custom_provider);
@@ -626,6 +630,9 @@ ClientAuthentication(Port *port)
case uaTrust:
status = STATUS_OK;
break;
+ case uaOAuth:
+ status = CheckSASLAuth(&pg_be_oauth_mech, port, NULL, NULL);
+ break;
case uaCustom:
{
CustomAuthProvider *provider = get_provider_by_name(port->hba->custom_provider);
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 42cb1ce51d..cd3b1cc140 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -136,7 +136,8 @@ static const char *const UserAuthName[] =
"cert",
"radius",
"custom",
- "peer"
+ "peer",
+ "oauth",
};
@@ -1401,6 +1402,8 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
#endif
else if (strcmp(token->string, "radius") == 0)
parsedline->auth_method = uaRADIUS;
+ else if (strcmp(token->string, "oauth") == 0)
+ parsedline->auth_method = uaOAuth;
else if (strcmp(token->string, "custom") == 0)
parsedline->auth_method = uaCustom;
else
@@ -1730,8 +1733,9 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
hbaline->auth_method != uaGSS &&
hbaline->auth_method != uaSSPI &&
hbaline->auth_method != uaCert &&
+ hbaline->auth_method != uaOAuth &&
hbaline->auth_method != uaCustom)
- INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, gssapi, sspi, cert and custom"));
+ INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, gssapi, sspi, cert, oauth, and custom"));
hbaline->usermap = pstrdup(val);
}
else if (strcmp(name, "clientcert") == 0)
@@ -2115,6 +2119,27 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
hbaline->radiusidentifiers = parsed_identifiers;
hbaline->radiusidentifiers_s = pstrdup(val);
}
+ else if (strcmp(name, "issuer") == 0)
+ {
+ if (hbaline->auth_method != uaOAuth)
+ INVALID_AUTH_OPTION("issuer", gettext_noop("oauth"));
+ hbaline->oauth_issuer = pstrdup(val);
+ }
+ else if (strcmp(name, "scope") == 0)
+ {
+ if (hbaline->auth_method != uaOAuth)
+ INVALID_AUTH_OPTION("scope", gettext_noop("oauth"));
+ hbaline->oauth_scope = pstrdup(val);
+ }
+ else if (strcmp(name, "trust_validator_authz") == 0)
+ {
+ if (hbaline->auth_method != uaOAuth)
+ INVALID_AUTH_OPTION("trust_validator_authz", gettext_noop("oauth"));
+ if (strcmp(val, "1") == 0)
+ hbaline->oauth_skip_usermap = true;
+ else
+ hbaline->oauth_skip_usermap = false;
+ }
else if (strcmp(name, "provider") == 0)
{
REQUIRE_AUTH_OPTION(uaCustom, "provider", "custom");
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index f70f7f5c01..9a5b2aa496 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -59,6 +59,7 @@
#include "libpq/auth.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
+#include "libpq/oauth.h"
#include "miscadmin.h"
#include "optimizer/cost.h"
#include "optimizer/geqo.h"
@@ -4666,6 +4667,17 @@ static struct config_string ConfigureNamesString[] =
check_backtrace_functions, assign_backtrace_functions, NULL
},
+ {
+ {"oauth_validator_command", PGC_SIGHUP, CONN_AUTH_AUTH,
+ gettext_noop("Command to validate OAuth v2 bearer tokens."),
+ NULL,
+ GUC_SUPERUSER_ONLY
+ },
+ &oauth_validator_command,
+ "",
+ NULL, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 31a00c4b71..e405103a2e 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -39,8 +39,9 @@ typedef enum UserAuth
uaCert,
uaRADIUS,
uaCustom,
- uaPeer
-#define USER_AUTH_LAST uaPeer /* Must be last value of this enum */
+ uaPeer,
+ uaOAuth
+#define USER_AUTH_LAST uaOAuth /* Must be last value of this enum */
} UserAuth;
/*
@@ -128,6 +129,9 @@ typedef struct HbaLine
char *radiusidentifiers_s;
List *radiusports;
char *radiusports_s;
+ char *oauth_issuer;
+ char *oauth_scope;
+ bool oauth_skip_usermap;
char *custom_provider;
List *custom_auth_options;
} HbaLine;
diff --git a/src/include/libpq/oauth.h b/src/include/libpq/oauth.h
new file mode 100644
index 0000000000..870e426af1
--- /dev/null
+++ b/src/include/libpq/oauth.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth.h
+ * Interface to libpq/auth-oauth.c
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/libpq/oauth.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_OAUTH_H
+#define PG_OAUTH_H
+
+#include "libpq/libpq-be.h"
+#include "libpq/sasl.h"
+
+extern char *oauth_validator_command;
+
+/* Implementation */
+extern const pg_be_sasl_mech pg_be_oauth_mech;
+
+#endif /* PG_OAUTH_H */
diff --git a/src/include/libpq/sasl.h b/src/include/libpq/sasl.h
index 39ccf8f0e3..f7d905591a 100644
--- a/src/include/libpq/sasl.h
+++ b/src/include/libpq/sasl.h
@@ -26,6 +26,14 @@
#define PG_SASL_EXCHANGE_SUCCESS 1
#define PG_SASL_EXCHANGE_FAILURE 2
+/*
+ * Maximum accepted size of SASL messages.
+ *
+ * The messages that the server or libpq generate are much smaller than this,
+ * but have some headroom.
+ */
+#define PG_MAX_SASL_MESSAGE_LENGTH 1024
+
/*
* Backend SASL mechanism callbacks.
*
@@ -127,6 +135,9 @@ typedef struct pg_be_sasl_mech
const char *input, int inputlen,
char **output, int *outputlen,
const char **logdetail);
+
+ /* The maximum size allowed for client SASLResponses. */
+ int max_message_length;
} pg_be_sasl_mech;
/* Common implementation for auth.c */
--
2.25.1
[text/x-patch] v4-0008-Add-a-very-simple-authn_id-extension.patch (2.8K, ../../[email protected]/9-v4-0008-Add-a-very-simple-authn_id-extension.patch)
download | inline diff:
From 9dd8e024fde29239829e822b8f2b82028044cd8b Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Tue, 18 May 2021 15:01:29 -0700
Subject: [PATCH v4 08/10] Add a very simple authn_id extension
...for retrieving the authn_id from the server in tests.
---
contrib/authn_id/Makefile | 19 +++++++++++++++++++
contrib/authn_id/authn_id--1.0.sql | 8 ++++++++
contrib/authn_id/authn_id.c | 28 ++++++++++++++++++++++++++++
contrib/authn_id/authn_id.control | 5 +++++
4 files changed, 60 insertions(+)
create mode 100644 contrib/authn_id/Makefile
create mode 100644 contrib/authn_id/authn_id--1.0.sql
create mode 100644 contrib/authn_id/authn_id.c
create mode 100644 contrib/authn_id/authn_id.control
diff --git a/contrib/authn_id/Makefile b/contrib/authn_id/Makefile
new file mode 100644
index 0000000000..46026358e0
--- /dev/null
+++ b/contrib/authn_id/Makefile
@@ -0,0 +1,19 @@
+# contrib/authn_id/Makefile
+
+MODULE_big = authn_id
+OBJS = authn_id.o
+
+EXTENSION = authn_id
+DATA = authn_id--1.0.sql
+PGFILEDESC = "authn_id - information about the authenticated user"
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/authn_id
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/authn_id/authn_id--1.0.sql b/contrib/authn_id/authn_id--1.0.sql
new file mode 100644
index 0000000000..af2a4d3991
--- /dev/null
+++ b/contrib/authn_id/authn_id--1.0.sql
@@ -0,0 +1,8 @@
+/* contrib/authn_id/authn_id--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION authn_id" to load this file. \quit
+
+CREATE FUNCTION authn_id() RETURNS text
+AS 'MODULE_PATHNAME', 'authn_id'
+LANGUAGE C IMMUTABLE;
diff --git a/contrib/authn_id/authn_id.c b/contrib/authn_id/authn_id.c
new file mode 100644
index 0000000000..0fecac36a8
--- /dev/null
+++ b/contrib/authn_id/authn_id.c
@@ -0,0 +1,28 @@
+/*
+ * Extension to expose the current user's authn_id.
+ *
+ * contrib/authn_id/authn_id.c
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "libpq/libpq-be.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(authn_id);
+
+/*
+ * Returns the current user's authenticated identity.
+ */
+Datum
+authn_id(PG_FUNCTION_ARGS)
+{
+ if (!MyProcPort->authn_id)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(MyProcPort->authn_id));
+}
diff --git a/contrib/authn_id/authn_id.control b/contrib/authn_id/authn_id.control
new file mode 100644
index 0000000000..e0f9e06bed
--- /dev/null
+++ b/contrib/authn_id/authn_id.control
@@ -0,0 +1,5 @@
+# authn_id extension
+comment = 'current user identity'
+default_version = '1.0'
+module_pathname = '$libdir/authn_id'
+relocatable = true
--
2.25.1
[text/x-patch] v4-0009-Add-pytest-suite-for-OAuth.patch (131.6K, ../../[email protected]/10-v4-0009-Add-pytest-suite-for-OAuth.patch)
download | inline diff:
From 6d8fd9e5b352fd0847c9454ced2b763a6b11e73f Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Fri, 4 Jun 2021 09:06:38 -0700
Subject: [PATCH v4 09/10] Add pytest suite for OAuth
Requires Python 3; on the first run of `make installcheck` the
dependencies will be installed into ./venv for you. See the README for
more details.
---
src/test/python/.gitignore | 2 +
src/test/python/Makefile | 38 +
src/test/python/README | 54 ++
src/test/python/client/__init__.py | 0
src/test/python/client/conftest.py | 126 +++
src/test/python/client/test_client.py | 180 ++++
src/test/python/client/test_oauth.py | 936 ++++++++++++++++++
src/test/python/pq3.py | 727 ++++++++++++++
src/test/python/pytest.ini | 4 +
src/test/python/requirements.txt | 7 +
src/test/python/server/__init__.py | 0
src/test/python/server/conftest.py | 45 +
src/test/python/server/test_oauth.py | 1012 ++++++++++++++++++++
src/test/python/server/test_server.py | 21 +
src/test/python/server/validate_bearer.py | 101 ++
src/test/python/server/validate_reflect.py | 34 +
src/test/python/test_internals.py | 138 +++
src/test/python/test_pq3.py | 558 +++++++++++
src/test/python/tls.py | 195 ++++
19 files changed, 4178 insertions(+)
create mode 100644 src/test/python/.gitignore
create mode 100644 src/test/python/Makefile
create mode 100644 src/test/python/README
create mode 100644 src/test/python/client/__init__.py
create mode 100644 src/test/python/client/conftest.py
create mode 100644 src/test/python/client/test_client.py
create mode 100644 src/test/python/client/test_oauth.py
create mode 100644 src/test/python/pq3.py
create mode 100644 src/test/python/pytest.ini
create mode 100644 src/test/python/requirements.txt
create mode 100644 src/test/python/server/__init__.py
create mode 100644 src/test/python/server/conftest.py
create mode 100644 src/test/python/server/test_oauth.py
create mode 100644 src/test/python/server/test_server.py
create mode 100755 src/test/python/server/validate_bearer.py
create mode 100755 src/test/python/server/validate_reflect.py
create mode 100644 src/test/python/test_internals.py
create mode 100644 src/test/python/test_pq3.py
create mode 100644 src/test/python/tls.py
diff --git a/src/test/python/.gitignore b/src/test/python/.gitignore
new file mode 100644
index 0000000000..0e8f027b2e
--- /dev/null
+++ b/src/test/python/.gitignore
@@ -0,0 +1,2 @@
+__pycache__/
+/venv/
diff --git a/src/test/python/Makefile b/src/test/python/Makefile
new file mode 100644
index 0000000000..b0695b6287
--- /dev/null
+++ b/src/test/python/Makefile
@@ -0,0 +1,38 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+# Only Python 3 is supported, but if it's named something different on your
+# system you can override it with the PYTHON3 variable.
+PYTHON3 := python3
+
+# All dependencies are placed into this directory. The default is .gitignored
+# for you, but you can override it if you'd like.
+VENV := ./venv
+
+override VBIN := $(VENV)/bin
+override PIP := $(VBIN)/pip
+override PYTEST := $(VBIN)/py.test
+override ISORT := $(VBIN)/isort
+override BLACK := $(VBIN)/black
+
+.PHONY: installcheck indent
+
+installcheck: $(PYTEST)
+ $(PYTEST) -v -rs
+
+indent: $(ISORT) $(BLACK)
+ $(ISORT) --profile black *.py client/*.py server/*.py
+ $(BLACK) *.py client/*.py server/*.py
+
+$(PYTEST) $(ISORT) $(BLACK) &: requirements.txt | $(PIP)
+ $(PIP) install --force-reinstall -r $<
+
+$(PIP):
+ $(PYTHON3) -m venv $(VENV)
+
+# A convenience recipe to rebuild psycopg2 against the local libpq.
+.PHONY: rebuild-psycopg2
+rebuild-psycopg2: | $(PIP)
+ $(PIP) install --force-reinstall --no-binary :all: $(shell grep psycopg2 requirements.txt)
diff --git a/src/test/python/README b/src/test/python/README
new file mode 100644
index 0000000000..0bda582c4b
--- /dev/null
+++ b/src/test/python/README
@@ -0,0 +1,54 @@
+A test suite for exercising both the libpq client and the server backend at the
+protocol level, based on pytest and Construct.
+
+The test suite currently assumes that the standard PG* environment variables
+point to the database under test and are sufficient to log in a superuser on
+that system. In other words, a bare `psql` needs to Just Work before the test
+suite can do its thing. For a newly built dev cluster, typically all that I need
+to do is a
+
+ export PGDATABASE=postgres
+
+but you can adjust as needed for your setup.
+
+## Requirements
+
+A supported version (3.6+) of Python.
+
+The first run of
+
+ make installcheck
+
+will install a local virtual environment and all needed dependencies. During
+development, if libpq changes incompatibly, you can issue
+
+ $ make rebuild-psycopg2
+
+to force a rebuild of the client library.
+
+## Hacking
+
+The code style is enforced by a _very_ opinionated autoformatter. Running the
+
+ make indent
+
+recipe will invoke it for you automatically. Don't fight the tool; part of the
+zen is in knowing that if the formatter makes your code ugly, there's probably a
+cleaner way to write your code.
+
+## Advanced Usage
+
+The Makefile is there for convenience, but you don't have to use it. Activate
+the virtualenv to be able to use pytest directly:
+
+ $ source venv/bin/activate
+ $ py.test -k oauth
+ ...
+ $ py.test ./server/test_server.py
+ ...
+ $ deactivate # puts the PATH et al back the way it was before
+
+To make quick smoke tests possible, slow tests have been marked explicitly. You
+can skip them by saying e.g.
+
+ $ py.test -m 'not slow'
diff --git a/src/test/python/client/__init__.py b/src/test/python/client/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/test/python/client/conftest.py b/src/test/python/client/conftest.py
new file mode 100644
index 0000000000..f38da7a138
--- /dev/null
+++ b/src/test/python/client/conftest.py
@@ -0,0 +1,126 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import socket
+import sys
+import threading
+
+import psycopg2
+import pytest
+
+import pq3
+
+BLOCKING_TIMEOUT = 2 # the number of seconds to wait for blocking calls
+
+
[email protected]
+def server_socket(unused_tcp_port_factory):
+ """
+ Returns a listening socket bound to an ephemeral port.
+ """
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
+ s.bind(("127.0.0.1", unused_tcp_port_factory()))
+ s.listen(1)
+ s.settimeout(BLOCKING_TIMEOUT)
+ yield s
+
+
+class ClientHandshake(threading.Thread):
+ """
+ A thread that connects to a local Postgres server using psycopg2. Once the
+ opening handshake completes, the connection will be immediately closed.
+ """
+
+ def __init__(self, *, port, **kwargs):
+ super().__init__()
+
+ kwargs["port"] = port
+ self._kwargs = kwargs
+
+ self.exception = None
+
+ def run(self):
+ try:
+ conn = psycopg2.connect(host="127.0.0.1", **self._kwargs)
+ conn.close()
+ except Exception as e:
+ self.exception = e
+
+ def check_completed(self, timeout=BLOCKING_TIMEOUT):
+ """
+ Joins the client thread. Raises an exception if the thread could not be
+ joined, or if it threw an exception itself. (The exception will be
+ cleared, so future calls to check_completed will succeed.)
+ """
+ self.join(timeout)
+
+ if self.is_alive():
+ raise TimeoutError("client thread did not handshake within the timeout")
+ elif self.exception:
+ e = self.exception
+ self.exception = None
+ raise e
+
+
[email protected]
+def accept(server_socket):
+ """
+ Returns a factory function that, when called, returns a pair (sock, client)
+ where sock is a server socket that has accepted a connection from client,
+ and client is an instance of ClientHandshake. Clients will complete their
+ handshakes and cleanly disconnect.
+
+ The default connstring options may be extended or overridden by passing
+ arbitrary keyword arguments. Keep in mind that you generally should not
+ override the host or port, since they point to the local test server.
+
+ For situations where a client needs to connect more than once to complete a
+ handshake, the accept function may be called more than once. (The client
+ returned for subsequent calls will always be the same client that was
+ returned for the first call.)
+
+ Tests must either complete the handshake so that the client thread can be
+ automatically joined during teardown, or else call client.check_completed()
+ and manually handle any expected errors.
+ """
+ _, port = server_socket.getsockname()
+
+ client = None
+ default_opts = dict(
+ port=port,
+ user=pq3.pguser(),
+ sslmode="disable",
+ )
+
+ def factory(**kwargs):
+ nonlocal client
+
+ if client is None:
+ opts = dict(default_opts)
+ opts.update(kwargs)
+
+ # The server_socket is already listening, so the client thread can
+ # be safely started; it'll block on the connection until we accept.
+ client = ClientHandshake(**opts)
+ client.start()
+
+ sock, _ = server_socket.accept()
+ return sock, client
+
+ yield factory
+ client.check_completed()
+
+
[email protected]
+def conn(accept):
+ """
+ Returns an accepted, wrapped pq3 connection to a psycopg2 client. The socket
+ will be closed when the test finishes, and the client will be checked for a
+ cleanly completed handshake.
+ """
+ sock, client = accept()
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ yield conn
diff --git a/src/test/python/client/test_client.py b/src/test/python/client/test_client.py
new file mode 100644
index 0000000000..c4c946fda4
--- /dev/null
+++ b/src/test/python/client/test_client.py
@@ -0,0 +1,180 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import base64
+import sys
+
+import psycopg2
+import pytest
+from cryptography.hazmat.primitives import hashes, hmac
+
+import pq3
+
+
+def finish_handshake(conn):
+ """
+ Sends the AuthenticationOK message and the standard opening salvo of server
+ messages, then asserts that the client immediately sends a Terminate message
+ to close the connection cleanly.
+ """
+ pq3.send(conn, pq3.types.AuthnRequest, type=pq3.authn.OK)
+ pq3.send(conn, pq3.types.ParameterStatus, name=b"client_encoding", value=b"UTF-8")
+ pq3.send(conn, pq3.types.ParameterStatus, name=b"DateStyle", value=b"ISO, MDY")
+ pq3.send(conn, pq3.types.ReadyForQuery, status=b"I")
+
+ pkt = pq3.recv1(conn)
+ assert pkt.type == pq3.types.Terminate
+
+
+def test_handshake(conn):
+ startup = pq3.recv1(conn, cls=pq3.Startup)
+ assert startup.proto == pq3.protocol(3, 0)
+
+ finish_handshake(conn)
+
+
+def test_aborted_connection(accept):
+ """
+ Make sure the client correctly reports an early close during handshakes.
+ """
+ sock, client = accept()
+ sock.close()
+
+ expected = "server closed the connection unexpectedly"
+ with pytest.raises(psycopg2.OperationalError, match=expected):
+ client.check_completed()
+
+
+#
+# SCRAM-SHA-256 (see RFC 5802: https://tools.ietf.org/html/rfc5802)
+#
+
+
[email protected]
+def password():
+ """
+ Returns a password for use by both client and server.
+ """
+ # TODO: parameterize this with passwords that require SASLprep.
+ return "secret"
+
+
[email protected]
+def pwconn(accept, password):
+ """
+ Like the conn fixture, but uses a password in the connection.
+ """
+ sock, client = accept(password=password)
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ yield conn
+
+
+def sha256(data):
+ """The H(str) function from Section 2.2."""
+ digest = hashes.Hash(hashes.SHA256())
+ digest.update(data)
+ return digest.finalize()
+
+
+def hmac_256(key, data):
+ """The HMAC(key, str) function from Section 2.2."""
+ h = hmac.HMAC(key, hashes.SHA256())
+ h.update(data)
+ return h.finalize()
+
+
+def xor(a, b):
+ """The XOR operation from Section 2.2."""
+ res = bytearray(a)
+ for i, byte in enumerate(b):
+ res[i] ^= byte
+ return bytes(res)
+
+
+def h_i(data, salt, i):
+ """The Hi(str, salt, i) function from Section 2.2."""
+ assert i > 0
+
+ acc = hmac_256(data, salt + b"\x00\x00\x00\x01")
+ last = acc
+ i -= 1
+
+ while i:
+ u = hmac_256(data, last)
+ acc = xor(acc, u)
+
+ last = u
+ i -= 1
+
+ return acc
+
+
+def test_scram(pwconn, password):
+ startup = pq3.recv1(pwconn, cls=pq3.Startup)
+ assert startup.proto == pq3.protocol(3, 0)
+
+ pq3.send(
+ pwconn,
+ pq3.types.AuthnRequest,
+ type=pq3.authn.SASL,
+ body=[b"SCRAM-SHA-256", b""],
+ )
+
+ # Get the client-first-message.
+ pkt = pq3.recv1(pwconn)
+ assert pkt.type == pq3.types.PasswordMessage
+
+ initial = pq3.SASLInitialResponse.parse(pkt.payload)
+ assert initial.name == b"SCRAM-SHA-256"
+
+ c_bind, authzid, c_name, c_nonce = initial.data.split(b",")
+ assert c_bind == b"n" # no channel bindings on a plaintext connection
+ assert authzid == b"" # we don't support authzid currently
+ assert c_name == b"n=" # libpq doesn't honor the GS2 username
+ assert c_nonce.startswith(b"r=")
+
+ # Send the server-first-message.
+ salt = b"12345"
+ iterations = 2
+
+ s_nonce = c_nonce + b"somenonce"
+ s_salt = b"s=" + base64.b64encode(salt)
+ s_iterations = b"i=%d" % iterations
+
+ msg = b",".join([s_nonce, s_salt, s_iterations])
+ pq3.send(pwconn, pq3.types.AuthnRequest, type=pq3.authn.SASLContinue, body=msg)
+
+ # Get the client-final-message.
+ pkt = pq3.recv1(pwconn)
+ assert pkt.type == pq3.types.PasswordMessage
+
+ c_bind_final, c_nonce_final, c_proof = pkt.payload.split(b",")
+ assert c_bind_final == b"c=" + base64.b64encode(c_bind + b"," + authzid + b",")
+ assert c_nonce_final == s_nonce
+
+ # Calculate what the client proof should be.
+ salted_password = h_i(password.encode("ascii"), salt, iterations)
+ client_key = hmac_256(salted_password, b"Client Key")
+ stored_key = sha256(client_key)
+
+ auth_message = b",".join(
+ [c_name, c_nonce, s_nonce, s_salt, s_iterations, c_bind_final, c_nonce_final]
+ )
+ client_signature = hmac_256(stored_key, auth_message)
+ client_proof = xor(client_key, client_signature)
+
+ expected = b"p=" + base64.b64encode(client_proof)
+ assert c_proof == expected
+
+ # Send the correct server signature.
+ server_key = hmac_256(salted_password, b"Server Key")
+ server_signature = hmac_256(server_key, auth_message)
+
+ s_verify = b"v=" + base64.b64encode(server_signature)
+ pq3.send(pwconn, pq3.types.AuthnRequest, type=pq3.authn.SASLFinal, body=s_verify)
+
+ # Done!
+ finish_handshake(pwconn)
diff --git a/src/test/python/client/test_oauth.py b/src/test/python/client/test_oauth.py
new file mode 100644
index 0000000000..a754a9c0b6
--- /dev/null
+++ b/src/test/python/client/test_oauth.py
@@ -0,0 +1,936 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import base64
+import http.server
+import json
+import secrets
+import sys
+import threading
+import time
+import urllib.parse
+
+import psycopg2
+import pytest
+
+import pq3
+
+from .conftest import BLOCKING_TIMEOUT
+
+
+def finish_handshake(conn):
+ """
+ Sends the AuthenticationOK message and the standard opening salvo of server
+ messages, then asserts that the client immediately sends a Terminate message
+ to close the connection cleanly.
+ """
+ pq3.send(conn, pq3.types.AuthnRequest, type=pq3.authn.OK)
+ pq3.send(conn, pq3.types.ParameterStatus, name=b"client_encoding", value=b"UTF-8")
+ pq3.send(conn, pq3.types.ParameterStatus, name=b"DateStyle", value=b"ISO, MDY")
+ pq3.send(conn, pq3.types.ReadyForQuery, status=b"I")
+
+ pkt = pq3.recv1(conn)
+ assert pkt.type == pq3.types.Terminate
+
+
+#
+# OAUTHBEARER (see RFC 7628: https://tools.ietf.org/html/rfc7628)
+#
+
+
+def start_oauth_handshake(conn):
+ """
+ Negotiates an OAUTHBEARER SASL challenge. Returns the client's initial
+ response data.
+ """
+ startup = pq3.recv1(conn, cls=pq3.Startup)
+ assert startup.proto == pq3.protocol(3, 0)
+
+ pq3.send(
+ conn, pq3.types.AuthnRequest, type=pq3.authn.SASL, body=[b"OAUTHBEARER", b""]
+ )
+
+ pkt = pq3.recv1(conn)
+ assert pkt.type == pq3.types.PasswordMessage
+
+ initial = pq3.SASLInitialResponse.parse(pkt.payload)
+ assert initial.name == b"OAUTHBEARER"
+
+ return initial.data
+
+
+def get_auth_value(initial):
+ """
+ Finds the auth value (e.g. "Bearer somedata..." in the client's initial SASL
+ response.
+ """
+ kvpairs = initial.split(b"\x01")
+ assert kvpairs[0] == b"n,," # no channel binding or authzid
+ assert kvpairs[2] == b"" # ends with an empty kvpair
+ assert kvpairs[3] == b"" # ...and there's nothing after it
+ assert len(kvpairs) == 4
+
+ key, value = kvpairs[1].split(b"=", 2)
+ assert key == b"auth"
+
+ return value
+
+
+def xtest_oauth_success(conn): # TODO
+ initial = start_oauth_handshake(conn)
+
+ auth = get_auth_value(initial)
+ assert auth.startswith(b"Bearer ")
+
+ # Accept the token. TODO actually validate
+ pq3.send(conn, pq3.types.AuthnRequest, type=pq3.authn.SASLFinal)
+ finish_handshake(conn)
+
+
+class OpenIDProvider(threading.Thread):
+ """
+ A thread that runs a mock OpenID provider server.
+ """
+
+ def __init__(self, *, port):
+ super().__init__()
+
+ self.exception = None
+
+ addr = ("", port)
+ self.server = self._Server(addr, self._Handler)
+
+ # TODO: allow HTTPS only, somehow
+ oauth = self._OAuthState()
+ oauth.host = f"localhost:{port}"
+ oauth.issuer = f"http://localhost:{port}"
+
+ # The following endpoints are required to be advertised by providers,
+ # even though our chosen client implementation does not actually make
+ # use of them.
+ oauth.register_endpoint(
+ "authorization_endpoint", "POST", "/authorize", self._authorization_handler
+ )
+ oauth.register_endpoint("jwks_uri", "GET", "/keys", self._jwks_handler)
+
+ self.server.oauth = oauth
+
+ def run(self):
+ try:
+ self.server.serve_forever()
+ except Exception as e:
+ self.exception = e
+
+ def stop(self, timeout=BLOCKING_TIMEOUT):
+ """
+ Shuts down the server and joins its thread. Raises an exception if the
+ thread could not be joined, or if it threw an exception itself. Must
+ only be called once, after start().
+ """
+ self.server.shutdown()
+ self.join(timeout)
+
+ if self.is_alive():
+ raise TimeoutError("client thread did not handshake within the timeout")
+ elif self.exception:
+ e = self.exception
+ raise e
+
+ class _OAuthState(object):
+ def __init__(self):
+ self.endpoint_paths = {}
+ self._endpoints = {}
+
+ def register_endpoint(self, name, method, path, func):
+ if method not in self._endpoints:
+ self._endpoints[method] = {}
+
+ self._endpoints[method][path] = func
+ self.endpoint_paths[name] = path
+
+ def endpoint(self, method, path):
+ if method not in self._endpoints:
+ return None
+
+ return self._endpoints[method].get(path)
+
+ class _Server(http.server.HTTPServer):
+ def handle_error(self, request, addr):
+ self.shutdown_request(request)
+ raise
+
+ @staticmethod
+ def _jwks_handler(headers, params):
+ return 200, {"keys": []}
+
+ @staticmethod
+ def _authorization_handler(headers, params):
+ # We don't actually want this to be called during these tests -- we
+ # should be using the device authorization endpoint instead.
+ assert (
+ False
+ ), "authorization handler called instead of device authorization handler"
+
+ class _Handler(http.server.BaseHTTPRequestHandler):
+ timeout = BLOCKING_TIMEOUT
+
+ def _discovery_handler(self, headers, params):
+ oauth = self.server.oauth
+
+ doc = {
+ "issuer": oauth.issuer,
+ "response_types_supported": ["token"],
+ "subject_types_supported": ["public"],
+ "id_token_signing_alg_values_supported": ["RS256"],
+ }
+
+ for name, path in oauth.endpoint_paths.items():
+ doc[name] = oauth.issuer + path
+
+ return 200, doc
+
+ def _handle(self, *, params=None, handler=None):
+ oauth = self.server.oauth
+ assert self.headers["Host"] == oauth.host
+
+ if handler is None:
+ handler = oauth.endpoint(self.command, self.path)
+ assert (
+ handler is not None
+ ), f"no registered endpoint for {self.command} {self.path}"
+
+ code, resp = handler(self.headers, params)
+
+ self.send_response(code)
+ self.send_header("Content-Type", "application/json")
+ self.end_headers()
+
+ resp = json.dumps(resp)
+ resp = resp.encode("utf-8")
+ self.wfile.write(resp)
+
+ self.close_connection = True
+
+ def do_GET(self):
+ if self.path == "/.well-known/openid-configuration":
+ self._handle(handler=self._discovery_handler)
+ return
+
+ self._handle()
+
+ def _request_body(self):
+ length = self.headers["Content-Length"]
+
+ # Handle only an explicit content-length.
+ assert length is not None
+ length = int(length)
+
+ return self.rfile.read(length).decode("utf-8")
+
+ def do_POST(self):
+ assert self.headers["Content-Type"] == "application/x-www-form-urlencoded"
+
+ body = self._request_body()
+ params = urllib.parse.parse_qs(body)
+
+ self._handle(params=params)
+
+
[email protected]
+def openid_provider(unused_tcp_port_factory):
+ """
+ A fixture that returns the OAuth state of a running OpenID provider server. The
+ server will be stopped when the fixture is torn down.
+ """
+ thread = OpenIDProvider(port=unused_tcp_port_factory())
+ thread.start()
+
+ try:
+ yield thread.server.oauth
+ finally:
+ thread.stop()
+
+
[email protected]("secret", [None, "", "hunter2"])
[email protected]("scope", [None, "", "openid email"])
[email protected]("retries", [0, 1])
+def test_oauth_with_explicit_issuer(
+ capfd, accept, openid_provider, retries, scope, secret
+):
+ client_id = secrets.token_hex()
+
+ sock, client = accept(
+ oauth_issuer=openid_provider.issuer,
+ oauth_client_id=client_id,
+ oauth_client_secret=secret,
+ oauth_scope=scope,
+ )
+
+ device_code = secrets.token_hex()
+ user_code = f"{secrets.token_hex(2)}-{secrets.token_hex(2)}"
+ verification_url = "https://example.com/device"
+
+ access_token = secrets.token_urlsafe()
+
+ def check_client_authn(headers, params):
+ if not secret:
+ assert params["client_id"] == [client_id]
+ return
+
+ # Require the client to use Basic authn; request-body credentials are
+ # NOT RECOMMENDED (RFC 6749, Sec. 2.3.1).
+ assert "Authorization" in headers
+
+ method, creds = headers["Authorization"].split()
+ assert method == "Basic"
+
+ expected = f"{client_id}:{secret}"
+ assert base64.b64decode(creds) == expected.encode("ascii")
+
+ # Set up our provider callbacks.
+ # NOTE that these callbacks will be called on a background thread. Don't do
+ # any unprotected state mutation here.
+
+ def authorization_endpoint(headers, params):
+ check_client_authn(headers, params)
+
+ if scope:
+ assert params["scope"] == [scope]
+ else:
+ assert "scope" not in params
+
+ resp = {
+ "device_code": device_code,
+ "user_code": user_code,
+ "interval": 0,
+ "verification_uri": verification_url,
+ "expires_in": 5,
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "device_authorization_endpoint", "POST", "/device", authorization_endpoint
+ )
+
+ attempts = 0
+ retry_lock = threading.Lock()
+
+ def token_endpoint(headers, params):
+ check_client_authn(headers, params)
+
+ assert params["grant_type"] == ["urn:ietf:params:oauth:grant-type:device_code"]
+ assert params["device_code"] == [device_code]
+
+ now = time.monotonic()
+
+ with retry_lock:
+ nonlocal attempts
+
+ # If the test wants to force the client to retry, return an
+ # authorization_pending response and decrement the retry count.
+ if attempts < retries:
+ attempts += 1
+ return 400, {"error": "authorization_pending"}
+
+ # Successfully finish the request by sending the access bearer token.
+ resp = {
+ "access_token": access_token,
+ "token_type": "bearer",
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "token_endpoint", "POST", "/token", token_endpoint
+ )
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ # Initiate a handshake, which should result in the above endpoints
+ # being called.
+ initial = start_oauth_handshake(conn)
+
+ # Validate and accept the token.
+ auth = get_auth_value(initial)
+ assert auth == f"Bearer {access_token}".encode("ascii")
+
+ pq3.send(conn, pq3.types.AuthnRequest, type=pq3.authn.SASLFinal)
+ finish_handshake(conn)
+
+ if retries:
+ # Finally, make sure that the client prompted the user with the expected
+ # authorization URL and user code.
+ expected = f"Visit {verification_url} and enter the code: {user_code}"
+ _, stderr = capfd.readouterr()
+ assert expected in stderr
+
+
+def test_oauth_requires_client_id(accept, openid_provider):
+ sock, client = accept(
+ oauth_issuer=openid_provider.issuer,
+ # Do not set a client ID; this should cause a client error after the
+ # server asks for OAUTHBEARER and the client tries to contact the
+ # issuer.
+ )
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ # Initiate a handshake.
+ startup = pq3.recv1(conn, cls=pq3.Startup)
+ assert startup.proto == pq3.protocol(3, 0)
+
+ pq3.send(
+ conn,
+ pq3.types.AuthnRequest,
+ type=pq3.authn.SASL,
+ body=[b"OAUTHBEARER", b""],
+ )
+
+ # The client should disconnect at this point.
+ assert not conn.read()
+
+ expected_error = "no oauth_client_id is set"
+ with pytest.raises(psycopg2.OperationalError, match=expected_error):
+ client.check_completed()
+
+
[email protected]
[email protected]("error_code", ["authorization_pending", "slow_down"])
[email protected]("retries", [1, 2])
+def test_oauth_retry_interval(accept, openid_provider, retries, error_code):
+ sock, client = accept(
+ oauth_issuer=openid_provider.issuer,
+ oauth_client_id="some-id",
+ )
+
+ expected_retry_interval = 1
+ access_token = secrets.token_urlsafe()
+
+ # Set up our provider callbacks.
+ # NOTE that these callbacks will be called on a background thread. Don't do
+ # any unprotected state mutation here.
+
+ def authorization_endpoint(headers, params):
+ resp = {
+ "device_code": "my-device-code",
+ "user_code": "my-user-code",
+ "interval": expected_retry_interval,
+ "verification_uri": "https://example.com",
+ "expires_in": 5,
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "device_authorization_endpoint", "POST", "/device", authorization_endpoint
+ )
+
+ attempts = 0
+ last_retry = None
+ retry_lock = threading.Lock()
+
+ def token_endpoint(headers, params):
+ now = time.monotonic()
+
+ with retry_lock:
+ nonlocal attempts, last_retry, expected_retry_interval
+
+ # Make sure the retry interval is being respected by the client.
+ if last_retry is not None:
+ interval = now - last_retry
+ assert interval >= expected_retry_interval
+
+ last_retry = now
+
+ # If the test wants to force the client to retry, return the desired
+ # error response and decrement the retry count.
+ if attempts < retries:
+ attempts += 1
+
+ # A slow_down code requires the client to additionally increase
+ # its interval by five seconds.
+ if error_code == "slow_down":
+ expected_retry_interval += 5
+
+ return 400, {"error": error_code}
+
+ # Successfully finish the request by sending the access bearer token.
+ resp = {
+ "access_token": access_token,
+ "token_type": "bearer",
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "token_endpoint", "POST", "/token", token_endpoint
+ )
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ # Initiate a handshake, which should result in the above endpoints
+ # being called.
+ initial = start_oauth_handshake(conn)
+
+ # Validate and accept the token.
+ auth = get_auth_value(initial)
+ assert auth == f"Bearer {access_token}".encode("ascii")
+
+ pq3.send(conn, pq3.types.AuthnRequest, type=pq3.authn.SASLFinal)
+ finish_handshake(conn)
+
+
[email protected](
+ "failure_mode, error_pattern",
+ [
+ pytest.param(
+ {
+ "error": "invalid_client",
+ "error_description": "client authentication failed",
+ },
+ r"client authentication failed \(invalid_client\)",
+ id="authentication failure with description",
+ ),
+ pytest.param(
+ {"error": "invalid_request"},
+ r"\(invalid_request\)",
+ id="invalid request without description",
+ ),
+ pytest.param(
+ {},
+ r"failed to obtain device authorization",
+ id="broken error response",
+ ),
+ ],
+)
+def test_oauth_device_authorization_failures(
+ accept, openid_provider, failure_mode, error_pattern
+):
+ client_id = secrets.token_hex()
+
+ sock, client = accept(
+ oauth_issuer=openid_provider.issuer,
+ oauth_client_id=client_id,
+ )
+
+ # Set up our provider callbacks.
+ # NOTE that these callbacks will be called on a background thread. Don't do
+ # any unprotected state mutation here.
+
+ def authorization_endpoint(headers, params):
+ return 400, failure_mode
+
+ openid_provider.register_endpoint(
+ "device_authorization_endpoint", "POST", "/device", authorization_endpoint
+ )
+
+ def token_endpoint(headers, params):
+ assert False, "token endpoint was invoked unexpectedly"
+
+ openid_provider.register_endpoint(
+ "token_endpoint", "POST", "/token", token_endpoint
+ )
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ # Initiate a handshake, which should result in the above endpoints
+ # being called.
+ startup = pq3.recv1(conn, cls=pq3.Startup)
+ assert startup.proto == pq3.protocol(3, 0)
+
+ pq3.send(
+ conn,
+ pq3.types.AuthnRequest,
+ type=pq3.authn.SASL,
+ body=[b"OAUTHBEARER", b""],
+ )
+
+ # The client should not continue the connection due to the hardcoded
+ # provider failure; we disconnect here.
+
+ # Now make sure the client correctly failed.
+ with pytest.raises(psycopg2.OperationalError, match=error_pattern):
+ client.check_completed()
+
+
[email protected](
+ "failure_mode, error_pattern",
+ [
+ pytest.param(
+ {
+ "error": "expired_token",
+ "error_description": "the device code has expired",
+ },
+ r"the device code has expired \(expired_token\)",
+ id="expired token with description",
+ ),
+ pytest.param(
+ {"error": "access_denied"},
+ r"\(access_denied\)",
+ id="access denied without description",
+ ),
+ pytest.param(
+ {},
+ r"OAuth token retrieval failed",
+ id="broken error response",
+ ),
+ ],
+)
[email protected]("retries", [0, 1])
+def test_oauth_token_failures(
+ accept, openid_provider, retries, failure_mode, error_pattern
+):
+ client_id = secrets.token_hex()
+
+ sock, client = accept(
+ oauth_issuer=openid_provider.issuer,
+ oauth_client_id=client_id,
+ )
+
+ device_code = secrets.token_hex()
+ user_code = f"{secrets.token_hex(2)}-{secrets.token_hex(2)}"
+
+ # Set up our provider callbacks.
+ # NOTE that these callbacks will be called on a background thread. Don't do
+ # any unprotected state mutation here.
+
+ def authorization_endpoint(headers, params):
+ assert params["client_id"] == [client_id]
+
+ resp = {
+ "device_code": device_code,
+ "user_code": user_code,
+ "interval": 0,
+ "verification_uri": "https://example.com/device",
+ "expires_in": 5,
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "device_authorization_endpoint", "POST", "/device", authorization_endpoint
+ )
+
+ retry_lock = threading.Lock()
+
+ def token_endpoint(headers, params):
+ with retry_lock:
+ nonlocal retries
+
+ # If the test wants to force the client to retry, return an
+ # authorization_pending response and decrement the retry count.
+ if retries > 0:
+ retries -= 1
+ return 400, {"error": "authorization_pending"}
+
+ return 400, failure_mode
+
+ openid_provider.register_endpoint(
+ "token_endpoint", "POST", "/token", token_endpoint
+ )
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ # Initiate a handshake, which should result in the above endpoints
+ # being called.
+ startup = pq3.recv1(conn, cls=pq3.Startup)
+ assert startup.proto == pq3.protocol(3, 0)
+
+ pq3.send(
+ conn,
+ pq3.types.AuthnRequest,
+ type=pq3.authn.SASL,
+ body=[b"OAUTHBEARER", b""],
+ )
+
+ # The client should not continue the connection due to the hardcoded
+ # provider failure; we disconnect here.
+
+ # Now make sure the client correctly failed.
+ with pytest.raises(psycopg2.OperationalError, match=error_pattern):
+ client.check_completed()
+
+
[email protected]("scope", [None, "openid email"])
[email protected](
+ "base_response",
+ [
+ {"status": "invalid_token"},
+ {"extra_object": {"key": "value"}, "status": "invalid_token"},
+ {"extra_object": {"status": 1}, "status": "invalid_token"},
+ ],
+)
+def test_oauth_discovery(accept, openid_provider, base_response, scope):
+ sock, client = accept(oauth_client_id=secrets.token_hex())
+
+ device_code = secrets.token_hex()
+ user_code = f"{secrets.token_hex(2)}-{secrets.token_hex(2)}"
+ verification_url = "https://example.com/device"
+
+ access_token = secrets.token_urlsafe()
+
+ # Set up our provider callbacks.
+ # NOTE that these callbacks will be called on a background thread. Don't do
+ # any unprotected state mutation here.
+
+ def authorization_endpoint(headers, params):
+ if scope:
+ assert params["scope"] == [scope]
+ else:
+ assert "scope" not in params
+
+ resp = {
+ "device_code": device_code,
+ "user_code": user_code,
+ "interval": 0,
+ "verification_uri": verification_url,
+ "expires_in": 5,
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "device_authorization_endpoint", "POST", "/device", authorization_endpoint
+ )
+
+ def token_endpoint(headers, params):
+ assert params["grant_type"] == ["urn:ietf:params:oauth:grant-type:device_code"]
+ assert params["device_code"] == [device_code]
+
+ # Successfully finish the request by sending the access bearer token.
+ resp = {
+ "access_token": access_token,
+ "token_type": "bearer",
+ }
+
+ return 200, resp
+
+ openid_provider.register_endpoint(
+ "token_endpoint", "POST", "/token", token_endpoint
+ )
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ initial = start_oauth_handshake(conn)
+
+ # For discovery, the client should send an empty auth header. See
+ # RFC 7628, Sec. 4.3.
+ auth = get_auth_value(initial)
+ assert auth == b""
+
+ # We will fail the first SASL exchange. First return a link to the
+ # discovery document, pointing to the test provider server.
+ resp = dict(base_response)
+
+ discovery_uri = f"{openid_provider.issuer}/.well-known/openid-configuration"
+ resp["openid-configuration"] = discovery_uri
+
+ if scope:
+ resp["scope"] = scope
+
+ resp = json.dumps(resp)
+
+ pq3.send(
+ conn,
+ pq3.types.AuthnRequest,
+ type=pq3.authn.SASLContinue,
+ body=resp.encode("ascii"),
+ )
+
+ # Per RFC, the client is required to send a dummy ^A response.
+ pkt = pq3.recv1(conn)
+ assert pkt.type == pq3.types.PasswordMessage
+ assert pkt.payload == b"\x01"
+
+ # Now fail the SASL exchange.
+ pq3.send(
+ conn,
+ pq3.types.ErrorResponse,
+ fields=[
+ b"SFATAL",
+ b"C28000",
+ b"Mdoesn't matter",
+ b"",
+ ],
+ )
+
+ # The client will connect to us a second time, using the parameters we sent
+ # it.
+ sock, _ = accept()
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ initial = start_oauth_handshake(conn)
+
+ # Validate and accept the token.
+ auth = get_auth_value(initial)
+ assert auth == f"Bearer {access_token}".encode("ascii")
+
+ pq3.send(conn, pq3.types.AuthnRequest, type=pq3.authn.SASLFinal)
+ finish_handshake(conn)
+
+
[email protected](
+ "response,expected_error",
+ [
+ pytest.param(
+ "abcde",
+ 'Token "abcde" is invalid',
+ id="bad JSON: invalid syntax",
+ ),
+ pytest.param(
+ '"abcde"',
+ "top-level element must be an object",
+ id="bad JSON: top-level element is a string",
+ ),
+ pytest.param(
+ "[]",
+ "top-level element must be an object",
+ id="bad JSON: top-level element is an array",
+ ),
+ pytest.param(
+ "{}",
+ "server sent error response without a status",
+ id="bad JSON: no status member",
+ ),
+ pytest.param(
+ '{ "status": null }',
+ 'field "status" must be a string',
+ id="bad JSON: null status member",
+ ),
+ pytest.param(
+ '{ "status": 0 }',
+ 'field "status" must be a string',
+ id="bad JSON: int status member",
+ ),
+ pytest.param(
+ '{ "status": [ "bad" ] }',
+ 'field "status" must be a string',
+ id="bad JSON: array status member",
+ ),
+ pytest.param(
+ '{ "status": { "bad": "bad" } }',
+ 'field "status" must be a string',
+ id="bad JSON: object status member",
+ ),
+ pytest.param(
+ '{ "nested": { "status": "bad" } }',
+ "server sent error response without a status",
+ id="bad JSON: nested status",
+ ),
+ pytest.param(
+ '{ "status": "invalid_token" ',
+ "The input string ended unexpectedly",
+ id="bad JSON: unterminated object",
+ ),
+ pytest.param(
+ '{ "status": "invalid_token" } { }',
+ 'Expected end of input, but found "{"',
+ id="bad JSON: trailing data",
+ ),
+ pytest.param(
+ '{ "status": "invalid_token", "openid-configuration": 1 }',
+ 'field "openid-configuration" must be a string',
+ id="bad JSON: int openid-configuration member",
+ ),
+ pytest.param(
+ '{ "status": "invalid_token", "openid-configuration": 1 }',
+ 'field "openid-configuration" must be a string',
+ id="bad JSON: int openid-configuration member",
+ ),
+ pytest.param(
+ '{ "status": "invalid_token", "scope": 1 }',
+ 'field "scope" must be a string',
+ id="bad JSON: int scope member",
+ ),
+ ],
+)
+def test_oauth_discovery_server_error(accept, response, expected_error):
+ sock, client = accept(oauth_client_id=secrets.token_hex())
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ initial = start_oauth_handshake(conn)
+
+ # Fail the SASL exchange with an invalid JSON response.
+ pq3.send(
+ conn,
+ pq3.types.AuthnRequest,
+ type=pq3.authn.SASLContinue,
+ body=response.encode("utf-8"),
+ )
+
+ # The client should disconnect, so the socket is closed here. (If
+ # the client doesn't disconnect, it will report a different error
+ # below and the test will fail.)
+
+ with pytest.raises(psycopg2.OperationalError, match=expected_error):
+ client.check_completed()
+
+
[email protected](
+ "sasl_err,resp_type,resp_payload,expected_error",
+ [
+ pytest.param(
+ {"status": "invalid_request"},
+ pq3.types.ErrorResponse,
+ dict(
+ fields=[b"SFATAL", b"C28000", b"Mexpected error message", b""],
+ ),
+ "expected error message",
+ id="standard server error: invalid_request",
+ ),
+ pytest.param(
+ {"status": "invalid_token"},
+ pq3.types.ErrorResponse,
+ dict(
+ fields=[b"SFATAL", b"C28000", b"Mexpected error message", b""],
+ ),
+ "expected error message",
+ id="standard server error: invalid_token without discovery URI",
+ ),
+ pytest.param(
+ {"status": "invalid_request"},
+ pq3.types.AuthnRequest,
+ dict(type=pq3.authn.SASLContinue, body=b""),
+ "server sent additional OAuth data",
+ id="broken server: additional challenge after error",
+ ),
+ pytest.param(
+ {"status": "invalid_request"},
+ pq3.types.AuthnRequest,
+ dict(type=pq3.authn.SASLFinal),
+ "server sent additional OAuth data",
+ id="broken server: SASL success after error",
+ ),
+ ],
+)
+def test_oauth_server_error(accept, sasl_err, resp_type, resp_payload, expected_error):
+ sock, client = accept()
+
+ with sock:
+ with pq3.wrap(sock, debug_stream=sys.stdout) as conn:
+ start_oauth_handshake(conn)
+
+ # Ignore the client data. Return an error "challenge".
+ resp = json.dumps(sasl_err)
+ resp = resp.encode("utf-8")
+
+ pq3.send(
+ conn, pq3.types.AuthnRequest, type=pq3.authn.SASLContinue, body=resp
+ )
+
+ # Per RFC, the client is required to send a dummy ^A response.
+ pkt = pq3.recv1(conn)
+ assert pkt.type == pq3.types.PasswordMessage
+ assert pkt.payload == b"\x01"
+
+ # Now fail the SASL exchange (in either a valid way, or an invalid
+ # one, depending on the test).
+ pq3.send(conn, resp_type, **resp_payload)
+
+ with pytest.raises(psycopg2.OperationalError, match=expected_error):
+ client.check_completed()
diff --git a/src/test/python/pq3.py b/src/test/python/pq3.py
new file mode 100644
index 0000000000..3a22dad0b6
--- /dev/null
+++ b/src/test/python/pq3.py
@@ -0,0 +1,727 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import contextlib
+import getpass
+import io
+import os
+import ssl
+import sys
+import textwrap
+
+from construct import *
+
+import tls
+
+
+def protocol(major, minor):
+ """
+ Returns the protocol version, in integer format, corresponding to the given
+ major and minor version numbers.
+ """
+ return (major << 16) | minor
+
+
+# Startup
+
+StringList = GreedyRange(NullTerminated(GreedyBytes))
+
+
+class KeyValueAdapter(Adapter):
+ """
+ Turns a key-value store into a null-terminated list of null-terminated
+ strings, as presented on the wire in the startup packet.
+ """
+
+ def _encode(self, obj, context, path):
+ if isinstance(obj, list):
+ return obj
+
+ l = []
+
+ for k, v in obj.items():
+ if isinstance(k, str):
+ k = k.encode("utf-8")
+ l.append(k)
+
+ if isinstance(v, str):
+ v = v.encode("utf-8")
+ l.append(v)
+
+ l.append(b"")
+ return l
+
+ def _decode(self, obj, context, path):
+ # TODO: turn a list back into a dict
+ return obj
+
+
+KeyValues = KeyValueAdapter(StringList)
+
+_startup_payload = Switch(
+ this.proto,
+ {
+ protocol(3, 0): KeyValues,
+ },
+ default=GreedyBytes,
+)
+
+
+def _default_protocol(this):
+ try:
+ if isinstance(this.payload, (list, dict)):
+ return protocol(3, 0)
+ except AttributeError:
+ pass # no payload passed during build
+
+ return 0
+
+
+def _startup_payload_len(this):
+ """
+ The payload field has a fixed size based on the length of the packet. But
+ if the caller hasn't supplied an explicit length at build time, we have to
+ build the payload to figure out how long it is, which requires us to know
+ the length first... This function exists solely to break the cycle.
+ """
+ assert this._building, "_startup_payload_len() cannot be called during parsing"
+
+ try:
+ payload = this.payload
+ except AttributeError:
+ return 0 # no payload
+
+ if isinstance(payload, bytes):
+ # already serialized; just use the given length
+ return len(payload)
+
+ try:
+ proto = this.proto
+ except AttributeError:
+ proto = _default_protocol(this)
+
+ data = _startup_payload.build(payload, proto=proto)
+ return len(data)
+
+
+Startup = Struct(
+ "len" / Default(Int32sb, lambda this: _startup_payload_len(this) + 8),
+ "proto" / Default(Hex(Int32sb), _default_protocol),
+ "payload" / FixedSized(this.len - 8, Default(_startup_payload, b"")),
+)
+
+# Pq3
+
+# Adapted from construct.core.EnumIntegerString
+class EnumNamedByte:
+ def __init__(self, val, name):
+ self._val = val
+ self._name = name
+
+ def __int__(self):
+ return ord(self._val)
+
+ def __str__(self):
+ return "(enum) %s %r" % (self._name, self._val)
+
+ def __repr__(self):
+ return "EnumNamedByte(%r)" % self._val
+
+ def __eq__(self, other):
+ if isinstance(other, EnumNamedByte):
+ other = other._val
+ if not isinstance(other, bytes):
+ return NotImplemented
+
+ return self._val == other
+
+ def __hash__(self):
+ return hash(self._val)
+
+
+# Adapted from construct.core.Enum
+class ByteEnum(Adapter):
+ def __init__(self, **mapping):
+ super(ByteEnum, self).__init__(Byte)
+ self.namemapping = {k: EnumNamedByte(v, k) for k, v in mapping.items()}
+ self.decmapping = {v: EnumNamedByte(v, k) for k, v in mapping.items()}
+
+ def __getattr__(self, name):
+ if name in self.namemapping:
+ return self.decmapping[self.namemapping[name]]
+ raise AttributeError
+
+ def _decode(self, obj, context, path):
+ b = bytes([obj])
+ try:
+ return self.decmapping[b]
+ except KeyError:
+ return EnumNamedByte(b, "(unknown)")
+
+ def _encode(self, obj, context, path):
+ if isinstance(obj, int):
+ return obj
+ elif isinstance(obj, bytes):
+ return ord(obj)
+ return int(obj)
+
+
+types = ByteEnum(
+ ErrorResponse=b"E",
+ ReadyForQuery=b"Z",
+ Query=b"Q",
+ EmptyQueryResponse=b"I",
+ AuthnRequest=b"R",
+ PasswordMessage=b"p",
+ BackendKeyData=b"K",
+ CommandComplete=b"C",
+ ParameterStatus=b"S",
+ DataRow=b"D",
+ Terminate=b"X",
+)
+
+
+authn = Enum(
+ Int32ub,
+ OK=0,
+ SASL=10,
+ SASLContinue=11,
+ SASLFinal=12,
+)
+
+
+_authn_body = Switch(
+ this.type,
+ {
+ authn.OK: Terminated,
+ authn.SASL: StringList,
+ },
+ default=GreedyBytes,
+)
+
+
+def _data_len(this):
+ assert this._building, "_data_len() cannot be called during parsing"
+
+ if not hasattr(this, "data") or this.data is None:
+ return -1
+
+ return len(this.data)
+
+
+# The protocol reuses the PasswordMessage for several authentication response
+# types, and there's no good way to figure out which is which without keeping
+# state for the entire stream. So this is a separate Construct that can be
+# explicitly parsed/built by code that knows it's needed.
+SASLInitialResponse = Struct(
+ "name" / NullTerminated(GreedyBytes),
+ "len" / Default(Int32sb, lambda this: _data_len(this)),
+ "data"
+ / IfThenElse(
+ # Allow tests to explicitly pass an incorrect length during testing, by
+ # not enforcing a FixedSized during build. (The len calculation above
+ # defaults to the correct size.)
+ this._building,
+ Optional(GreedyBytes),
+ If(this.len != -1, Default(FixedSized(this.len, GreedyBytes), b"")),
+ ),
+ Terminated, # make sure the entire response is consumed
+)
+
+
+_column = FocusedSeq(
+ "data",
+ "len" / Default(Int32sb, lambda this: _data_len(this)),
+ "data" / If(this.len != -1, FixedSized(this.len, GreedyBytes)),
+)
+
+
+_payload_map = {
+ types.ErrorResponse: Struct("fields" / StringList),
+ types.ReadyForQuery: Struct("status" / Bytes(1)),
+ types.Query: Struct("query" / NullTerminated(GreedyBytes)),
+ types.EmptyQueryResponse: Terminated,
+ types.AuthnRequest: Struct("type" / authn, "body" / Default(_authn_body, b"")),
+ types.BackendKeyData: Struct("pid" / Int32ub, "key" / Hex(Int32ub)),
+ types.CommandComplete: Struct("tag" / NullTerminated(GreedyBytes)),
+ types.ParameterStatus: Struct(
+ "name" / NullTerminated(GreedyBytes), "value" / NullTerminated(GreedyBytes)
+ ),
+ types.DataRow: Struct("columns" / Default(PrefixedArray(Int16sb, _column), b"")),
+ types.Terminate: Terminated,
+}
+
+
+_payload = FocusedSeq(
+ "_payload",
+ "_payload"
+ / Switch(
+ this._.type,
+ _payload_map,
+ default=GreedyBytes,
+ ),
+ Terminated, # make sure every payload consumes the entire packet
+)
+
+
+def _payload_len(this):
+ """
+ See _startup_payload_len() for an explanation.
+ """
+ assert this._building, "_payload_len() cannot be called during parsing"
+
+ try:
+ payload = this.payload
+ except AttributeError:
+ return 0 # no payload
+
+ if isinstance(payload, bytes):
+ # already serialized; just use the given length
+ return len(payload)
+
+ data = _payload.build(payload, type=this.type)
+ return len(data)
+
+
+Pq3 = Struct(
+ "type" / types,
+ "len" / Default(Int32ub, lambda this: _payload_len(this) + 4),
+ "payload" / FixedSized(this.len - 4, Default(_payload, b"")),
+)
+
+
+# Environment
+
+
+def pghost():
+ return os.environ.get("PGHOST", default="localhost")
+
+
+def pgport():
+ return int(os.environ.get("PGPORT", default=5432))
+
+
+def pguser():
+ try:
+ return os.environ["PGUSER"]
+ except KeyError:
+ return getpass.getuser()
+
+
+def pgdatabase():
+ return os.environ.get("PGDATABASE", default="postgres")
+
+
+# Connections
+
+
+def _hexdump_translation_map():
+ """
+ For hexdumps. Translates any unprintable or non-ASCII bytes into '.'.
+ """
+ input = bytearray()
+
+ for i in range(128):
+ c = chr(i)
+
+ if not c.isprintable():
+ input += bytes([i])
+
+ input += bytes(range(128, 256))
+
+ return bytes.maketrans(input, b"." * len(input))
+
+
+class _DebugStream(object):
+ """
+ Wraps a file-like object and adds hexdumps of the read and write data. Call
+ end_packet() on a _DebugStream to write the accumulated hexdumps to the
+ output stream, along with the packet that was sent.
+ """
+
+ _translation_map = _hexdump_translation_map()
+
+ def __init__(self, stream, out=sys.stdout):
+ """
+ Creates a new _DebugStream wrapping the given stream (which must have
+ been created by wrap()). All attributes not provided by the _DebugStream
+ are delegated to the wrapped stream. out is the text stream to which
+ hexdumps are written.
+ """
+ self.raw = stream
+ self._out = out
+ self._rbuf = io.BytesIO()
+ self._wbuf = io.BytesIO()
+
+ def __getattr__(self, name):
+ return getattr(self.raw, name)
+
+ def __setattr__(self, name, value):
+ if name in ("raw", "_out", "_rbuf", "_wbuf"):
+ return object.__setattr__(self, name, value)
+
+ setattr(self.raw, name, value)
+
+ def read(self, *args, **kwargs):
+ buf = self.raw.read(*args, **kwargs)
+
+ self._rbuf.write(buf)
+ return buf
+
+ def write(self, b):
+ self._wbuf.write(b)
+ return self.raw.write(b)
+
+ def recv(self, *args):
+ buf = self.raw.recv(*args)
+
+ self._rbuf.write(buf)
+ return buf
+
+ def _flush(self, buf, prefix):
+ width = 16
+ hexwidth = width * 3 - 1
+
+ count = 0
+ buf.seek(0)
+
+ while True:
+ line = buf.read(16)
+
+ if not line:
+ if count:
+ self._out.write("\n") # separate the output block with a newline
+ return
+
+ self._out.write("%s %04X:\t" % (prefix, count))
+ self._out.write("%*s\t" % (-hexwidth, line.hex(" ")))
+ self._out.write(line.translate(self._translation_map).decode("ascii"))
+ self._out.write("\n")
+
+ count += 16
+
+ def print_debug(self, obj, *, prefix=""):
+ contents = ""
+ if obj is not None:
+ contents = str(obj)
+
+ for line in contents.splitlines():
+ self._out.write("%s%s\n" % (prefix, line))
+
+ self._out.write("\n")
+
+ def flush_debug(self, *, prefix=""):
+ self._flush(self._rbuf, prefix + "<")
+ self._rbuf = io.BytesIO()
+
+ self._flush(self._wbuf, prefix + ">")
+ self._wbuf = io.BytesIO()
+
+ def end_packet(self, pkt, *, read=False, prefix="", indent=" "):
+ """
+ Marks the end of a logical "packet" of data. A string representation of
+ pkt will be printed, and the debug buffers will be flushed with an
+ indent. All lines can be optionally prefixed.
+
+ If read is True, the packet representation is written after the debug
+ buffers; otherwise the default of False (meaning write) causes the
+ packet representation to be dumped first. This is meant to capture the
+ logical flow of layer translation.
+ """
+ write = not read
+
+ if write:
+ self.print_debug(pkt, prefix=prefix + "> ")
+
+ self.flush_debug(prefix=prefix + indent)
+
+ if read:
+ self.print_debug(pkt, prefix=prefix + "< ")
+
+
[email protected]
+def wrap(socket, *, debug_stream=None):
+ """
+ Transforms a raw socket into a connection that can be used for Construct
+ building and parsing. The return value is a context manager and can be used
+ in a with statement.
+ """
+ # It is critical that buffering be disabled here, so that we can still
+ # manipulate the raw socket without desyncing the stream.
+ with socket.makefile("rwb", buffering=0) as sfile:
+ # Expose the original socket's recv() on the SocketIO object we return.
+ def recv(self, *args):
+ return socket.recv(*args)
+
+ sfile.recv = recv.__get__(sfile)
+
+ conn = sfile
+ if debug_stream:
+ conn = _DebugStream(conn, debug_stream)
+
+ try:
+ yield conn
+ finally:
+ if debug_stream:
+ conn.flush_debug(prefix="? ")
+
+
+def _send(stream, cls, obj):
+ debugging = hasattr(stream, "flush_debug")
+ out = io.BytesIO()
+
+ # Ideally we would build directly to the passed stream, but because we need
+ # to reparse the generated output for the debugging case, build to an
+ # intermediate BytesIO and send it instead.
+ cls.build_stream(obj, out)
+ buf = out.getvalue()
+
+ stream.write(buf)
+ if debugging:
+ pkt = cls.parse(buf)
+ stream.end_packet(pkt)
+
+ stream.flush()
+
+
+def send(stream, packet_type, payload_data=None, **payloadkw):
+ """
+ Sends a packet on the given pq3 connection. type is the pq3.types member
+ that should be assigned to the packet. If payload_data is given, it will be
+ used as the packet payload; otherwise the key/value pairs in payloadkw will
+ be the payload contents.
+ """
+ data = payloadkw
+
+ if payload_data is not None:
+ if payloadkw:
+ raise ValueError(
+ "payload_data and payload keywords may not be used simultaneously"
+ )
+
+ data = payload_data
+
+ _send(stream, Pq3, dict(type=packet_type, payload=data))
+
+
+def send_startup(stream, proto=None, **kwargs):
+ """
+ Sends a startup packet on the given pq3 connection. In most cases you should
+ use the handshake functions instead, which will do this for you.
+
+ By default, a protocol version 3 packet will be sent. This can be overridden
+ with the proto parameter.
+ """
+ pkt = {}
+
+ if proto is not None:
+ pkt["proto"] = proto
+ if kwargs:
+ pkt["payload"] = kwargs
+
+ _send(stream, Startup, pkt)
+
+
+def recv1(stream, *, cls=Pq3):
+ """
+ Receives a single pq3 packet from the given stream and returns it.
+ """
+ resp = cls.parse_stream(stream)
+
+ debugging = hasattr(stream, "flush_debug")
+ if debugging:
+ stream.end_packet(resp, read=True)
+
+ return resp
+
+
+def handshake(stream, **kwargs):
+ """
+ Performs a libpq v3 startup handshake. kwargs should contain the key/value
+ parameters to send to the server in the startup packet.
+ """
+ # Send our startup parameters.
+ send_startup(stream, **kwargs)
+
+ # Receive and dump packets until the server indicates it's ready for our
+ # first query.
+ while True:
+ resp = recv1(stream)
+ if resp is None:
+ raise RuntimeError("server closed connection during handshake")
+
+ if resp.type == types.ReadyForQuery:
+ return
+ elif resp.type == types.ErrorResponse:
+ raise RuntimeError(
+ f"received error response from peer: {resp.payload.fields!r}"
+ )
+
+
+# TLS
+
+
+class _TLSStream(object):
+ """
+ A file-like object that performs TLS encryption/decryption on a wrapped
+ stream. Differs from ssl.SSLSocket in that we have full visibility and
+ control over the TLS layer.
+ """
+
+ def __init__(self, stream, context):
+ self._stream = stream
+ self._debugging = hasattr(stream, "flush_debug")
+
+ self._in = ssl.MemoryBIO()
+ self._out = ssl.MemoryBIO()
+ self._ssl = context.wrap_bio(self._in, self._out)
+
+ def handshake(self):
+ try:
+ self._pump(lambda: self._ssl.do_handshake())
+ finally:
+ self._flush_debug(prefix="? ")
+
+ def read(self, *args):
+ return self._pump(lambda: self._ssl.read(*args))
+
+ def write(self, *args):
+ return self._pump(lambda: self._ssl.write(*args))
+
+ def _decode(self, buf):
+ """
+ Attempts to decode a buffer of TLS data into a packet representation
+ that can be printed.
+
+ TODO: handle buffers (and record fragments) that don't align with packet
+ boundaries.
+ """
+ end = len(buf)
+ bio = io.BytesIO(buf)
+
+ ret = io.StringIO()
+
+ while bio.tell() < end:
+ record = tls.Plaintext.parse_stream(bio)
+
+ if ret.tell() > 0:
+ ret.write("\n")
+ ret.write("[Record] ")
+ ret.write(str(record))
+ ret.write("\n")
+
+ if record.type == tls.ContentType.handshake:
+ record_cls = tls.Handshake
+ else:
+ continue
+
+ innerlen = len(record.fragment)
+ inner = io.BytesIO(record.fragment)
+
+ while inner.tell() < innerlen:
+ msg = record_cls.parse_stream(inner)
+
+ indented = "[Message] " + str(msg)
+ indented = textwrap.indent(indented, " ")
+
+ ret.write("\n")
+ ret.write(indented)
+ ret.write("\n")
+
+ return ret.getvalue()
+
+ def flush(self):
+ if not self._out.pending:
+ self._stream.flush()
+ return
+
+ buf = self._out.read()
+ self._stream.write(buf)
+
+ if self._debugging:
+ pkt = self._decode(buf)
+ self._stream.end_packet(pkt, prefix=" ")
+
+ self._stream.flush()
+
+ def _pump(self, operation):
+ while True:
+ try:
+ return operation()
+ except (ssl.SSLWantReadError, ssl.SSLWantWriteError) as e:
+ want = e
+ self._read_write(want)
+
+ def _recv(self, maxsize):
+ buf = self._stream.recv(4096)
+ if not buf:
+ self._in.write_eof()
+ return
+
+ self._in.write(buf)
+
+ if not self._debugging:
+ return
+
+ pkt = self._decode(buf)
+ self._stream.end_packet(pkt, read=True, prefix=" ")
+
+ def _read_write(self, want):
+ # XXX This needs work. So many corner cases yet to handle. For one,
+ # doing blocking writes in flush may lead to distributed deadlock if the
+ # peer is already blocking on its writes.
+
+ if isinstance(want, ssl.SSLWantWriteError):
+ assert self._out.pending, "SSL backend wants write without data"
+
+ self.flush()
+
+ if isinstance(want, ssl.SSLWantReadError):
+ self._recv(4096)
+
+ def _flush_debug(self, prefix):
+ if not self._debugging:
+ return
+
+ self._stream.flush_debug(prefix=prefix)
+
+
[email protected]
+def tls_handshake(stream, context):
+ """
+ Performs a TLS handshake over the given stream (which must have been created
+ via a call to wrap()), and returns a new stream which transparently tunnels
+ data over the TLS connection.
+
+ If the passed stream has debugging enabled, the returned stream will also
+ have debugging, using the same output IO.
+ """
+ debugging = hasattr(stream, "flush_debug")
+
+ # Send our startup parameters.
+ send_startup(stream, proto=protocol(1234, 5679))
+
+ # Look at the SSL response.
+ resp = stream.read(1)
+ if debugging:
+ stream.flush_debug(prefix=" ")
+
+ if resp == b"N":
+ raise RuntimeError("server does not support SSLRequest")
+ if resp != b"S":
+ raise RuntimeError(f"unexpected response of type {resp!r} during TLS startup")
+
+ tls = _TLSStream(stream, context)
+ tls.handshake()
+
+ if debugging:
+ tls = _DebugStream(tls, stream._out)
+
+ try:
+ yield tls
+ # TODO: teardown/unwrap the connection?
+ finally:
+ if debugging:
+ tls.flush_debug(prefix="? ")
diff --git a/src/test/python/pytest.ini b/src/test/python/pytest.ini
new file mode 100644
index 0000000000..ab7a6e7fb9
--- /dev/null
+++ b/src/test/python/pytest.ini
@@ -0,0 +1,4 @@
+[pytest]
+
+markers =
+ slow: mark test as slow
diff --git a/src/test/python/requirements.txt b/src/test/python/requirements.txt
new file mode 100644
index 0000000000..32f105ea84
--- /dev/null
+++ b/src/test/python/requirements.txt
@@ -0,0 +1,7 @@
+black
+cryptography~=3.4.6
+construct~=2.10.61
+isort~=5.6
+psycopg2~=2.8.6
+pytest~=6.1
+pytest-asyncio~=0.14.0
diff --git a/src/test/python/server/__init__.py b/src/test/python/server/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/test/python/server/conftest.py b/src/test/python/server/conftest.py
new file mode 100644
index 0000000000..ba7342a453
--- /dev/null
+++ b/src/test/python/server/conftest.py
@@ -0,0 +1,45 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import contextlib
+import socket
+import sys
+
+import pytest
+
+import pq3
+
+
[email protected]
+def connect():
+ """
+ A factory fixture that, when called, returns a socket connected to a
+ Postgres server, wrapped in a pq3 connection. The calling test will be
+ skipped automatically if a server is not running at PGHOST:PGPORT, so it's
+ best to connect as soon as possible after the test case begins, to avoid
+ doing unnecessary work.
+ """
+ # Set up an ExitStack to handle safe cleanup of all of the moving pieces.
+ with contextlib.ExitStack() as stack:
+
+ def conn_factory():
+ addr = (pq3.pghost(), pq3.pgport())
+
+ try:
+ sock = socket.create_connection(addr, timeout=2)
+ except ConnectionError as e:
+ pytest.skip(f"unable to connect to {addr}: {e}")
+
+ # Have ExitStack close our socket.
+ stack.enter_context(sock)
+
+ # Wrap the connection in a pq3 layer and have ExitStack clean it up
+ # too.
+ wrap_ctx = pq3.wrap(sock, debug_stream=sys.stdout)
+ conn = stack.enter_context(wrap_ctx)
+
+ return conn
+
+ yield conn_factory
diff --git a/src/test/python/server/test_oauth.py b/src/test/python/server/test_oauth.py
new file mode 100644
index 0000000000..cb5ca7fa23
--- /dev/null
+++ b/src/test/python/server/test_oauth.py
@@ -0,0 +1,1012 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import base64
+import contextlib
+import json
+import os
+import pathlib
+import secrets
+import shlex
+import shutil
+import socket
+import struct
+from multiprocessing import shared_memory
+
+import psycopg2
+import pytest
+from psycopg2 import sql
+
+import pq3
+
+MAX_SASL_MESSAGE_LENGTH = 65535
+
+INVALID_AUTHORIZATION_ERRCODE = b"28000"
+PROTOCOL_VIOLATION_ERRCODE = b"08P01"
+FEATURE_NOT_SUPPORTED_ERRCODE = b"0A000"
+
+SHARED_MEM_NAME = "oauth-pytest"
+MAX_TOKEN_SIZE = 4096
+MAX_UINT16 = 2 ** 16 - 1
+
+
+def skip_if_no_postgres():
+ """
+ Used by the oauth_ctx fixture to skip this test module if no Postgres server
+ is running.
+
+ This logic is nearly duplicated with the conn fixture. Ideally oauth_ctx
+ would depend on that, but a module-scope fixture can't depend on a
+ test-scope fixture, and we haven't reached the rule of three yet.
+ """
+ addr = (pq3.pghost(), pq3.pgport())
+
+ try:
+ with socket.create_connection(addr, timeout=2):
+ pass
+ except ConnectionError as e:
+ pytest.skip(f"unable to connect to {addr}: {e}")
+
+
[email protected]
+def prepend_file(path, lines):
+ """
+ A context manager that prepends a file on disk with the desired lines of
+ text. When the context manager is exited, the file will be restored to its
+ original contents.
+ """
+ # First make a backup of the original file.
+ bak = path + ".bak"
+ shutil.copy2(path, bak)
+
+ try:
+ # Write the new lines, followed by the original file content.
+ with open(path, "w") as new, open(bak, "r") as orig:
+ new.writelines(lines)
+ shutil.copyfileobj(orig, new)
+
+ # Return control to the calling code.
+ yield
+
+ finally:
+ # Put the backup back into place.
+ os.replace(bak, path)
+
+
[email protected](scope="module")
+def oauth_ctx():
+ """
+ Creates a database and user that use the oauth auth method. The context
+ object contains the dbname and user attributes as strings to be used during
+ connection, as well as the issuer and scope that have been set in the HBA
+ configuration.
+
+ This fixture assumes that the standard PG* environment variables point to a
+ server running on a local machine, and that the PGUSER has rights to create
+ databases and roles.
+ """
+ skip_if_no_postgres() # don't bother running these tests without a server
+
+ id = secrets.token_hex(4)
+
+ class Context:
+ dbname = "oauth_test_" + id
+
+ user = "oauth_user_" + id
+ map_user = "oauth_map_user_" + id
+ authz_user = "oauth_authz_user_" + id
+
+ issuer = "https://example.com/" + id
+ scope = "openid " + id
+
+ ctx = Context()
+ hba_lines = (
+ f'host {ctx.dbname} {ctx.map_user} samehost oauth issuer="{ctx.issuer}" scope="{ctx.scope}" map=oauth\n',
+ f'host {ctx.dbname} {ctx.authz_user} samehost oauth issuer="{ctx.issuer}" scope="{ctx.scope}" trust_validator_authz=1\n',
+ f'host {ctx.dbname} all samehost oauth issuer="{ctx.issuer}" scope="{ctx.scope}"\n',
+ )
+ ident_lines = (r"oauth /^(.*)@example\.com$ \1",)
+
+ conn = psycopg2.connect("")
+ conn.autocommit = True
+
+ with contextlib.closing(conn):
+ c = conn.cursor()
+
+ # Create our roles and database.
+ user = sql.Identifier(ctx.user)
+ map_user = sql.Identifier(ctx.map_user)
+ authz_user = sql.Identifier(ctx.authz_user)
+ dbname = sql.Identifier(ctx.dbname)
+
+ c.execute(sql.SQL("CREATE ROLE {} LOGIN;").format(user))
+ c.execute(sql.SQL("CREATE ROLE {} LOGIN;").format(map_user))
+ c.execute(sql.SQL("CREATE ROLE {} LOGIN;").format(authz_user))
+ c.execute(sql.SQL("CREATE DATABASE {};").format(dbname))
+
+ # Make this test script the server's oauth_validator.
+ path = pathlib.Path(__file__).parent / "validate_bearer.py"
+ path = str(path.absolute())
+
+ cmd = f"{shlex.quote(path)} {SHARED_MEM_NAME} <&%f"
+ c.execute("ALTER SYSTEM SET oauth_validator_command TO %s;", (cmd,))
+
+ # Replace pg_hba and pg_ident.
+ c.execute("SHOW hba_file;")
+ hba = c.fetchone()[0]
+
+ c.execute("SHOW ident_file;")
+ ident = c.fetchone()[0]
+
+ with prepend_file(hba, hba_lines), prepend_file(ident, ident_lines):
+ c.execute("SELECT pg_reload_conf();")
+
+ # Use the new database and user.
+ yield ctx
+
+ # Put things back the way they were.
+ c.execute("SELECT pg_reload_conf();")
+
+ c.execute("ALTER SYSTEM RESET oauth_validator_command;")
+ c.execute(sql.SQL("DROP DATABASE {};").format(dbname))
+ c.execute(sql.SQL("DROP ROLE {};").format(authz_user))
+ c.execute(sql.SQL("DROP ROLE {};").format(map_user))
+ c.execute(sql.SQL("DROP ROLE {};").format(user))
+
+
[email protected]()
+def conn(oauth_ctx, connect):
+ """
+ A convenience wrapper for connect(). The main purpose of this fixture is to
+ make sure oauth_ctx runs its setup code before the connection is made.
+ """
+ return connect()
+
+
[email protected](scope="module", autouse=True)
+def authn_id_extension(oauth_ctx):
+ """
+ Performs a `CREATE EXTENSION authn_id` in the test database. This fixture is
+ autoused, so tests don't need to rely on it.
+ """
+ conn = psycopg2.connect(database=oauth_ctx.dbname)
+ conn.autocommit = True
+
+ with contextlib.closing(conn):
+ c = conn.cursor()
+ c.execute("CREATE EXTENSION authn_id;")
+
+
[email protected](scope="session")
+def shared_mem():
+ """
+ Yields a shared memory segment that can be used for communication between
+ the bearer_token fixture and ./validate_bearer.py.
+ """
+ size = MAX_TOKEN_SIZE + 2 # two byte length prefix
+ mem = shared_memory.SharedMemory(SHARED_MEM_NAME, create=True, size=size)
+
+ try:
+ with contextlib.closing(mem):
+ yield mem
+ finally:
+ mem.unlink()
+
+
[email protected]()
+def bearer_token(shared_mem):
+ """
+ Returns a factory function that, when called, will store a Bearer token in
+ shared_mem. If token is None (the default), a new token will be generated
+ using secrets.token_urlsafe() and returned; otherwise the passed token will
+ be used as-is.
+
+ When token is None, the generated token size in bytes may be specified as an
+ argument; if unset, a small 16-byte token will be generated. The token size
+ may not exceed MAX_TOKEN_SIZE in any case.
+
+ The return value is the token, converted to a bytes object.
+
+ As a special case for testing failure modes, accept_any may be set to True.
+ This signals to the validator command that any bearer token should be
+ accepted. The returned token in this case may be used or discarded as needed
+ by the test.
+ """
+
+ def set_token(token=None, *, size=16, accept_any=False):
+ if token is not None:
+ size = len(token)
+
+ if size > MAX_TOKEN_SIZE:
+ raise ValueError(f"token size {size} exceeds maximum size {MAX_TOKEN_SIZE}")
+
+ if token is None:
+ if size % 4:
+ raise ValueError(f"requested token size {size} is not a multiple of 4")
+
+ token = secrets.token_urlsafe(size // 4 * 3)
+ assert len(token) == size
+
+ try:
+ token = token.encode("ascii")
+ except AttributeError:
+ pass # already encoded
+
+ if accept_any:
+ # Two-byte magic value.
+ shared_mem.buf[:2] = struct.pack("H", MAX_UINT16)
+ else:
+ # Two-byte length prefix, then the token data.
+ shared_mem.buf[:2] = struct.pack("H", len(token))
+ shared_mem.buf[2 : size + 2] = token
+
+ return token
+
+ return set_token
+
+
+def begin_oauth_handshake(conn, oauth_ctx, *, user=None):
+ if user is None:
+ user = oauth_ctx.authz_user
+
+ pq3.send_startup(conn, user=user, database=oauth_ctx.dbname)
+
+ resp = pq3.recv1(conn)
+ assert resp.type == pq3.types.AuthnRequest
+
+ # The server should advertise exactly one mechanism.
+ assert resp.payload.type == pq3.authn.SASL
+ assert resp.payload.body == [b"OAUTHBEARER", b""]
+
+
+def send_initial_response(conn, *, auth=None, bearer=None):
+ """
+ Sends the OAUTHBEARER initial response on the connection, using the given
+ bearer token. Alternatively to a bearer token, the initial response's auth
+ field may be explicitly specified to test corner cases.
+ """
+ if bearer is not None and auth is not None:
+ raise ValueError("exactly one of the auth and bearer kwargs must be set")
+
+ if bearer is not None:
+ auth = b"Bearer " + bearer
+
+ if auth is None:
+ raise ValueError("exactly one of the auth and bearer kwargs must be set")
+
+ initial = pq3.SASLInitialResponse.build(
+ dict(
+ name=b"OAUTHBEARER",
+ data=b"n,,\x01auth=" + auth + b"\x01\x01",
+ )
+ )
+ pq3.send(conn, pq3.types.PasswordMessage, initial)
+
+
+def expect_handshake_success(conn):
+ """
+ Validates that the server responds with an AuthnOK message, and then drains
+ the connection until a ReadyForQuery message is received.
+ """
+ resp = pq3.recv1(conn)
+
+ assert resp.type == pq3.types.AuthnRequest
+ assert resp.payload.type == pq3.authn.OK
+ assert not resp.payload.body
+
+ receive_until(conn, pq3.types.ReadyForQuery)
+
+
+def expect_handshake_failure(conn, oauth_ctx):
+ """
+ Performs the OAUTHBEARER SASL failure "handshake" and validates the server's
+ side of the conversation, including the final ErrorResponse.
+ """
+
+ # We expect a discovery "challenge" back from the server before the authn
+ # failure message.
+ resp = pq3.recv1(conn)
+ assert resp.type == pq3.types.AuthnRequest
+
+ req = resp.payload
+ assert req.type == pq3.authn.SASLContinue
+
+ body = json.loads(req.body)
+ assert body["status"] == "invalid_token"
+ assert body["scope"] == oauth_ctx.scope
+
+ expected_config = oauth_ctx.issuer + "/.well-known/openid-configuration"
+ assert body["openid-configuration"] == expected_config
+
+ # Send the dummy response to complete the failed handshake.
+ pq3.send(conn, pq3.types.PasswordMessage, b"\x01")
+ resp = pq3.recv1(conn)
+
+ err = ExpectedError(INVALID_AUTHORIZATION_ERRCODE, "bearer authentication failed")
+ err.match(resp)
+
+
+def receive_until(conn, type):
+ """
+ receive_until pulls packets off the pq3 connection until a packet with the
+ desired type is found, or an error response is received.
+ """
+ while True:
+ pkt = pq3.recv1(conn)
+
+ if pkt.type == type:
+ return pkt
+ elif pkt.type == pq3.types.ErrorResponse:
+ raise RuntimeError(
+ f"received error response from peer: {pkt.payload.fields!r}"
+ )
+
+
[email protected]("token_len", [16, 1024, 4096])
[email protected](
+ "auth_prefix",
+ [
+ b"Bearer ",
+ b"bearer ",
+ b"Bearer ",
+ ],
+)
+def test_oauth(conn, oauth_ctx, bearer_token, auth_prefix, token_len):
+ begin_oauth_handshake(conn, oauth_ctx)
+
+ # Generate our bearer token with the desired length.
+ token = bearer_token(size=token_len)
+ auth = auth_prefix + token
+
+ send_initial_response(conn, auth=auth)
+ expect_handshake_success(conn)
+
+ # Make sure that the server has not set an authenticated ID.
+ pq3.send(conn, pq3.types.Query, query=b"SELECT authn_id();")
+ resp = receive_until(conn, pq3.types.DataRow)
+
+ row = resp.payload
+ assert row.columns == [None]
+
+
[email protected](
+ "token_value",
+ [
+ "abcdzA==",
+ "123456M=",
+ "x-._~+/x",
+ ],
+)
+def test_oauth_bearer_corner_cases(conn, oauth_ctx, bearer_token, token_value):
+ begin_oauth_handshake(conn, oauth_ctx)
+
+ send_initial_response(conn, bearer=bearer_token(token_value))
+
+ expect_handshake_success(conn)
+
+
[email protected](
+ "user,authn_id,should_succeed",
+ [
+ pytest.param(
+ lambda ctx: ctx.user,
+ lambda ctx: ctx.user,
+ True,
+ id="validator authn: succeeds when authn_id == username",
+ ),
+ pytest.param(
+ lambda ctx: ctx.user,
+ lambda ctx: None,
+ False,
+ id="validator authn: fails when authn_id is not set",
+ ),
+ pytest.param(
+ lambda ctx: ctx.user,
+ lambda ctx: ctx.authz_user,
+ False,
+ id="validator authn: fails when authn_id != username",
+ ),
+ pytest.param(
+ lambda ctx: ctx.map_user,
+ lambda ctx: ctx.map_user + "@example.com",
+ True,
+ id="validator with map: succeeds when authn_id matches map",
+ ),
+ pytest.param(
+ lambda ctx: ctx.map_user,
+ lambda ctx: None,
+ False,
+ id="validator with map: fails when authn_id is not set",
+ ),
+ pytest.param(
+ lambda ctx: ctx.map_user,
+ lambda ctx: ctx.map_user + "@example.net",
+ False,
+ id="validator with map: fails when authn_id doesn't match map",
+ ),
+ pytest.param(
+ lambda ctx: ctx.authz_user,
+ lambda ctx: None,
+ True,
+ id="validator authz: succeeds with no authn_id",
+ ),
+ pytest.param(
+ lambda ctx: ctx.authz_user,
+ lambda ctx: "",
+ True,
+ id="validator authz: succeeds with empty authn_id",
+ ),
+ pytest.param(
+ lambda ctx: ctx.authz_user,
+ lambda ctx: "postgres",
+ True,
+ id="validator authz: succeeds with basic username",
+ ),
+ pytest.param(
+ lambda ctx: ctx.authz_user,
+ lambda ctx: "[email protected]",
+ True,
+ id="validator authz: succeeds with email address",
+ ),
+ ],
+)
+def test_oauth_authn_id(conn, oauth_ctx, bearer_token, user, authn_id, should_succeed):
+ token = None
+
+ authn_id = authn_id(oauth_ctx)
+ if authn_id is not None:
+ authn_id = authn_id.encode("ascii")
+
+ # As a hack to get the validator to reflect arbitrary output from this
+ # test, encode the desired output as a base64 token. The validator will
+ # key on the leading "output=" to differentiate this from the random
+ # tokens generated by secrets.token_urlsafe().
+ output = b"output=" + authn_id + b"\n"
+ token = base64.urlsafe_b64encode(output)
+
+ token = bearer_token(token)
+ username = user(oauth_ctx)
+
+ begin_oauth_handshake(conn, oauth_ctx, user=username)
+ send_initial_response(conn, bearer=token)
+
+ if not should_succeed:
+ expect_handshake_failure(conn, oauth_ctx)
+ return
+
+ expect_handshake_success(conn)
+
+ # Check the reported authn_id.
+ pq3.send(conn, pq3.types.Query, query=b"SELECT authn_id();")
+ resp = receive_until(conn, pq3.types.DataRow)
+
+ row = resp.payload
+ assert row.columns == [authn_id]
+
+
+class ExpectedError(object):
+ def __init__(self, code, msg=None, detail=None):
+ self.code = code
+ self.msg = msg
+ self.detail = detail
+
+ # Protect against the footgun of an accidental empty string, which will
+ # "match" anything. If you don't want to match message or detail, just
+ # don't pass them.
+ if self.msg == "":
+ raise ValueError("msg must be non-empty or None")
+ if self.detail == "":
+ raise ValueError("detail must be non-empty or None")
+
+ def _getfield(self, resp, type):
+ """
+ Searches an ErrorResponse for a single field of the given type (e.g.
+ "M", "C", "D") and returns its value. Asserts if it doesn't find exactly
+ one field.
+ """
+ prefix = type.encode("ascii")
+ fields = [f for f in resp.payload.fields if f.startswith(prefix)]
+
+ assert len(fields) == 1
+ return fields[0][1:] # strip off the type byte
+
+ def match(self, resp):
+ """
+ Checks that the given response matches the expected code, message, and
+ detail (if given). The error code must match exactly. The expected
+ message and detail must be contained within the actual strings.
+ """
+ assert resp.type == pq3.types.ErrorResponse
+
+ code = self._getfield(resp, "C")
+ assert code == self.code
+
+ if self.msg:
+ msg = self._getfield(resp, "M")
+ expected = self.msg.encode("utf-8")
+ assert expected in msg
+
+ if self.detail:
+ detail = self._getfield(resp, "D")
+ expected = self.detail.encode("utf-8")
+ assert expected in detail
+
+
+def test_oauth_rejected_bearer(conn, oauth_ctx, bearer_token):
+ # Generate a new bearer token, which we will proceed not to use.
+ _ = bearer_token()
+
+ begin_oauth_handshake(conn, oauth_ctx)
+
+ # Send a bearer token that doesn't match what the validator expects. It
+ # should fail the connection.
+ send_initial_response(conn, bearer=b"xxxxxx")
+
+ expect_handshake_failure(conn, oauth_ctx)
+
+
[email protected](
+ "bad_bearer",
+ [
+ b"Bearer ",
+ b"Bearer a===b",
+ b"Bearer hello!",
+ b"Bearer [email protected]",
+ b'OAuth realm="Example"',
+ b"",
+ ],
+)
+def test_oauth_invalid_bearer(conn, oauth_ctx, bearer_token, bad_bearer):
+ # Tell the validator to accept any token. This ensures that the invalid
+ # bearer tokens are rejected before the validation step.
+ _ = bearer_token(accept_any=True)
+
+ begin_oauth_handshake(conn, oauth_ctx)
+ send_initial_response(conn, auth=bad_bearer)
+
+ expect_handshake_failure(conn, oauth_ctx)
+
+
[email protected]
[email protected](
+ "resp_type,resp,err",
+ [
+ pytest.param(
+ None,
+ None,
+ None,
+ marks=pytest.mark.slow,
+ id="no response (expect timeout)",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ b"hello",
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "did not send a kvsep response",
+ ),
+ id="bad dummy response",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ b"\x01\x01",
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "did not send a kvsep response",
+ ),
+ id="multiple kvseps",
+ ),
+ pytest.param(
+ pq3.types.Query,
+ dict(query=b""),
+ ExpectedError(PROTOCOL_VIOLATION_ERRCODE, "expected SASL response"),
+ id="bad response message type",
+ ),
+ ],
+)
+def test_oauth_bad_response_to_error_challenge(conn, oauth_ctx, resp_type, resp, err):
+ begin_oauth_handshake(conn, oauth_ctx)
+
+ # Send an empty auth initial response, which will force an authn failure.
+ send_initial_response(conn, auth=b"")
+
+ # We expect a discovery "challenge" back from the server before the authn
+ # failure message.
+ pkt = pq3.recv1(conn)
+ assert pkt.type == pq3.types.AuthnRequest
+
+ req = pkt.payload
+ assert req.type == pq3.authn.SASLContinue
+
+ body = json.loads(req.body)
+ assert body["status"] == "invalid_token"
+
+ if resp_type is None:
+ # Do not send the dummy response. We should time out and not get a
+ # response from the server.
+ with pytest.raises(socket.timeout):
+ conn.read(1)
+
+ # Done with the test.
+ return
+
+ # Send the bad response.
+ pq3.send(conn, resp_type, resp)
+
+ # Make sure the server fails the connection correctly.
+ pkt = pq3.recv1(conn)
+ err.match(pkt)
+
+
[email protected](
+ "type,payload,err",
+ [
+ pytest.param(
+ pq3.types.ErrorResponse,
+ dict(fields=[b""]),
+ ExpectedError(PROTOCOL_VIOLATION_ERRCODE, "expected SASL response"),
+ id="error response in initial message",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ b"x" * (MAX_SASL_MESSAGE_LENGTH + 1),
+ ExpectedError(
+ INVALID_AUTHORIZATION_ERRCODE, "bearer authentication failed"
+ ),
+ id="overlong initial response data",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"SCRAM-SHA-256")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE, "invalid SASL authentication mechanism"
+ ),
+ id="bad SASL mechanism selection",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", len=2, data=b"x")),
+ ExpectedError(PROTOCOL_VIOLATION_ERRCODE, "insufficient data"),
+ id="SASL data underflow",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", len=0, data=b"x")),
+ ExpectedError(PROTOCOL_VIOLATION_ERRCODE, "invalid message format"),
+ id="SASL data overflow",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "message is empty",
+ ),
+ id="empty",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"n,,\x01auth=\x01\x01\0")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "length does not match input length",
+ ),
+ id="contains null byte",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"\x01")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Unexpected channel-binding flag", # XXX this is a bit strange
+ ),
+ id="initial error response",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"p=tls-server-end-point,,\x01")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "server does not support channel binding",
+ ),
+ id="uses channel binding",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"x,,\x01")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Unexpected channel-binding flag",
+ ),
+ id="invalid channel binding specifier",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Comma expected",
+ ),
+ id="bad GS2 header: missing channel binding terminator",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y,a")),
+ ExpectedError(
+ FEATURE_NOT_SUPPORTED_ERRCODE,
+ "client uses authorization identity",
+ ),
+ id="bad GS2 header: authzid in use",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y,b,")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Unexpected attribute",
+ ),
+ id="bad GS2 header: extra attribute",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y,")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Unexpected attribute 0x00", # XXX this is a bit strange
+ ),
+ id="bad GS2 header: missing authzid terminator",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y,,")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Key-value separator expected",
+ ),
+ id="missing initial kvsep",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y,,")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "Key-value separator expected",
+ ),
+ id="missing initial kvsep",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"y,,\x01\x01")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "does not contain an auth value",
+ ),
+ id="missing auth value: empty key-value list",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"y,,\x01host=example.com\x01\x01")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "does not contain an auth value",
+ ),
+ id="missing auth value: other keys present",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"y,,\x01host=example.com")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "unterminated key/value pair",
+ ),
+ id="missing value terminator",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER", data=b"y,,\x01")),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "did not contain a final terminator",
+ ),
+ id="missing list terminator: empty list",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"y,,\x01auth=Bearer 0\x01")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "did not contain a final terminator",
+ ),
+ id="missing list terminator: with auth value",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"y,,\x01auth=Bearer 0\x01\x01blah")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "additional data after the final terminator",
+ ),
+ id="additional key after terminator",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(name=b"OAUTHBEARER", data=b"y,,\x01key\x01\x01")
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "key without a value",
+ ),
+ id="key without value",
+ ),
+ pytest.param(
+ pq3.types.PasswordMessage,
+ pq3.SASLInitialResponse.build(
+ dict(
+ name=b"OAUTHBEARER",
+ data=b"y,,\x01auth=Bearer 0\x01auth=Bearer 1\x01\x01",
+ )
+ ),
+ ExpectedError(
+ PROTOCOL_VIOLATION_ERRCODE,
+ "malformed OAUTHBEARER message",
+ "contains multiple auth values",
+ ),
+ id="multiple auth values",
+ ),
+ ],
+)
+def test_oauth_bad_initial_response(conn, oauth_ctx, type, payload, err):
+ begin_oauth_handshake(conn, oauth_ctx)
+
+ # The server expects a SASL response; give it something else instead.
+ if not isinstance(payload, dict):
+ payload = dict(payload_data=payload)
+ pq3.send(conn, type, **payload)
+
+ resp = pq3.recv1(conn)
+ err.match(resp)
+
+
+def test_oauth_empty_initial_response(conn, oauth_ctx, bearer_token):
+ begin_oauth_handshake(conn, oauth_ctx)
+
+ # Send an initial response without data.
+ initial = pq3.SASLInitialResponse.build(dict(name=b"OAUTHBEARER"))
+ pq3.send(conn, pq3.types.PasswordMessage, initial)
+
+ # The server should respond with an empty challenge so we can send the data
+ # it wants.
+ pkt = pq3.recv1(conn)
+
+ assert pkt.type == pq3.types.AuthnRequest
+ assert pkt.payload.type == pq3.authn.SASLContinue
+ assert not pkt.payload.body
+
+ # Now send the initial data.
+ data = b"n,,\x01auth=Bearer " + bearer_token() + b"\x01\x01"
+ pq3.send(conn, pq3.types.PasswordMessage, data)
+
+ # Server should now complete the handshake.
+ expect_handshake_success(conn)
+
+
[email protected]()
+def set_validator():
+ """
+ A per-test fixture that allows a test to override the setting of
+ oauth_validator_command for the cluster. The setting will be reverted during
+ teardown.
+
+ Passing None will perform an ALTER SYSTEM RESET.
+ """
+ conn = psycopg2.connect("")
+ conn.autocommit = True
+
+ with contextlib.closing(conn):
+ c = conn.cursor()
+
+ # Save the previous value.
+ c.execute("SHOW oauth_validator_command;")
+ prev_cmd = c.fetchone()[0]
+
+ def setter(cmd):
+ c.execute("ALTER SYSTEM SET oauth_validator_command TO %s;", (cmd,))
+ c.execute("SELECT pg_reload_conf();")
+
+ yield setter
+
+ # Restore the previous value.
+ c.execute("ALTER SYSTEM SET oauth_validator_command TO %s;", (prev_cmd,))
+ c.execute("SELECT pg_reload_conf();")
+
+
+def test_oauth_no_validator(oauth_ctx, set_validator, connect, bearer_token):
+ # Clear out our validator command, then establish a new connection.
+ set_validator("")
+ conn = connect()
+
+ begin_oauth_handshake(conn, oauth_ctx)
+ send_initial_response(conn, bearer=bearer_token())
+
+ # The server should fail the connection.
+ expect_handshake_failure(conn, oauth_ctx)
+
+
+def test_oauth_validator_role(oauth_ctx, set_validator, connect):
+ # Switch the validator implementation. This validator will reflect the
+ # PGUSER as the authenticated identity.
+ path = pathlib.Path(__file__).parent / "validate_reflect.py"
+ path = str(path.absolute())
+
+ set_validator(f"{shlex.quote(path)} '%r' <&%f")
+ conn = connect()
+
+ # Log in. Note that the reflection validator ignores the bearer token.
+ begin_oauth_handshake(conn, oauth_ctx, user=oauth_ctx.user)
+ send_initial_response(conn, bearer=b"dontcare")
+ expect_handshake_success(conn)
+
+ # Check the user identity.
+ pq3.send(conn, pq3.types.Query, query=b"SELECT authn_id();")
+ resp = receive_until(conn, pq3.types.DataRow)
+
+ row = resp.payload
+ expected = oauth_ctx.user.encode("utf-8")
+ assert row.columns == [expected]
+
+
+def test_oauth_role_with_shell_unsafe_characters(oauth_ctx, set_validator, connect):
+ """
+ XXX This test pins undesirable behavior. We should be able to handle any
+ valid Postgres role name.
+ """
+ # Switch the validator implementation. This validator will reflect the
+ # PGUSER as the authenticated identity.
+ path = pathlib.Path(__file__).parent / "validate_reflect.py"
+ path = str(path.absolute())
+
+ set_validator(f"{shlex.quote(path)} '%r' <&%f")
+ conn = connect()
+
+ unsafe_username = "hello'there"
+ begin_oauth_handshake(conn, oauth_ctx, user=unsafe_username)
+
+ # The server should reject the handshake.
+ send_initial_response(conn, bearer=b"dontcare")
+ expect_handshake_failure(conn, oauth_ctx)
diff --git a/src/test/python/server/test_server.py b/src/test/python/server/test_server.py
new file mode 100644
index 0000000000..02126dba79
--- /dev/null
+++ b/src/test/python/server/test_server.py
@@ -0,0 +1,21 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import pq3
+
+
+def test_handshake(connect):
+ """Basic sanity check."""
+ conn = connect()
+
+ pq3.handshake(conn, user=pq3.pguser(), database=pq3.pgdatabase())
+
+ pq3.send(conn, pq3.types.Query, query=b"")
+
+ resp = pq3.recv1(conn)
+ assert resp.type == pq3.types.EmptyQueryResponse
+
+ resp = pq3.recv1(conn)
+ assert resp.type == pq3.types.ReadyForQuery
diff --git a/src/test/python/server/validate_bearer.py b/src/test/python/server/validate_bearer.py
new file mode 100755
index 0000000000..2cc73ff154
--- /dev/null
+++ b/src/test/python/server/validate_bearer.py
@@ -0,0 +1,101 @@
+#! /usr/bin/env python3
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+# DO NOT USE THIS OAUTH VALIDATOR IN PRODUCTION. It doesn't actually validate
+# anything, and it logs the bearer token data, which is sensitive.
+#
+# This executable is used as an oauth_validator_command in concert with
+# test_oauth.py. Memory is shared and communicated from that test module's
+# bearer_token() fixture.
+#
+# This script must run under the Postgres server environment; keep the
+# dependency list fairly standard.
+
+import base64
+import binascii
+import contextlib
+import struct
+import sys
+from multiprocessing import shared_memory
+
+MAX_UINT16 = 2 ** 16 - 1
+
+
+def remove_shm_from_resource_tracker():
+ """
+ Monkey-patch multiprocessing.resource_tracker so SharedMemory won't be
+ tracked. Pulled from this thread, where there are more details:
+
+ https://bugs.python.org/issue38119
+
+ TL;DR: all clients of shared memory segments automatically destroy them on
+ process exit, which makes shared memory segments much less useful. This
+ monkeypatch removes that behavior so that we can defer to the test to manage
+ the segment lifetime.
+
+ Ideally a future Python patch will pull in this fix and then the entire
+ function can go away.
+ """
+ from multiprocessing import resource_tracker
+
+ def fix_register(name, rtype):
+ if rtype == "shared_memory":
+ return
+ return resource_tracker._resource_tracker.register(self, name, rtype)
+
+ resource_tracker.register = fix_register
+
+ def fix_unregister(name, rtype):
+ if rtype == "shared_memory":
+ return
+ return resource_tracker._resource_tracker.unregister(self, name, rtype)
+
+ resource_tracker.unregister = fix_unregister
+
+ if "shared_memory" in resource_tracker._CLEANUP_FUNCS:
+ del resource_tracker._CLEANUP_FUNCS["shared_memory"]
+
+
+def main(args):
+ remove_shm_from_resource_tracker() # XXX remove some day
+
+ # Get the expected token from the currently running test.
+ shared_mem_name = args[0]
+
+ mem = shared_memory.SharedMemory(shared_mem_name)
+ with contextlib.closing(mem):
+ # First two bytes are the token length.
+ size = struct.unpack("H", mem.buf[:2])[0]
+
+ if size == MAX_UINT16:
+ # Special case: the test wants us to accept any token.
+ sys.stderr.write("accepting token without validation\n")
+ return
+
+ # The remainder of the buffer contains the expected token.
+ assert size <= (mem.size - 2)
+ expected_token = mem.buf[2 : size + 2].tobytes()
+
+ mem.buf[:] = b"\0" * mem.size # scribble over the token
+
+ token = sys.stdin.buffer.read()
+ if token != expected_token:
+ sys.exit(f"failed to match Bearer token ({token!r} != {expected_token!r})")
+
+ # See if the test wants us to print anything. If so, it will have encoded
+ # the desired output in the token with an "output=" prefix.
+ try:
+ # altchars="-_" corresponds to the urlsafe alphabet.
+ data = base64.b64decode(token, altchars="-_", validate=True)
+
+ if data.startswith(b"output="):
+ sys.stdout.buffer.write(data[7:])
+
+ except binascii.Error:
+ pass
+
+
+if __name__ == "__main__":
+ main(sys.argv[1:])
diff --git a/src/test/python/server/validate_reflect.py b/src/test/python/server/validate_reflect.py
new file mode 100755
index 0000000000..24c3a7e715
--- /dev/null
+++ b/src/test/python/server/validate_reflect.py
@@ -0,0 +1,34 @@
+#! /usr/bin/env python3
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+# DO NOT USE THIS OAUTH VALIDATOR IN PRODUCTION. It ignores the bearer token
+# entirely and automatically logs the user in.
+#
+# This executable is used as an oauth_validator_command in concert with
+# test_oauth.py. It expects the user's desired role name as an argument; the
+# actual token will be discarded and the user will be logged in with the role
+# name as the authenticated identity.
+#
+# This script must run under the Postgres server environment; keep the
+# dependency list fairly standard.
+
+import sys
+
+
+def main(args):
+ # We have to read the entire token as our first action to unblock the
+ # server, but we won't actually use it.
+ _ = sys.stdin.buffer.read()
+
+ if len(args) != 1:
+ sys.exit("usage: ./validate_reflect.py ROLE")
+
+ # Log the user in as the provided role.
+ role = args[0]
+ print(role)
+
+
+if __name__ == "__main__":
+ main(sys.argv[1:])
diff --git a/src/test/python/test_internals.py b/src/test/python/test_internals.py
new file mode 100644
index 0000000000..dee4855fc0
--- /dev/null
+++ b/src/test/python/test_internals.py
@@ -0,0 +1,138 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import io
+
+from pq3 import _DebugStream
+
+
+def test_DebugStream_read():
+ under = io.BytesIO(b"abcdefghijklmnopqrstuvwxyz")
+ out = io.StringIO()
+
+ stream = _DebugStream(under, out)
+
+ res = stream.read(5)
+ assert res == b"abcde"
+
+ res = stream.read(16)
+ assert res == b"fghijklmnopqrstu"
+
+ stream.flush_debug()
+
+ res = stream.read()
+ assert res == b"vwxyz"
+
+ stream.flush_debug()
+
+ expected = (
+ "< 0000:\t61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70\tabcdefghijklmnop\n"
+ "< 0010:\t71 72 73 74 75 \tqrstu\n"
+ "\n"
+ "< 0000:\t76 77 78 79 7a \tvwxyz\n"
+ "\n"
+ )
+ assert out.getvalue() == expected
+
+
+def test_DebugStream_write():
+ under = io.BytesIO()
+ out = io.StringIO()
+
+ stream = _DebugStream(under, out)
+
+ stream.write(b"\x00\x01\x02")
+ stream.flush()
+
+ assert under.getvalue() == b"\x00\x01\x02"
+
+ stream.write(b"\xc0\xc1\xc2")
+ stream.flush()
+
+ assert under.getvalue() == b"\x00\x01\x02\xc0\xc1\xc2"
+
+ stream.flush_debug()
+
+ expected = "> 0000:\t00 01 02 c0 c1 c2 \t......\n\n"
+ assert out.getvalue() == expected
+
+
+def test_DebugStream_read_write():
+ under = io.BytesIO(b"abcdefghijklmnopqrstuvwxyz")
+ out = io.StringIO()
+ stream = _DebugStream(under, out)
+
+ res = stream.read(5)
+ assert res == b"abcde"
+
+ stream.write(b"xxxxx")
+ stream.flush()
+
+ assert under.getvalue() == b"abcdexxxxxklmnopqrstuvwxyz"
+
+ res = stream.read(5)
+ assert res == b"klmno"
+
+ stream.write(b"xxxxx")
+ stream.flush()
+
+ assert under.getvalue() == b"abcdexxxxxklmnoxxxxxuvwxyz"
+
+ stream.flush_debug()
+
+ expected = (
+ "< 0000:\t61 62 63 64 65 6b 6c 6d 6e 6f \tabcdeklmno\n"
+ "\n"
+ "> 0000:\t78 78 78 78 78 78 78 78 78 78 \txxxxxxxxxx\n"
+ "\n"
+ )
+ assert out.getvalue() == expected
+
+
+def test_DebugStream_end_packet():
+ under = io.BytesIO(b"abcdefghijklmnopqrstuvwxyz")
+ out = io.StringIO()
+ stream = _DebugStream(under, out)
+
+ stream.read(5)
+ stream.end_packet("read description", read=True, indent=" ")
+
+ stream.write(b"xxxxx")
+ stream.flush()
+ stream.end_packet("write description", indent=" ")
+
+ stream.read(5)
+ stream.write(b"xxxxx")
+ stream.flush()
+ stream.end_packet("read/write combo for read", read=True, indent=" ")
+
+ stream.read(5)
+ stream.write(b"xxxxx")
+ stream.flush()
+ stream.end_packet("read/write combo for write", indent=" ")
+
+ expected = (
+ " < 0000:\t61 62 63 64 65 \tabcde\n"
+ "\n"
+ "< read description\n"
+ "\n"
+ "> write description\n"
+ "\n"
+ " > 0000:\t78 78 78 78 78 \txxxxx\n"
+ "\n"
+ " < 0000:\t6b 6c 6d 6e 6f \tklmno\n"
+ "\n"
+ " > 0000:\t78 78 78 78 78 \txxxxx\n"
+ "\n"
+ "< read/write combo for read\n"
+ "\n"
+ "> read/write combo for write\n"
+ "\n"
+ " < 0000:\t75 76 77 78 79 \tuvwxy\n"
+ "\n"
+ " > 0000:\t78 78 78 78 78 \txxxxx\n"
+ "\n"
+ )
+ assert out.getvalue() == expected
diff --git a/src/test/python/test_pq3.py b/src/test/python/test_pq3.py
new file mode 100644
index 0000000000..e0c0e0568d
--- /dev/null
+++ b/src/test/python/test_pq3.py
@@ -0,0 +1,558 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+import contextlib
+import getpass
+import io
+import struct
+import sys
+
+import pytest
+from construct import Container, PaddingError, StreamError, TerminatedError
+
+import pq3
+
+
[email protected](
+ "raw,expected,extra",
+ [
+ pytest.param(
+ b"\x00\x00\x00\x10\x00\x04\x00\x00abcdefgh",
+ Container(len=16, proto=0x40000, payload=b"abcdefgh"),
+ b"",
+ id="8-byte payload",
+ ),
+ pytest.param(
+ b"\x00\x00\x00\x08\x00\x04\x00\x00",
+ Container(len=8, proto=0x40000, payload=b""),
+ b"",
+ id="no payload",
+ ),
+ pytest.param(
+ b"\x00\x00\x00\x09\x00\x04\x00\x00abcde",
+ Container(len=9, proto=0x40000, payload=b"a"),
+ b"bcde",
+ id="1-byte payload and extra padding",
+ ),
+ pytest.param(
+ b"\x00\x00\x00\x0B\x00\x03\x00\x00hi\x00",
+ Container(len=11, proto=pq3.protocol(3, 0), payload=[b"hi"]),
+ b"",
+ id="implied parameter list when using proto version 3.0",
+ ),
+ ],
+)
+def test_Startup_parse(raw, expected, extra):
+ with io.BytesIO(raw) as stream:
+ actual = pq3.Startup.parse_stream(stream)
+
+ assert actual == expected
+ assert stream.read() == extra
+
+
[email protected](
+ "packet,expected_bytes",
+ [
+ pytest.param(
+ dict(),
+ b"\x00\x00\x00\x08\x00\x00\x00\x00",
+ id="nothing set",
+ ),
+ pytest.param(
+ dict(len=10, proto=0x12345678),
+ b"\x00\x00\x00\x0A\x12\x34\x56\x78\x00\x00",
+ id="len and proto set explicitly",
+ ),
+ pytest.param(
+ dict(proto=0x12345678),
+ b"\x00\x00\x00\x08\x12\x34\x56\x78",
+ id="implied len with no payload",
+ ),
+ pytest.param(
+ dict(proto=0x12345678, payload=b"abcd"),
+ b"\x00\x00\x00\x0C\x12\x34\x56\x78abcd",
+ id="implied len with payload",
+ ),
+ pytest.param(
+ dict(payload=[b""]),
+ b"\x00\x00\x00\x09\x00\x03\x00\x00\x00",
+ id="implied proto version 3 when sending parameters",
+ ),
+ pytest.param(
+ dict(payload=[b"hi", b""]),
+ b"\x00\x00\x00\x0C\x00\x03\x00\x00hi\x00\x00",
+ id="implied proto version 3 and len when sending more than one parameter",
+ ),
+ pytest.param(
+ dict(payload=dict(user="jsmith", database="postgres")),
+ b"\x00\x00\x00\x27\x00\x03\x00\x00user\x00jsmith\x00database\x00postgres\x00\x00",
+ id="auto-serialization of dict parameters",
+ ),
+ ],
+)
+def test_Startup_build(packet, expected_bytes):
+ actual = pq3.Startup.build(packet)
+ assert actual == expected_bytes
+
+
[email protected](
+ "raw,expected,extra",
+ [
+ pytest.param(
+ b"*\x00\x00\x00\x08abcd",
+ dict(type=b"*", len=8, payload=b"abcd"),
+ b"",
+ id="4-byte payload",
+ ),
+ pytest.param(
+ b"*\x00\x00\x00\x04",
+ dict(type=b"*", len=4, payload=b""),
+ b"",
+ id="no payload",
+ ),
+ pytest.param(
+ b"*\x00\x00\x00\x05xabcd",
+ dict(type=b"*", len=5, payload=b"x"),
+ b"abcd",
+ id="1-byte payload with extra padding",
+ ),
+ pytest.param(
+ b"R\x00\x00\x00\x08\x00\x00\x00\x00",
+ dict(
+ type=pq3.types.AuthnRequest,
+ len=8,
+ payload=dict(type=pq3.authn.OK, body=None),
+ ),
+ b"",
+ id="AuthenticationOk",
+ ),
+ pytest.param(
+ b"R\x00\x00\x00\x12\x00\x00\x00\x0AEXTERNAL\x00\x00",
+ dict(
+ type=pq3.types.AuthnRequest,
+ len=18,
+ payload=dict(type=pq3.authn.SASL, body=[b"EXTERNAL", b""]),
+ ),
+ b"",
+ id="AuthenticationSASL",
+ ),
+ pytest.param(
+ b"R\x00\x00\x00\x0D\x00\x00\x00\x0B12345",
+ dict(
+ type=pq3.types.AuthnRequest,
+ len=13,
+ payload=dict(type=pq3.authn.SASLContinue, body=b"12345"),
+ ),
+ b"",
+ id="AuthenticationSASLContinue",
+ ),
+ pytest.param(
+ b"R\x00\x00\x00\x0D\x00\x00\x00\x0C12345",
+ dict(
+ type=pq3.types.AuthnRequest,
+ len=13,
+ payload=dict(type=pq3.authn.SASLFinal, body=b"12345"),
+ ),
+ b"",
+ id="AuthenticationSASLFinal",
+ ),
+ pytest.param(
+ b"p\x00\x00\x00\x0Bhunter2",
+ dict(
+ type=pq3.types.PasswordMessage,
+ len=11,
+ payload=b"hunter2",
+ ),
+ b"",
+ id="PasswordMessage",
+ ),
+ pytest.param(
+ b"K\x00\x00\x00\x0C\x00\x00\x00\x00\x12\x34\x56\x78",
+ dict(
+ type=pq3.types.BackendKeyData,
+ len=12,
+ payload=dict(pid=0, key=0x12345678),
+ ),
+ b"",
+ id="BackendKeyData",
+ ),
+ pytest.param(
+ b"C\x00\x00\x00\x08SET\x00",
+ dict(
+ type=pq3.types.CommandComplete,
+ len=8,
+ payload=dict(tag=b"SET"),
+ ),
+ b"",
+ id="CommandComplete",
+ ),
+ pytest.param(
+ b"E\x00\x00\x00\x11Mbad!\x00Mdog!\x00\x00",
+ dict(type=b"E", len=17, payload=dict(fields=[b"Mbad!", b"Mdog!", b""])),
+ b"",
+ id="ErrorResponse",
+ ),
+ pytest.param(
+ b"S\x00\x00\x00\x08a\x00b\x00",
+ dict(
+ type=pq3.types.ParameterStatus,
+ len=8,
+ payload=dict(name=b"a", value=b"b"),
+ ),
+ b"",
+ id="ParameterStatus",
+ ),
+ pytest.param(
+ b"Z\x00\x00\x00\x05x",
+ dict(type=b"Z", len=5, payload=dict(status=b"x")),
+ b"",
+ id="ReadyForQuery",
+ ),
+ pytest.param(
+ b"Q\x00\x00\x00\x06!\x00",
+ dict(type=pq3.types.Query, len=6, payload=dict(query=b"!")),
+ b"",
+ id="Query",
+ ),
+ pytest.param(
+ b"D\x00\x00\x00\x0B\x00\x01\x00\x00\x00\x01!",
+ dict(type=pq3.types.DataRow, len=11, payload=dict(columns=[b"!"])),
+ b"",
+ id="DataRow",
+ ),
+ pytest.param(
+ b"D\x00\x00\x00\x06\x00\x00extra",
+ dict(type=pq3.types.DataRow, len=6, payload=dict(columns=[])),
+ b"extra",
+ id="DataRow with extra data",
+ ),
+ pytest.param(
+ b"I\x00\x00\x00\x04",
+ dict(type=pq3.types.EmptyQueryResponse, len=4, payload=None),
+ b"",
+ id="EmptyQueryResponse",
+ ),
+ pytest.param(
+ b"I\x00\x00\x00\x04\xFF",
+ dict(type=b"I", len=4, payload=None),
+ b"\xFF",
+ id="EmptyQueryResponse with extra bytes",
+ ),
+ pytest.param(
+ b"X\x00\x00\x00\x04",
+ dict(type=pq3.types.Terminate, len=4, payload=None),
+ b"",
+ id="Terminate",
+ ),
+ ],
+)
+def test_Pq3_parse(raw, expected, extra):
+ with io.BytesIO(raw) as stream:
+ actual = pq3.Pq3.parse_stream(stream)
+
+ assert actual == expected
+ assert stream.read() == extra
+
+
[email protected](
+ "fields,expected",
+ [
+ pytest.param(
+ dict(type=b"*", len=5),
+ b"*\x00\x00\x00\x05\x00",
+ id="type and len set explicitly",
+ ),
+ pytest.param(
+ dict(type=b"*"),
+ b"*\x00\x00\x00\x04",
+ id="implied len with no payload",
+ ),
+ pytest.param(
+ dict(type=b"*", payload=b"1234"),
+ b"*\x00\x00\x00\x081234",
+ id="implied len with payload",
+ ),
+ pytest.param(
+ dict(type=pq3.types.AuthnRequest, payload=dict(type=pq3.authn.OK)),
+ b"R\x00\x00\x00\x08\x00\x00\x00\x00",
+ id="implied len/type for AuthenticationOK",
+ ),
+ pytest.param(
+ dict(
+ type=pq3.types.AuthnRequest,
+ payload=dict(
+ type=pq3.authn.SASL,
+ body=[b"SCRAM-SHA-256-PLUS", b"SCRAM-SHA-256", b""],
+ ),
+ ),
+ b"R\x00\x00\x00\x2A\x00\x00\x00\x0ASCRAM-SHA-256-PLUS\x00SCRAM-SHA-256\x00\x00",
+ id="implied len/type for AuthenticationSASL",
+ ),
+ pytest.param(
+ dict(
+ type=pq3.types.AuthnRequest,
+ payload=dict(type=pq3.authn.SASLContinue, body=b"12345"),
+ ),
+ b"R\x00\x00\x00\x0D\x00\x00\x00\x0B12345",
+ id="implied len/type for AuthenticationSASLContinue",
+ ),
+ pytest.param(
+ dict(
+ type=pq3.types.AuthnRequest,
+ payload=dict(type=pq3.authn.SASLFinal, body=b"12345"),
+ ),
+ b"R\x00\x00\x00\x0D\x00\x00\x00\x0C12345",
+ id="implied len/type for AuthenticationSASLFinal",
+ ),
+ pytest.param(
+ dict(
+ type=pq3.types.PasswordMessage,
+ payload=b"hunter2",
+ ),
+ b"p\x00\x00\x00\x0Bhunter2",
+ id="implied len/type for PasswordMessage",
+ ),
+ pytest.param(
+ dict(type=pq3.types.BackendKeyData, payload=dict(pid=1, key=7)),
+ b"K\x00\x00\x00\x0C\x00\x00\x00\x01\x00\x00\x00\x07",
+ id="implied len/type for BackendKeyData",
+ ),
+ pytest.param(
+ dict(type=pq3.types.CommandComplete, payload=dict(tag=b"SET")),
+ b"C\x00\x00\x00\x08SET\x00",
+ id="implied len/type for CommandComplete",
+ ),
+ pytest.param(
+ dict(type=pq3.types.ErrorResponse, payload=dict(fields=[b"error", b""])),
+ b"E\x00\x00\x00\x0Berror\x00\x00",
+ id="implied len/type for ErrorResponse",
+ ),
+ pytest.param(
+ dict(type=pq3.types.ParameterStatus, payload=dict(name=b"a", value=b"b")),
+ b"S\x00\x00\x00\x08a\x00b\x00",
+ id="implied len/type for ParameterStatus",
+ ),
+ pytest.param(
+ dict(type=pq3.types.ReadyForQuery, payload=dict(status=b"I")),
+ b"Z\x00\x00\x00\x05I",
+ id="implied len/type for ReadyForQuery",
+ ),
+ pytest.param(
+ dict(type=pq3.types.Query, payload=dict(query=b"SELECT 1;")),
+ b"Q\x00\x00\x00\x0eSELECT 1;\x00",
+ id="implied len/type for Query",
+ ),
+ pytest.param(
+ dict(type=pq3.types.DataRow, payload=dict(columns=[b"abcd"])),
+ b"D\x00\x00\x00\x0E\x00\x01\x00\x00\x00\x04abcd",
+ id="implied len/type for DataRow",
+ ),
+ pytest.param(
+ dict(type=pq3.types.EmptyQueryResponse),
+ b"I\x00\x00\x00\x04",
+ id="implied len for EmptyQueryResponse",
+ ),
+ pytest.param(
+ dict(type=pq3.types.Terminate),
+ b"X\x00\x00\x00\x04",
+ id="implied len for Terminate",
+ ),
+ ],
+)
+def test_Pq3_build(fields, expected):
+ actual = pq3.Pq3.build(fields)
+ assert actual == expected
+
+
[email protected](
+ "raw,expected,extra",
+ [
+ pytest.param(
+ b"\x00\x00",
+ dict(columns=[]),
+ b"",
+ id="no columns",
+ ),
+ pytest.param(
+ b"\x00\x01\x00\x00\x00\x04abcd",
+ dict(columns=[b"abcd"]),
+ b"",
+ id="one column",
+ ),
+ pytest.param(
+ b"\x00\x02\x00\x00\x00\x04abcd\x00\x00\x00\x01x",
+ dict(columns=[b"abcd", b"x"]),
+ b"",
+ id="multiple columns",
+ ),
+ pytest.param(
+ b"\x00\x02\x00\x00\x00\x00\x00\x00\x00\x01x",
+ dict(columns=[b"", b"x"]),
+ b"",
+ id="empty column value",
+ ),
+ pytest.param(
+ b"\x00\x02\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
+ dict(columns=[None, None]),
+ b"",
+ id="null columns",
+ ),
+ ],
+)
+def test_DataRow_parse(raw, expected, extra):
+ pkt = b"D" + struct.pack("!i", len(raw) + 4) + raw
+ with io.BytesIO(pkt) as stream:
+ actual = pq3.Pq3.parse_stream(stream)
+
+ assert actual.type == pq3.types.DataRow
+ assert actual.payload == expected
+ assert stream.read() == extra
+
+
[email protected](
+ "fields,expected",
+ [
+ pytest.param(
+ dict(),
+ b"\x00\x00",
+ id="no columns",
+ ),
+ pytest.param(
+ dict(columns=[None, None]),
+ b"\x00\x02\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
+ id="null columns",
+ ),
+ ],
+)
+def test_DataRow_build(fields, expected):
+ actual = pq3.Pq3.build(dict(type=pq3.types.DataRow, payload=fields))
+
+ expected = b"D" + struct.pack("!i", len(expected) + 4) + expected
+ assert actual == expected
+
+
[email protected](
+ "raw,expected,exception",
+ [
+ pytest.param(
+ b"EXTERNAL\x00\xFF\xFF\xFF\xFF",
+ dict(name=b"EXTERNAL", len=-1, data=None),
+ None,
+ id="no initial response",
+ ),
+ pytest.param(
+ b"EXTERNAL\x00\x00\x00\x00\x02me",
+ dict(name=b"EXTERNAL", len=2, data=b"me"),
+ None,
+ id="initial response",
+ ),
+ pytest.param(
+ b"EXTERNAL\x00\x00\x00\x00\x02meextra",
+ None,
+ TerminatedError,
+ id="extra data",
+ ),
+ pytest.param(
+ b"EXTERNAL\x00\x00\x00\x00\xFFme",
+ None,
+ StreamError,
+ id="underflow",
+ ),
+ ],
+)
+def test_SASLInitialResponse_parse(raw, expected, exception):
+ ctx = contextlib.nullcontext()
+ if exception:
+ ctx = pytest.raises(exception)
+
+ with ctx:
+ actual = pq3.SASLInitialResponse.parse(raw)
+ assert actual == expected
+
+
[email protected](
+ "fields,expected",
+ [
+ pytest.param(
+ dict(name=b"EXTERNAL"),
+ b"EXTERNAL\x00\xFF\xFF\xFF\xFF",
+ id="no initial response",
+ ),
+ pytest.param(
+ dict(name=b"EXTERNAL", data=None),
+ b"EXTERNAL\x00\xFF\xFF\xFF\xFF",
+ id="no initial response (explicit None)",
+ ),
+ pytest.param(
+ dict(name=b"EXTERNAL", data=b""),
+ b"EXTERNAL\x00\x00\x00\x00\x00",
+ id="empty response",
+ ),
+ pytest.param(
+ dict(name=b"EXTERNAL", data=b"[email protected]"),
+ b"EXTERNAL\x00\x00\x00\x00\[email protected]",
+ id="initial response",
+ ),
+ pytest.param(
+ dict(name=b"EXTERNAL", len=2, data=b"[email protected]"),
+ b"EXTERNAL\x00\x00\x00\x00\[email protected]",
+ id="data overflow",
+ ),
+ pytest.param(
+ dict(name=b"EXTERNAL", len=14, data=b"me"),
+ b"EXTERNAL\x00\x00\x00\x00\x0Eme",
+ id="data underflow",
+ ),
+ ],
+)
+def test_SASLInitialResponse_build(fields, expected):
+ actual = pq3.SASLInitialResponse.build(fields)
+ assert actual == expected
+
+
[email protected](
+ "version,expected_bytes",
+ [
+ pytest.param((3, 0), b"\x00\x03\x00\x00", id="version 3"),
+ pytest.param((1234, 5679), b"\x04\xd2\x16\x2f", id="SSLRequest"),
+ ],
+)
+def test_protocol(version, expected_bytes):
+ # Make sure the integer returned by protocol is correctly serialized on the
+ # wire.
+ assert struct.pack("!i", pq3.protocol(*version)) == expected_bytes
+
+
[email protected](
+ "envvar,func,expected",
+ [
+ ("PGHOST", pq3.pghost, "localhost"),
+ ("PGPORT", pq3.pgport, 5432),
+ ("PGUSER", pq3.pguser, getpass.getuser()),
+ ("PGDATABASE", pq3.pgdatabase, "postgres"),
+ ],
+)
+def test_env_defaults(monkeypatch, envvar, func, expected):
+ monkeypatch.delenv(envvar, raising=False)
+
+ actual = func()
+ assert actual == expected
+
+
[email protected](
+ "envvars,func,expected",
+ [
+ (dict(PGHOST="otherhost"), pq3.pghost, "otherhost"),
+ (dict(PGPORT="6789"), pq3.pgport, 6789),
+ (dict(PGUSER="postgres"), pq3.pguser, "postgres"),
+ (dict(PGDATABASE="template1"), pq3.pgdatabase, "template1"),
+ ],
+)
+def test_env(monkeypatch, envvars, func, expected):
+ for k, v in envvars.items():
+ monkeypatch.setenv(k, v)
+
+ actual = func()
+ assert actual == expected
diff --git a/src/test/python/tls.py b/src/test/python/tls.py
new file mode 100644
index 0000000000..075c02c1ca
--- /dev/null
+++ b/src/test/python/tls.py
@@ -0,0 +1,195 @@
+#
+# Copyright 2021 VMware, Inc.
+# SPDX-License-Identifier: PostgreSQL
+#
+
+from construct import *
+
+#
+# TLS 1.3
+#
+# Most of the types below are transcribed from RFC 8446:
+#
+# https://tools.ietf.org/html/rfc8446
+#
+
+
+def _Vector(size_field, element):
+ return Prefixed(size_field, GreedyRange(element))
+
+
+# Alerts
+
+AlertLevel = Enum(
+ Byte,
+ warning=1,
+ fatal=2,
+)
+
+AlertDescription = Enum(
+ Byte,
+ close_notify=0,
+ unexpected_message=10,
+ bad_record_mac=20,
+ decryption_failed_RESERVED=21,
+ record_overflow=22,
+ decompression_failure=30,
+ handshake_failure=40,
+ no_certificate_RESERVED=41,
+ bad_certificate=42,
+ unsupported_certificate=43,
+ certificate_revoked=44,
+ certificate_expired=45,
+ certificate_unknown=46,
+ illegal_parameter=47,
+ unknown_ca=48,
+ access_denied=49,
+ decode_error=50,
+ decrypt_error=51,
+ export_restriction_RESERVED=60,
+ protocol_version=70,
+ insufficient_security=71,
+ internal_error=80,
+ user_canceled=90,
+ no_renegotiation=100,
+ unsupported_extension=110,
+)
+
+Alert = Struct(
+ "level" / AlertLevel,
+ "description" / AlertDescription,
+)
+
+
+# Extensions
+
+ExtensionType = Enum(
+ Int16ub,
+ server_name=0,
+ max_fragment_length=1,
+ status_request=5,
+ supported_groups=10,
+ signature_algorithms=13,
+ use_srtp=14,
+ heartbeat=15,
+ application_layer_protocol_negotiation=16,
+ signed_certificate_timestamp=18,
+ client_certificate_type=19,
+ server_certificate_type=20,
+ padding=21,
+ pre_shared_key=41,
+ early_data=42,
+ supported_versions=43,
+ cookie=44,
+ psk_key_exchange_modes=45,
+ certificate_authorities=47,
+ oid_filters=48,
+ post_handshake_auth=49,
+ signature_algorithms_cert=50,
+ key_share=51,
+)
+
+Extension = Struct(
+ "extension_type" / ExtensionType,
+ "extension_data" / Prefixed(Int16ub, GreedyBytes),
+)
+
+
+# ClientHello
+
+
+class _CipherSuiteAdapter(Adapter):
+ class _hextuple(tuple):
+ def __repr__(self):
+ return f"(0x{self[0]:02X}, 0x{self[1]:02X})"
+
+ def _encode(self, obj, context, path):
+ return bytes(obj)
+
+ def _decode(self, obj, context, path):
+ assert len(obj) == 2
+ return self._hextuple(obj)
+
+
+ProtocolVersion = Hex(Int16ub)
+
+Random = Hex(Bytes(32))
+
+CipherSuite = _CipherSuiteAdapter(Byte[2])
+
+ClientHello = Struct(
+ "legacy_version" / ProtocolVersion,
+ "random" / Random,
+ "legacy_session_id" / Prefixed(Byte, Hex(GreedyBytes)),
+ "cipher_suites" / _Vector(Int16ub, CipherSuite),
+ "legacy_compression_methods" / Prefixed(Byte, GreedyBytes),
+ "extensions" / _Vector(Int16ub, Extension),
+)
+
+# ServerHello
+
+ServerHello = Struct(
+ "legacy_version" / ProtocolVersion,
+ "random" / Random,
+ "legacy_session_id_echo" / Prefixed(Byte, Hex(GreedyBytes)),
+ "cipher_suite" / CipherSuite,
+ "legacy_compression_method" / Hex(Byte),
+ "extensions" / _Vector(Int16ub, Extension),
+)
+
+# Handshake
+
+HandshakeType = Enum(
+ Byte,
+ client_hello=1,
+ server_hello=2,
+ new_session_ticket=4,
+ end_of_early_data=5,
+ encrypted_extensions=8,
+ certificate=11,
+ certificate_request=13,
+ certificate_verify=15,
+ finished=20,
+ key_update=24,
+ message_hash=254,
+)
+
+Handshake = Struct(
+ "msg_type" / HandshakeType,
+ "length" / Int24ub,
+ "payload"
+ / Switch(
+ this.msg_type,
+ {
+ HandshakeType.client_hello: ClientHello,
+ HandshakeType.server_hello: ServerHello,
+ # HandshakeType.end_of_early_data: EndOfEarlyData,
+ # HandshakeType.encrypted_extensions: EncryptedExtensions,
+ # HandshakeType.certificate_request: CertificateRequest,
+ # HandshakeType.certificate: Certificate,
+ # HandshakeType.certificate_verify: CertificateVerify,
+ # HandshakeType.finished: Finished,
+ # HandshakeType.new_session_ticket: NewSessionTicket,
+ # HandshakeType.key_update: KeyUpdate,
+ },
+ default=FixedSized(this.length, GreedyBytes),
+ ),
+)
+
+# Records
+
+ContentType = Enum(
+ Byte,
+ invalid=0,
+ change_cipher_spec=20,
+ alert=21,
+ handshake=22,
+ application_data=23,
+)
+
+Plaintext = Struct(
+ "type" / ContentType,
+ "legacy_record_version" / ProtocolVersion,
+ "length" / Int16ub,
+ "fragment" / FixedSized(this.length, GreedyBytes),
+)
--
2.25.1
[text/x-patch] v4-0010-contrib-oauth-switch-to-pluggable-auth-API.patch (17.1K, ../../[email protected]/11-v4-0010-contrib-oauth-switch-to-pluggable-auth-API.patch)
download | inline diff:
From f520c08e1aee5239051e304c8a8faf5cb25bdbf2 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Fri, 25 Mar 2022 16:35:30 -0700
Subject: [PATCH v4 10/10] contrib/oauth: switch to pluggable auth API
Move the core server implementation to contrib/oauth as a pluggable
provider, using the RegisterAuthProvider() API. oauth_validator_command
has been moved from core to a custom GUC. HBA options are handled
using the new hook. Tests have been updated to handle the new
implementations.
One server modification remains: allowing custom SASL mechanisms to
declare their own maximum message length.
This patch is optional; you can apply/revert it to compare the two
approaches.
---
contrib/oauth/Makefile | 16 ++++
.../auth-oauth.c => contrib/oauth/oauth.c | 88 ++++++++++++++++---
src/backend/libpq/Makefile | 1 -
src/backend/libpq/auth.c | 7 --
src/backend/libpq/hba.c | 27 +-----
src/backend/utils/misc/guc.c | 12 ---
src/include/libpq/hba.h | 6 +-
src/include/libpq/oauth.h | 24 -----
src/test/python/README | 3 +-
src/test/python/server/test_oauth.py | 20 ++---
10 files changed, 104 insertions(+), 100 deletions(-)
create mode 100644 contrib/oauth/Makefile
rename src/backend/libpq/auth-oauth.c => contrib/oauth/oauth.c (90%)
delete mode 100644 src/include/libpq/oauth.h
diff --git a/contrib/oauth/Makefile b/contrib/oauth/Makefile
new file mode 100644
index 0000000000..880bc1fef3
--- /dev/null
+++ b/contrib/oauth/Makefile
@@ -0,0 +1,16 @@
+# contrib/oauth/Makefile
+
+MODULE_big = oauth
+OBJS = oauth.o
+PGFILEDESC = "oauth - auth provider supporting OAuth 2.0/OIDC"
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/oauth
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/backend/libpq/auth-oauth.c b/contrib/oauth/oauth.c
similarity index 90%
rename from src/backend/libpq/auth-oauth.c
rename to contrib/oauth/oauth.c
index c1232a31a0..e83f3c5d99 100644
--- a/src/backend/libpq/auth-oauth.c
+++ b/contrib/oauth/oauth.c
@@ -1,33 +1,39 @@
-/*-------------------------------------------------------------------------
+/* -------------------------------------------------------------------------
*
- * auth-oauth.c
+ * oauth.c
* Server-side implementation of the SASL OAUTHBEARER mechanism.
*
* See the following RFC for more details:
* - RFC 7628: https://tools.ietf.org/html/rfc7628
*
- * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * src/backend/libpq/auth-oauth.c
+ * contrib/oauth/oauth.c
*
- *-------------------------------------------------------------------------
+ * -------------------------------------------------------------------------
*/
+
#include "postgres.h"
#include <unistd.h>
#include <fcntl.h>
#include "common/oauth-common.h"
+#include "fmgr.h"
#include "lib/stringinfo.h"
#include "libpq/auth.h"
#include "libpq/hba.h"
-#include "libpq/oauth.h"
#include "libpq/sasl.h"
#include "storage/fd.h"
+#include "utils/guc.h"
+
+PG_MODULE_MAGIC;
+
+void _PG_init(void);
/* GUC */
-char *oauth_validator_command;
+static char *oauth_validator_command;
static void oauth_get_mechanisms(Port *port, StringInfo buf);
static void *oauth_init(Port *port, const char *selected_mech, const char *shadow_pass);
@@ -35,7 +41,7 @@ static int oauth_exchange(void *opaq, const char *input, int inputlen,
char **output, int *outputlen, const char **logdetail);
/* Mechanism declaration */
-const pg_be_sasl_mech pg_be_oauth_mech = {
+static const pg_be_sasl_mech oauth_mech = {
oauth_get_mechanisms,
oauth_init,
oauth_exchange,
@@ -57,12 +63,13 @@ struct oauth_ctx
Port *port;
const char *issuer;
const char *scope;
+ bool skip_usermap;
};
static char *sanitize_char(char c);
static char *parse_kvpairs_for_auth(char **input);
static void generate_error_response(struct oauth_ctx *ctx, char **output, int *outputlen);
-static bool validate(Port *port, const char *auth, const char **logdetail);
+static bool validate(struct oauth_ctx *ctx, const char *auth, const char **logdetail);
static bool run_validator_command(Port *port, const char *token);
static bool check_exit(FILE **fh, const char *command);
static bool unset_cloexec(int fd);
@@ -84,6 +91,7 @@ static void *
oauth_init(Port *port, const char *selected_mech, const char *shadow_pass)
{
struct oauth_ctx *ctx;
+ ListCell *lc;
if (strcmp(selected_mech, OAUTHBEARER_NAME))
ereport(ERROR,
@@ -96,8 +104,21 @@ oauth_init(Port *port, const char *selected_mech, const char *shadow_pass)
ctx->port = port;
Assert(port->hba);
- ctx->issuer = port->hba->oauth_issuer;
- ctx->scope = port->hba->oauth_scope;
+
+ foreach (lc, port->hba->custom_auth_options)
+ {
+ CustomOption *option = lfirst(lc);
+
+ if (strcmp(option->name, "issuer") == 0)
+ ctx->issuer = option->value;
+ else if (strcmp(option->name, "scope") == 0)
+ ctx->scope = option->value;
+ else if (strcmp(option->name, "trust_validator_authz") == 0)
+ {
+ if (strcmp(option->value, "1") == 0)
+ ctx->skip_usermap = true;
+ }
+ }
return ctx;
}
@@ -248,7 +269,7 @@ oauth_exchange(void *opaq, const char *input, int inputlen,
errmsg("malformed OAUTHBEARER message"),
errdetail("Message contains additional data after the final terminator.")));
- if (!validate(ctx->port, auth, logdetail))
+ if (!validate(ctx, auth, logdetail))
{
generate_error_response(ctx, output, outputlen);
@@ -415,12 +436,13 @@ generate_error_response(struct oauth_ctx *ctx, char **output, int *outputlen)
}
static bool
-validate(Port *port, const char *auth, const char **logdetail)
+validate(struct oauth_ctx *ctx, const char *auth, const char **logdetail)
{
static const char * const b64_set = "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789-._~+/";
+ Port *port = ctx->port;
const char *token;
size_t span;
int ret;
@@ -497,7 +519,7 @@ validate(Port *port, const char *auth, const char **logdetail)
if (!run_validator_command(port, token))
return false;
- if (port->hba->oauth_skip_usermap)
+ if (ctx->skip_usermap)
{
/*
* If the validator is our authorization authority, we're done.
@@ -795,3 +817,41 @@ username_ok_for_shell(const char *username)
return true;
}
+
+static int CheckOAuth(Port *port)
+{
+ return CheckSASLAuth(&oauth_mech, port, NULL, NULL);
+}
+
+static const char *OAuthError(Port *port)
+{
+ return psprintf("OAuth bearer authentication failed for user \"%s\"",
+ port->user_name);
+}
+
+static bool OAuthCheckOption(char *name, char *val,
+ struct HbaLine *hbaline, char **errmsg)
+{
+ if (!strcmp(name, "issuer"))
+ return true;
+ if (!strcmp(name, "scope"))
+ return true;
+ if (!strcmp(name, "trust_validator_authz"))
+ return true;
+
+ return false;
+}
+
+void
+_PG_init(void)
+{
+ RegisterAuthProvider("oauth", CheckOAuth, OAuthError, OAuthCheckOption);
+
+ DefineCustomStringVariable("oauth.validator_command",
+ gettext_noop("Command to validate OAuth v2 bearer tokens."),
+ NULL,
+ &oauth_validator_command,
+ "",
+ PGC_SIGHUP, GUC_SUPERUSER_ONLY,
+ NULL, NULL, NULL);
+}
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 98eb2a8242..6d385fd6a4 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -15,7 +15,6 @@ include $(top_builddir)/src/Makefile.global
# be-fsstubs is here for historical reasons, probably belongs elsewhere
OBJS = \
- auth-oauth.o \
auth-sasl.o \
auth-scram.o \
auth.o \
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 17042d84ad..4a8a63922a 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -30,7 +30,6 @@
#include "libpq/auth.h"
#include "libpq/crypt.h"
#include "libpq/libpq.h"
-#include "libpq/oauth.h"
#include "libpq/pqformat.h"
#include "libpq/sasl.h"
#include "libpq/scram.h"
@@ -299,9 +298,6 @@ auth_failed(Port *port, int status, const char *logdetail)
case uaRADIUS:
errstr = gettext_noop("RADIUS authentication failed for user \"%s\"");
break;
- case uaOAuth:
- errstr = gettext_noop("OAuth bearer authentication failed for user \"%s\"");
- break;
case uaCustom:
{
CustomAuthProvider *provider = get_provider_by_name(port->hba->custom_provider);
@@ -630,9 +626,6 @@ ClientAuthentication(Port *port)
case uaTrust:
status = STATUS_OK;
break;
- case uaOAuth:
- status = CheckSASLAuth(&pg_be_oauth_mech, port, NULL, NULL);
- break;
case uaCustom:
{
CustomAuthProvider *provider = get_provider_by_name(port->hba->custom_provider);
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index cd3b1cc140..6bf986d5b3 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -137,7 +137,6 @@ static const char *const UserAuthName[] =
"radius",
"custom",
"peer",
- "oauth",
};
@@ -1402,8 +1401,6 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
#endif
else if (strcmp(token->string, "radius") == 0)
parsedline->auth_method = uaRADIUS;
- else if (strcmp(token->string, "oauth") == 0)
- parsedline->auth_method = uaOAuth;
else if (strcmp(token->string, "custom") == 0)
parsedline->auth_method = uaCustom;
else
@@ -1733,9 +1730,8 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
hbaline->auth_method != uaGSS &&
hbaline->auth_method != uaSSPI &&
hbaline->auth_method != uaCert &&
- hbaline->auth_method != uaOAuth &&
hbaline->auth_method != uaCustom)
- INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, gssapi, sspi, cert, oauth, and custom"));
+ INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, gssapi, sspi, cert, and custom"));
hbaline->usermap = pstrdup(val);
}
else if (strcmp(name, "clientcert") == 0)
@@ -2119,27 +2115,6 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
hbaline->radiusidentifiers = parsed_identifiers;
hbaline->radiusidentifiers_s = pstrdup(val);
}
- else if (strcmp(name, "issuer") == 0)
- {
- if (hbaline->auth_method != uaOAuth)
- INVALID_AUTH_OPTION("issuer", gettext_noop("oauth"));
- hbaline->oauth_issuer = pstrdup(val);
- }
- else if (strcmp(name, "scope") == 0)
- {
- if (hbaline->auth_method != uaOAuth)
- INVALID_AUTH_OPTION("scope", gettext_noop("oauth"));
- hbaline->oauth_scope = pstrdup(val);
- }
- else if (strcmp(name, "trust_validator_authz") == 0)
- {
- if (hbaline->auth_method != uaOAuth)
- INVALID_AUTH_OPTION("trust_validator_authz", gettext_noop("oauth"));
- if (strcmp(val, "1") == 0)
- hbaline->oauth_skip_usermap = true;
- else
- hbaline->oauth_skip_usermap = false;
- }
else if (strcmp(name, "provider") == 0)
{
REQUIRE_AUTH_OPTION(uaCustom, "provider", "custom");
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 9a5b2aa496..f70f7f5c01 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -59,7 +59,6 @@
#include "libpq/auth.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
-#include "libpq/oauth.h"
#include "miscadmin.h"
#include "optimizer/cost.h"
#include "optimizer/geqo.h"
@@ -4667,17 +4666,6 @@ static struct config_string ConfigureNamesString[] =
check_backtrace_functions, assign_backtrace_functions, NULL
},
- {
- {"oauth_validator_command", PGC_SIGHUP, CONN_AUTH_AUTH,
- gettext_noop("Command to validate OAuth v2 bearer tokens."),
- NULL,
- GUC_SUPERUSER_ONLY
- },
- &oauth_validator_command,
- "",
- NULL, NULL, NULL
- },
-
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index e405103a2e..bbc94363cb 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -40,8 +40,7 @@ typedef enum UserAuth
uaRADIUS,
uaCustom,
uaPeer,
- uaOAuth
-#define USER_AUTH_LAST uaOAuth /* Must be last value of this enum */
+#define USER_AUTH_LAST uaPeer /* Must be last value of this enum */
} UserAuth;
/*
@@ -129,9 +128,6 @@ typedef struct HbaLine
char *radiusidentifiers_s;
List *radiusports;
char *radiusports_s;
- char *oauth_issuer;
- char *oauth_scope;
- bool oauth_skip_usermap;
char *custom_provider;
List *custom_auth_options;
} HbaLine;
diff --git a/src/include/libpq/oauth.h b/src/include/libpq/oauth.h
deleted file mode 100644
index 870e426af1..0000000000
--- a/src/include/libpq/oauth.h
+++ /dev/null
@@ -1,24 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * oauth.h
- * Interface to libpq/auth-oauth.c
- *
- * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * src/include/libpq/oauth.h
- *
- *-------------------------------------------------------------------------
- */
-#ifndef PG_OAUTH_H
-#define PG_OAUTH_H
-
-#include "libpq/libpq-be.h"
-#include "libpq/sasl.h"
-
-extern char *oauth_validator_command;
-
-/* Implementation */
-extern const pg_be_sasl_mech pg_be_oauth_mech;
-
-#endif /* PG_OAUTH_H */
diff --git a/src/test/python/README b/src/test/python/README
index 0bda582c4b..0fbc1046cf 100644
--- a/src/test/python/README
+++ b/src/test/python/README
@@ -13,7 +13,8 @@ but you can adjust as needed for your setup.
## Requirements
-A supported version (3.6+) of Python.
+- A supported version (3.6+) of Python.
+- The oauth extension must be installed and loaded via shared_preload_libraries.
The first run of
diff --git a/src/test/python/server/test_oauth.py b/src/test/python/server/test_oauth.py
index cb5ca7fa23..07fc25edc2 100644
--- a/src/test/python/server/test_oauth.py
+++ b/src/test/python/server/test_oauth.py
@@ -103,9 +103,9 @@ def oauth_ctx():
ctx = Context()
hba_lines = (
- f'host {ctx.dbname} {ctx.map_user} samehost oauth issuer="{ctx.issuer}" scope="{ctx.scope}" map=oauth\n',
- f'host {ctx.dbname} {ctx.authz_user} samehost oauth issuer="{ctx.issuer}" scope="{ctx.scope}" trust_validator_authz=1\n',
- f'host {ctx.dbname} all samehost oauth issuer="{ctx.issuer}" scope="{ctx.scope}"\n',
+ f'host {ctx.dbname} {ctx.map_user} samehost custom provider=oauth issuer="{ctx.issuer}" scope="{ctx.scope}" map=oauth\n',
+ f'host {ctx.dbname} {ctx.authz_user} samehost custom provider=oauth issuer="{ctx.issuer}" scope="{ctx.scope}" trust_validator_authz=1\n',
+ f'host {ctx.dbname} all samehost custom provider=oauth issuer="{ctx.issuer}" scope="{ctx.scope}"\n',
)
ident_lines = (r"oauth /^(.*)@example\.com$ \1",)
@@ -126,12 +126,12 @@ def oauth_ctx():
c.execute(sql.SQL("CREATE ROLE {} LOGIN;").format(authz_user))
c.execute(sql.SQL("CREATE DATABASE {};").format(dbname))
- # Make this test script the server's oauth_validator.
+ # Make this test script the server's oauth validator.
path = pathlib.Path(__file__).parent / "validate_bearer.py"
path = str(path.absolute())
cmd = f"{shlex.quote(path)} {SHARED_MEM_NAME} <&%f"
- c.execute("ALTER SYSTEM SET oauth_validator_command TO %s;", (cmd,))
+ c.execute("ALTER SYSTEM SET oauth.validator_command TO %s;", (cmd,))
# Replace pg_hba and pg_ident.
c.execute("SHOW hba_file;")
@@ -149,7 +149,7 @@ def oauth_ctx():
# Put things back the way they were.
c.execute("SELECT pg_reload_conf();")
- c.execute("ALTER SYSTEM RESET oauth_validator_command;")
+ c.execute("ALTER SYSTEM RESET oauth.validator_command;")
c.execute(sql.SQL("DROP DATABASE {};").format(dbname))
c.execute(sql.SQL("DROP ROLE {};").format(authz_user))
c.execute(sql.SQL("DROP ROLE {};").format(map_user))
@@ -930,7 +930,7 @@ def test_oauth_empty_initial_response(conn, oauth_ctx, bearer_token):
def set_validator():
"""
A per-test fixture that allows a test to override the setting of
- oauth_validator_command for the cluster. The setting will be reverted during
+ oauth.validator_command for the cluster. The setting will be reverted during
teardown.
Passing None will perform an ALTER SYSTEM RESET.
@@ -942,17 +942,17 @@ def set_validator():
c = conn.cursor()
# Save the previous value.
- c.execute("SHOW oauth_validator_command;")
+ c.execute("SHOW oauth.validator_command;")
prev_cmd = c.fetchone()[0]
def setter(cmd):
- c.execute("ALTER SYSTEM SET oauth_validator_command TO %s;", (cmd,))
+ c.execute("ALTER SYSTEM SET oauth.validator_command TO %s;", (cmd,))
c.execute("SELECT pg_reload_conf();")
yield setter
# Restore the previous value.
- c.execute("ALTER SYSTEM SET oauth_validator_command TO %s;", (prev_cmd,))
+ c.execute("ALTER SYSTEM SET oauth.validator_command TO %s;", (prev_cmd,))
c.execute("SELECT pg_reload_conf();")
--
2.25.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2022-09-23 22:39 ` Jacob Champion <[email protected]>
2022-09-27 01:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2022-09-23 22:39 UTC (permalink / raw)
To: pgsql-hackers; +Cc: [email protected] <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; mahendrakar s <[email protected]>; Andrey Chudnovsky <[email protected]>
On Fri, Mar 25, 2022 at 5:00 PM Jacob Champion <[email protected]> wrote:
> v4 rebases over the latest version of the pluggable auth patchset
> (included as 0001-4). Note that there's a recent conflict as
> of d4781d887; use an older commit as the base (or wait for the other
> thread to be updated).
Here's a newly rebased v5. (They're all zipped now, which I probably
should have done a while back, sorry.)
- As before, 0001-4 are the pluggable auth set; they've now diverged
from the official version over on the other thread [1].
- I'm not sure that 0005 is still completely coherent after the
rebase, given the recent changes to jsonapi.c. But for now, the tests
are green, and that should be enough to keep the conversation going.
- 0008 will hopefully be obsoleted when the SYSTEM_USER proposal [2] lands.
Thanks,
--Jacob
[1] https://www.postgresql.org/message-id/CAJxrbyxgFzfqby%2BVRCkeAhJnwVZE50%2BZLPx0JT2TDg9LbZtkCg%40mail...
[2] https://www.postgresql.org/message-id/flat/[email protected]
Attachments:
[application/gzip] v5-0004-Add-support-for-map-and-custom-auth-options.patch.gz (3.7K, ../../CAAWbhmiWudPQk2euOQQPPa=o14zCN9U_qLwU1pShKO4A-F9yeA@mail.gmail.com/2-v5-0004-Add-support-for-map-and-custom-auth-options.patch.gz)
download
[application/gzip] v5-0001-Add-support-for-custom-authentication-methods.patch.gz (3.9K, ../../CAAWbhmiWudPQk2euOQQPPa=o14zCN9U_qLwU1pShKO4A-F9yeA@mail.gmail.com/3-v5-0001-Add-support-for-custom-authentication-methods.patch.gz)
download
[application/gzip] v5-0002-Add-sample-extension-to-test-custom-auth-provider.patch.gz (1.6K, ../../CAAWbhmiWudPQk2euOQQPPa=o14zCN9U_qLwU1pShKO4A-F9yeA@mail.gmail.com/4-v5-0002-Add-sample-extension-to-test-custom-auth-provider.patch.gz)
download
[application/gzip] v5-0005-common-jsonapi-support-FRONTEND-clients.patch.gz (6.3K, ../../CAAWbhmiWudPQk2euOQQPPa=o14zCN9U_qLwU1pShKO4A-F9yeA@mail.gmail.com/5-v5-0005-common-jsonapi-support-FRONTEND-clients.patch.gz)
download
[application/gzip] v5-0003-Add-tests-for-test_auth_provider-extension.patch.gz (2.2K, ../../CAAWbhmiWudPQk2euOQQPPa=o14zCN9U_qLwU1pShKO4A-F9yeA@mail.gmail.com/6-v5-0003-Add-tests-for-test_auth_provider-extension.patch.gz)
download
[application/gzip] v5-0006-libpq-add-OAUTHBEARER-SASL-mechanism.patch.gz (10.4K, ../../CAAWbhmiWudPQk2euOQQPPa=o14zCN9U_qLwU1pShKO4A-F9yeA@mail.gmail.com/7-v5-0006-libpq-add-OAUTHBEARER-SASL-mechanism.patch.gz)
download
[application/gzip] v5-0009-Add-pytest-suite-for-OAuth.patch.gz (28.5K, ../../CAAWbhmiWudPQk2euOQQPPa=o14zCN9U_qLwU1pShKO4A-F9yeA@mail.gmail.com/8-v5-0009-Add-pytest-suite-for-OAuth.patch.gz)
download
[application/gzip] v5-0010-contrib-oauth-switch-to-pluggable-auth-API.patch.gz (5.3K, ../../CAAWbhmiWudPQk2euOQQPPa=o14zCN9U_qLwU1pShKO4A-F9yeA@mail.gmail.com/9-v5-0010-contrib-oauth-switch-to-pluggable-auth-API.patch.gz)
download
[application/gzip] v5-0008-Add-a-very-simple-authn_id-extension.patch.gz (1.2K, ../../CAAWbhmiWudPQk2euOQQPPa=o14zCN9U_qLwU1pShKO4A-F9yeA@mail.gmail.com/10-v5-0008-Add-a-very-simple-authn_id-extension.patch.gz)
download
[application/gzip] v5-0007-backend-add-OAUTHBEARER-SASL-mechanism.patch.gz (11.4K, ../../CAAWbhmiWudPQk2euOQQPPa=o14zCN9U_qLwU1pShKO4A-F9yeA@mail.gmail.com/11-v5-0007-backend-add-OAUTHBEARER-SASL-mechanism.patch.gz)
download
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2022-09-27 01:39 ` Andrey Chudnovsky <[email protected]>
2022-09-27 21:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Andrey Chudnovsky @ 2022-09-27 01:39 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: pgsql-hackers; [email protected] <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; mahendrakar s <[email protected]>
>>> Libpq passing toked directly from an upstream client is useful in other scenarios:
>>> 1. Enterprise clients, built with .Net / Java and using provider-specific authentication libraries, like MSAL for AAD. Those can also support more advanced provider-specific token acquisition flows.
> I can see that providing a token directly would help you work around
> limitations in libpq's "standard" OAuth flows, whether we use iddawc or
> not. And it's cheap in terms of implementation. But I have a feeling it
> would fall apart rapidly with error cases, where the server is giving
> libpq information via the OAUTHBEARER mechanism, but libpq can only
> communicate to your wrapper through human-readable error messages on stderr.
For the providing token directly, that would be primarily used for
scenarios where the same party controls both the server and the client
side wrapper.
I.e. The client knows how to get a token for a particular principal
and doesn't need any additional information other than human readable
messages.
Please clarify the scenarios where you see this falling apart.
I can provide an example in the cloud world. We (Azure) as well as
other providers offer ways to obtain OAUTH tokens for
Service-to-Service communication at IAAS / PAAS level.
on Azure "Managed Identity" feature integrated in Compute VM allows a
client to make a local http call to get a token. VM itself manages the
certificate livecycle, as well as implements the corresponding OAUTH
flow.
This capability is used by both our 1st party PAAS offerings, as well
as 3rd party services deploying on VMs or managed K8S clusters.
Here, the client doesn't need libpq assistance in obtaining the token.
> This seems like clear motivation for client-side SASL plugins (which
> were also discussed on Samay's proposal thread). That's a lot more
> expensive to implement in libpq, but if it were hypothetically
> available, wouldn't you rather your provider-specific code be able to
> speak OAUTHBEARER directly with the server?
I generally agree that pluggable auth layers in libpq could be
beneficial. However, as you pointed out in Samay's thread, that would
require a new distribution model for libpq / clients to optionally
include provider-specific logic.
My optimistic plan here would be to implement several core OAUTH flows
in libpq core which would be generic enough to support major
enterprise OAUTH providers:
1. Client Credentials flow (Client_id + Client_secret) for backend applications.
2. Authorization Code Flow with PKCE and/or Device code flow for GUI
applications.
(2.) above would require a protocol between libpq and upstream clients
to exchange several messages.
Your patch includes a way for libpq to deliver to the client a message
about the next authentication steps, so planned to build on top of
that.
A little about scenarios, we look at.
What we're trying to achieve here is an easy integration path for
multiple players in the ecosystem:
- Managed PaaS Postgres providers (both us and multi-cloud solutions)
- SaaS providers deploying postgres on IaaS/PaaS providers' clouds
- Tools - pg_admin, psql and other ones.
- BI, ETL, Federation and other scenarios where postgres is used as
the data source.
If we can offer a provider agnostic solution for Backend <=> libpq <=>
Upstreal client path, we can have all players above build support for
OAUTH credentials, managed by the cloud provider of their choice.
For us, that would mean:
- Better administrator experience with pg_admin / psql handling of the
AAD (Azure Active Directory) authentication flows.
- Path for integration solutions using Postgres to build AAD
authentication in their management experience.
- Ability to use AAD identity provider for any Postgres deployments
other than our 1st party PaaS offering.
- Ability to offer github as the identity provider for PaaS Postgres offering.
Other players in the ecosystem above would be able to get the same benefits.
Does that make sense and possible without provider specific libpq plugin?
-------------------------
On resource constrained scenarios.
> I want to dig into this much more; resource-constrained systems are near
> and dear to me. I can see two cases here:
I just referred to the ability to compile libpq without extra
dependencies to save some kilobytes.
Not sure if OAUTH is widely used in those cases. It involves overhead
anyway, and requires the device to talk to an additional party (OAUTH
provider).
Likely Cert authentication is easier.
If needed, it can get libpq with full OAUTH support and use a client
code. But I didn't think about this scenario.
On Fri, Sep 23, 2022 at 3:39 PM Jacob Champion <[email protected]> wrote:
>
> On Fri, Mar 25, 2022 at 5:00 PM Jacob Champion <[email protected]> wrote:
> > v4 rebases over the latest version of the pluggable auth patchset
> > (included as 0001-4). Note that there's a recent conflict as
> > of d4781d887; use an older commit as the base (or wait for the other
> > thread to be updated).
>
> Here's a newly rebased v5. (They're all zipped now, which I probably
> should have done a while back, sorry.)
>
> - As before, 0001-4 are the pluggable auth set; they've now diverged
> from the official version over on the other thread [1].
> - I'm not sure that 0005 is still completely coherent after the
> rebase, given the recent changes to jsonapi.c. But for now, the tests
> are green, and that should be enough to keep the conversation going.
> - 0008 will hopefully be obsoleted when the SYSTEM_USER proposal [2] lands.
>
> Thanks,
> --Jacob
>
> [1] https://www.postgresql.org/message-id/CAJxrbyxgFzfqby%2BVRCkeAhJnwVZE50%2BZLPx0JT2TDg9LbZtkCg%40mail...
> [2] https://www.postgresql.org/message-id/flat/[email protected]
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-27 01:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
@ 2022-09-27 21:45 ` Jacob Champion <[email protected]>
2022-09-30 14:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2022-09-27 21:45 UTC (permalink / raw)
To: Andrey Chudnovsky <[email protected]>; +Cc: pgsql-hackers; [email protected] <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; mahendrakar s <[email protected]>
On Mon, Sep 26, 2022 at 6:39 PM Andrey Chudnovsky
<[email protected]> wrote:
> For the providing token directly, that would be primarily used for
> scenarios where the same party controls both the server and the client
> side wrapper.
> I.e. The client knows how to get a token for a particular principal
> and doesn't need any additional information other than human readable
> messages.
> Please clarify the scenarios where you see this falling apart.
The most concrete example I can see is with the OAUTHBEARER error
response. If you want to eventually handle differing scopes per role,
or different error statuses (which the proof-of-concept currently
hardcodes as `invalid_token`), then the client can't assume it knows
what the server is going to say there. I think that's true even if you
control both sides and are hardcoding the provider.
How should we communicate those pieces to a custom client when it's
passing a token directly? The easiest way I can see is for the custom
client to speak the OAUTHBEARER protocol directly (e.g. SASL plugin).
If you had to parse the libpq error message, I don't think that'd be
particularly maintainable.
> I can provide an example in the cloud world. We (Azure) as well as
> other providers offer ways to obtain OAUTH tokens for
> Service-to-Service communication at IAAS / PAAS level.
> on Azure "Managed Identity" feature integrated in Compute VM allows a
> client to make a local http call to get a token. VM itself manages the
> certificate livecycle, as well as implements the corresponding OAUTH
> flow.
> This capability is used by both our 1st party PAAS offerings, as well
> as 3rd party services deploying on VMs or managed K8S clusters.
> Here, the client doesn't need libpq assistance in obtaining the token.
Cool. To me that's the strongest argument yet for directly providing
tokens to libpq.
> My optimistic plan here would be to implement several core OAUTH flows
> in libpq core which would be generic enough to support major
> enterprise OAUTH providers:
> 1. Client Credentials flow (Client_id + Client_secret) for backend applications.
> 2. Authorization Code Flow with PKCE and/or Device code flow for GUI
> applications.
As long as it's clear to DBAs when to use which flow (because existing
documentation for that is hit-and-miss), I think it's reasonable to
eventually support multiple flows. Personally my preference would be
to start with one or two core flows, and expand outward once we're
sure that we do those perfectly. Otherwise the explosion of knobs and
buttons might be overwhelming, both to users and devs.
Related to the question of flows is the client implementation library.
I've mentioned that I don't think iddawc is production-ready. As far
as I'm aware, there is only one certified OpenID relying party written
in C, and that's... an Apache server plugin. That leaves us either
choosing an untested library, scouring the web for a "tested" library
(and hoping we're right in our assessment), or implementing our own
(which is going to tamp down enthusiasm for supporting many flows,
though that has its own set of benefits). If you know of any reliable
implementations with a C API, please let me know.
> (2.) above would require a protocol between libpq and upstream clients
> to exchange several messages.
> Your patch includes a way for libpq to deliver to the client a message
> about the next authentication steps, so planned to build on top of
> that.
Specifically it delivers that message to an end user. If you want a
generic machine client to be able to use that, then we'll need to talk
about how.
> A little about scenarios, we look at.
> What we're trying to achieve here is an easy integration path for
> multiple players in the ecosystem:
> - Managed PaaS Postgres providers (both us and multi-cloud solutions)
> - SaaS providers deploying postgres on IaaS/PaaS providers' clouds
> - Tools - pg_admin, psql and other ones.
> - BI, ETL, Federation and other scenarios where postgres is used as
> the data source.
>
> If we can offer a provider agnostic solution for Backend <=> libpq <=>
> Upstreal client path, we can have all players above build support for
> OAUTH credentials, managed by the cloud provider of their choice.
Well... I don't quite understand why we'd go to the trouble of
providing a provider-agnostic communication solution only to have
everyone write their own provider-specific client support. Unless
you're saying Microsoft would provide an officially blessed plugin for
the *server* side only, and Google would provide one of their own, and
so on.
The server side authorization is the only place where I think it makes
sense to specialize by default. libpq should remain agnostic, with the
understanding that we'll need to make hard decisions when a major
provider decides not to follow a spec.
> For us, that would mean:
> - Better administrator experience with pg_admin / psql handling of the
> AAD (Azure Active Directory) authentication flows.
> - Path for integration solutions using Postgres to build AAD
> authentication in their management experience.
> - Ability to use AAD identity provider for any Postgres deployments
> other than our 1st party PaaS offering.
> - Ability to offer github as the identity provider for PaaS Postgres offering.
GitHub is unfortunately a bit tricky, unless they've started
supporting OpenID recently?
> Other players in the ecosystem above would be able to get the same benefits.
>
> Does that make sense and possible without provider specific libpq plugin?
If the players involved implement the flows and follow the specs, yes.
That's a big "if", unfortunately. I think GitHub and Google are two
major players who are currently doing things their own way.
> I just referred to the ability to compile libpq without extra
> dependencies to save some kilobytes.
> Not sure if OAUTH is widely used in those cases. It involves overhead
> anyway, and requires the device to talk to an additional party (OAUTH
> provider).
> Likely Cert authentication is easier.
> If needed, it can get libpq with full OAUTH support and use a client
> code. But I didn't think about this scenario.
Makes sense. Thanks!
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-27 01:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-27 21:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2022-09-30 14:47 ` Andrey Chudnovsky <[email protected]>
2022-09-30 20:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Andrey Chudnovsky @ 2022-09-30 14:47 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: [email protected] <[email protected]>; mahendrakar s <[email protected]>; [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
> The most concrete example I can see is with the OAUTHBEARER error
> response. If you want to eventually handle differing scopes per role,
> or different error statuses (which the proof-of-concept currently
> hardcodes as `invalid_token`), then the client can't assume it knows
> what the server is going to say there. I think that's true even if you
> control both sides and are hardcoding the provider.
Ok, I see the point. It's related to the topic of communication
between libpq and the upstream client.
> How should we communicate those pieces to a custom client when it's
> passing a token directly? The easiest way I can see is for the custom
> client to speak the OAUTHBEARER protocol directly (e.g. SASL plugin).
> If you had to parse the libpq error message, I don't think that'd be
> particularly maintainable.
I agree that parsing the message is not a sustainable way.
Could you provide more details on the SASL plugin approach you propose?
Specifically, is this basically a set of extension hooks for the client
side?
With the need for the client to be compiled with the plugins based on
the set of providers it needs.
> Well... I don't quite understand why we'd go to the trouble of
> providing a provider-agnostic communication solution only to have
> everyone write their own provider-specific client support. Unless
> you're saying Microsoft would provide an officially blessed plugin for
> the *server* side only, and Google would provide one of their own, and
> so on.
Yes, via extensions. Identity providers can open source extensions to
use their auth services outside of first party PaaS offerings.
For 3rd party Postgres PaaS or on premise deployments.
> The server side authorization is the only place where I think it makes
> sense to specialize by default. libpq should remain agnostic, with the
> understanding that we'll need to make hard decisions when a major
> provider decides not to follow a spec.
Completely agree with agnostic libpq. Though needs validation with
several major providers to know if this is possible.
> Specifically it delivers that message to an end user. If you want a
> generic machine client to be able to use that, then we'll need to talk
> about how.
Yes, that's what needs to be decided.
In both Device code and Authorization code scenarios, libpq and the
client would need to exchange a couple of pieces of metadata.
Plus, after success, the client should be able to access a refresh token
for further use.
Can we implement a generic protocol like for this between libpq and the
clients?
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-27 01:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-27 21:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-30 14:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
@ 2022-09-30 20:45 ` Jacob Champion <[email protected]>
2022-10-03 18:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2022-09-30 20:45 UTC (permalink / raw)
To: Andrey Chudnovsky <[email protected]>; +Cc: [email protected] <[email protected]>; mahendrakar s <[email protected]>; [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
On Fri, Sep 30, 2022 at 7:47 AM Andrey Chudnovsky
<[email protected]> wrote:
> > How should we communicate those pieces to a custom client when it's
> > passing a token directly? The easiest way I can see is for the custom
> > client to speak the OAUTHBEARER protocol directly (e.g. SASL plugin).
> > If you had to parse the libpq error message, I don't think that'd be
> > particularly maintainable.
>
> I agree that parsing the message is not a sustainable way.
> Could you provide more details on the SASL plugin approach you propose?
>
> Specifically, is this basically a set of extension hooks for the client side?
> With the need for the client to be compiled with the plugins based on
> the set of providers it needs.
That's a good question. I can see two broad approaches, with maybe
some ability to combine them into a hybrid:
1. If there turns out to be serious interest in having libpq itself
handle OAuth natively (with all of the web-facing code that implies,
and all of the questions still left to answer), then we might be able
to provide a "token hook" in the same way that we currently provide a
passphrase hook for OpenSSL keys. By default, libpq would use its
internal machinery to take the provider details, navigate its builtin
flow, and return the Bearer token. If you wanted to override that
behavior as a client, you could replace the builtin flow with your
own, by registering a set of callbacks.
2. Alternatively, OAuth support could be provided via a mechanism
plugin for some third-party SASL library (GNU libgsasl, Cyrus
libsasl2). We could provide an OAuth plugin in contrib that handles
the default flow. Other providers could publish their alternative
plugins to completely replace the OAUTHBEARER mechanism handling.
Approach (2) would make for some duplicated effort since every
provider has to write code to speak the OAUTHBEARER protocol. It might
simplify provider-specific distribution, since (at least for Cyrus) I
think you could build a single plugin that supports both the client
and server side. But it would be a lot easier to unknowingly (or
knowingly) break the spec, since you'd control both the client and
server sides. There would be less incentive to interoperate.
Finally, we could potentially take pieces from both, by having an
official OAuth mechanism plugin that provides a client-side hook to
override the flow. I have no idea if the benefits would offset the
costs of a plugin-for-a-plugin style architecture. And providers would
still be free to ignore it and just provide a full mechanism plugin
anyway.
> > Well... I don't quite understand why we'd go to the trouble of
> > providing a provider-agnostic communication solution only to have
> > everyone write their own provider-specific client support. Unless
> > you're saying Microsoft would provide an officially blessed plugin for
> > the *server* side only, and Google would provide one of their own, and
> > so on.
>
> Yes, via extensions. Identity providers can open source extensions to
> use their auth services outside of first party PaaS offerings.
> For 3rd party Postgres PaaS or on premise deployments.
Sounds reasonable.
> > The server side authorization is the only place where I think it makes
> > sense to specialize by default. libpq should remain agnostic, with the
> > understanding that we'll need to make hard decisions when a major
> > provider decides not to follow a spec.
>
> Completely agree with agnostic libpq. Though needs validation with
> several major providers to know if this is possible.
Agreed.
> > Specifically it delivers that message to an end user. If you want a
> > generic machine client to be able to use that, then we'll need to talk
> > about how.
>
> Yes, that's what needs to be decided.
> In both Device code and Authorization code scenarios, libpq and the
> client would need to exchange a couple of pieces of metadata.
> Plus, after success, the client should be able to access a refresh token for further use.
>
> Can we implement a generic protocol like for this between libpq and the clients?
I think we can probably prototype a callback hook for approach (1)
pretty quickly. (2) is a lot more work and investigation, but it's
work that I'm interested in doing (when I get the time). I think there
are other very good reasons to consider a third-party SASL library,
and some good lessons to be learned, even if the community decides not
to go down that road.
Thanks,
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-27 01:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-27 21:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-30 14:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-30 20:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2022-10-03 18:04 ` Andrey Chudnovsky <[email protected]>
2022-11-23 09:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Andrey Chudnovsky @ 2022-10-03 18:04 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: [email protected] <[email protected]>; mahendrakar s <[email protected]>; [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
> I think we can probably prototype a callback hook for approach (1)
> pretty quickly. (2) is a lot more work and investigation, but it's
> work that I'm interested in doing (when I get the time). I think there
> are other very good reasons to consider a third-party SASL library,
> and some good lessons to be learned, even if the community decides not
> to go down that road.
Makes sense. We will work on (1.) and do some check if there are any
blockers for a shared solution to support github and google.
On Fri, Sep 30, 2022 at 1:45 PM Jacob Champion <[email protected]> wrote:
>
> On Fri, Sep 30, 2022 at 7:47 AM Andrey Chudnovsky
> <[email protected]> wrote:
> > > How should we communicate those pieces to a custom client when it's
> > > passing a token directly? The easiest way I can see is for the custom
> > > client to speak the OAUTHBEARER protocol directly (e.g. SASL plugin).
> > > If you had to parse the libpq error message, I don't think that'd be
> > > particularly maintainable.
> >
> > I agree that parsing the message is not a sustainable way.
> > Could you provide more details on the SASL plugin approach you propose?
> >
> > Specifically, is this basically a set of extension hooks for the client side?
> > With the need for the client to be compiled with the plugins based on
> > the set of providers it needs.
>
> That's a good question. I can see two broad approaches, with maybe
> some ability to combine them into a hybrid:
>
> 1. If there turns out to be serious interest in having libpq itself
> handle OAuth natively (with all of the web-facing code that implies,
> and all of the questions still left to answer), then we might be able
> to provide a "token hook" in the same way that we currently provide a
> passphrase hook for OpenSSL keys. By default, libpq would use its
> internal machinery to take the provider details, navigate its builtin
> flow, and return the Bearer token. If you wanted to override that
> behavior as a client, you could replace the builtin flow with your
> own, by registering a set of callbacks.
>
> 2. Alternatively, OAuth support could be provided via a mechanism
> plugin for some third-party SASL library (GNU libgsasl, Cyrus
> libsasl2). We could provide an OAuth plugin in contrib that handles
> the default flow. Other providers could publish their alternative
> plugins to completely replace the OAUTHBEARER mechanism handling.
>
> Approach (2) would make for some duplicated effort since every
> provider has to write code to speak the OAUTHBEARER protocol. It might
> simplify provider-specific distribution, since (at least for Cyrus) I
> think you could build a single plugin that supports both the client
> and server side. But it would be a lot easier to unknowingly (or
> knowingly) break the spec, since you'd control both the client and
> server sides. There would be less incentive to interoperate.
>
> Finally, we could potentially take pieces from both, by having an
> official OAuth mechanism plugin that provides a client-side hook to
> override the flow. I have no idea if the benefits would offset the
> costs of a plugin-for-a-plugin style architecture. And providers would
> still be free to ignore it and just provide a full mechanism plugin
> anyway.
>
> > > Well... I don't quite understand why we'd go to the trouble of
> > > providing a provider-agnostic communication solution only to have
> > > everyone write their own provider-specific client support. Unless
> > > you're saying Microsoft would provide an officially blessed plugin for
> > > the *server* side only, and Google would provide one of their own, and
> > > so on.
> >
> > Yes, via extensions. Identity providers can open source extensions to
> > use their auth services outside of first party PaaS offerings.
> > For 3rd party Postgres PaaS or on premise deployments.
>
> Sounds reasonable.
>
> > > The server side authorization is the only place where I think it makes
> > > sense to specialize by default. libpq should remain agnostic, with the
> > > understanding that we'll need to make hard decisions when a major
> > > provider decides not to follow a spec.
> >
> > Completely agree with agnostic libpq. Though needs validation with
> > several major providers to know if this is possible.
>
> Agreed.
>
> > > Specifically it delivers that message to an end user. If you want a
> > > generic machine client to be able to use that, then we'll need to talk
> > > about how.
> >
> > Yes, that's what needs to be decided.
> > In both Device code and Authorization code scenarios, libpq and the
> > client would need to exchange a couple of pieces of metadata.
> > Plus, after success, the client should be able to access a refresh token for further use.
> >
> > Can we implement a generic protocol like for this between libpq and the clients?
>
> I think we can probably prototype a callback hook for approach (1)
> pretty quickly. (2) is a lot more work and investigation, but it's
> work that I'm interested in doing (when I get the time). I think there
> are other very good reasons to consider a third-party SASL library,
> and some good lessons to be learned, even if the community decides not
> to go down that road.
>
> Thanks,
> --Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-27 01:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-27 21:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-30 14:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-30 20:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-10-03 18:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
@ 2022-11-23 09:58 ` mahendrakar s <[email protected]>
2022-11-23 20:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: mahendrakar s @ 2022-11-23 09:58 UTC (permalink / raw)
To: Andrey Chudnovsky <[email protected]>; +Cc: Jacob Champion <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
Hi,
We validated on libpq handling OAuth natively with different flows
with different OIDC certified providers.
Flows: Device Code, Client Credentials and Refresh Token.
Providers: Microsoft, Google and Okta.
Also validated with OAuth provider Github.
We propose using OpenID Connect (OIDC) as the protocol, instead of
OAuth, as it is:
- Discovery mechanism to bridge the differences and provide metadata.
- Stricter protocol and certification process to reliably identify
which providers can be supported.
- OIDC is designed for authentication, while the main purpose of OAUTH is to
authorize applications on behalf of the user.
Github is not OIDC certified, so won’t be supported with this proposal.
However, it may be supported in the future through the ability for the
extension to provide custom discovery document content.
OpenID configuration has a well-known discovery mechanism
for the provider configuration URI which is
defined in OpenID Connect. It allows libpq to fetch
metadata about provider (i.e endpoints, supported grants, response types, etc).
In the attached patch (based on V2 patch in the thread and does not
contain Samay's changes):
- Provider can configure issuer url and scope through the options hook.)
- Server passes on an open discovery url and scope to libpq.
- Libpq handles OAuth flow based on the flow_type sent in the
connection string [1].
- Added callbacks to notify a structure to client tools if OAuth flow
requires user interaction.
- Pg backend uses hooks to validate bearer token.
Note that authentication code flow with PKCE for GUI clients is not
implemented yet.
Proposed next steps:
- Broaden discussion to reach agreement on the approach.
- Implement libpq changes without iddawc
- Prototype GUI flow with pgAdmin
Thanks,
Mahendrakar.
[1]:
connection string for refresh token flow:
./psql -U <user> -d 'dbname=postgres oauth_client_id=<client_id>
oauth_flow_type=<flowtype> oauth_refresh_token=<refresh token>'
On Mon, 3 Oct 2022 at 23:34, Andrey Chudnovsky <[email protected]> wrote:
>
> > I think we can probably prototype a callback hook for approach (1)
> > pretty quickly. (2) is a lot more work and investigation, but it's
> > work that I'm interested in doing (when I get the time). I think there
> > are other very good reasons to consider a third-party SASL library,
> > and some good lessons to be learned, even if the community decides not
> > to go down that road.
>
> Makes sense. We will work on (1.) and do some check if there are any
> blockers for a shared solution to support github and google.
>
> On Fri, Sep 30, 2022 at 1:45 PM Jacob Champion <[email protected]> wrote:
> >
> > On Fri, Sep 30, 2022 at 7:47 AM Andrey Chudnovsky
> > <[email protected]> wrote:
> > > > How should we communicate those pieces to a custom client when it's
> > > > passing a token directly? The easiest way I can see is for the custom
> > > > client to speak the OAUTHBEARER protocol directly (e.g. SASL plugin).
> > > > If you had to parse the libpq error message, I don't think that'd be
> > > > particularly maintainable.
> > >
> > > I agree that parsing the message is not a sustainable way.
> > > Could you provide more details on the SASL plugin approach you propose?
> > >
> > > Specifically, is this basically a set of extension hooks for the client side?
> > > With the need for the client to be compiled with the plugins based on
> > > the set of providers it needs.
> >
> > That's a good question. I can see two broad approaches, with maybe
> > some ability to combine them into a hybrid:
> >
> > 1. If there turns out to be serious interest in having libpq itself
> > handle OAuth natively (with all of the web-facing code that implies,
> > and all of the questions still left to answer), then we might be able
> > to provide a "token hook" in the same way that we currently provide a
> > passphrase hook for OpenSSL keys. By default, libpq would use its
> > internal machinery to take the provider details, navigate its builtin
> > flow, and return the Bearer token. If you wanted to override that
> > behavior as a client, you could replace the builtin flow with your
> > own, by registering a set of callbacks.
> >
> > 2. Alternatively, OAuth support could be provided via a mechanism
> > plugin for some third-party SASL library (GNU libgsasl, Cyrus
> > libsasl2). We could provide an OAuth plugin in contrib that handles
> > the default flow. Other providers could publish their alternative
> > plugins to completely replace the OAUTHBEARER mechanism handling.
> >
> > Approach (2) would make for some duplicated effort since every
> > provider has to write code to speak the OAUTHBEARER protocol. It might
> > simplify provider-specific distribution, since (at least for Cyrus) I
> > think you could build a single plugin that supports both the client
> > and server side. But it would be a lot easier to unknowingly (or
> > knowingly) break the spec, since you'd control both the client and
> > server sides. There would be less incentive to interoperate.
> >
> > Finally, we could potentially take pieces from both, by having an
> > official OAuth mechanism plugin that provides a client-side hook to
> > override the flow. I have no idea if the benefits would offset the
> > costs of a plugin-for-a-plugin style architecture. And providers would
> > still be free to ignore it and just provide a full mechanism plugin
> > anyway.
> >
> > > > Well... I don't quite understand why we'd go to the trouble of
> > > > providing a provider-agnostic communication solution only to have
> > > > everyone write their own provider-specific client support. Unless
> > > > you're saying Microsoft would provide an officially blessed plugin for
> > > > the *server* side only, and Google would provide one of their own, and
> > > > so on.
> > >
> > > Yes, via extensions. Identity providers can open source extensions to
> > > use their auth services outside of first party PaaS offerings.
> > > For 3rd party Postgres PaaS or on premise deployments.
> >
> > Sounds reasonable.
> >
> > > > The server side authorization is the only place where I think it makes
> > > > sense to specialize by default. libpq should remain agnostic, with the
> > > > understanding that we'll need to make hard decisions when a major
> > > > provider decides not to follow a spec.
> > >
> > > Completely agree with agnostic libpq. Though needs validation with
> > > several major providers to know if this is possible.
> >
> > Agreed.
> >
> > > > Specifically it delivers that message to an end user. If you want a
> > > > generic machine client to be able to use that, then we'll need to talk
> > > > about how.
> > >
> > > Yes, that's what needs to be decided.
> > > In both Device code and Authorization code scenarios, libpq and the
> > > client would need to exchange a couple of pieces of metadata.
> > > Plus, after success, the client should be able to access a refresh token for further use.
> > >
> > > Can we implement a generic protocol like for this between libpq and the clients?
> >
> > I think we can probably prototype a callback hook for approach (1)
> > pretty quickly. (2) is a lot more work and investigation, but it's
> > work that I'm interested in doing (when I get the time). I think there
> > are other very good reasons to consider a third-party SASL library,
> > and some good lessons to be learned, even if the community decides not
> > to go down that road.
> >
> > Thanks,
> > --Jacob
Attachments:
[application/octet-stream] v1-0001-oauth-flows-validation-hook-approach.patch (31.2K, ../../CABkiuWqp1mog7GiUq+thfn0bqKye=QShe5aVBEVSUDPYyBWKvw@mail.gmail.com/2-v1-0001-oauth-flows-validation-hook-approach.patch)
download | inline diff:
diff --git a/src/backend/libpq/auth-oauth.c b/src/backend/libpq/auth-oauth.c
index 3a625847f3..f213a40b65 100644
--- a/src/backend/libpq/auth-oauth.c
+++ b/src/backend/libpq/auth-oauth.c
@@ -24,15 +24,23 @@
#include "libpq/hba.h"
#include "libpq/oauth.h"
#include "libpq/sasl.h"
+#include "miscadmin.h"
#include "storage/fd.h"
/* GUC */
char *oauth_validator_command;
+static OAuthProvider* oauth_provider = NULL;
+
+/*----------------------------------------------------------------
+ * OAuth Authentication
+ *----------------------------------------------------------------
+ */
+static List *oauth_providers = NIL;
static void oauth_get_mechanisms(Port *port, StringInfo buf);
static void *oauth_init(Port *port, const char *selected_mech, const char *shadow_pass);
static int oauth_exchange(void *opaq, const char *input, int inputlen,
- char **output, int *outputlen, char **logdetail);
+ char **output, int *outputlen, const char **logdetail);
/* Mechanism declaration */
const pg_be_sasl_mech pg_be_oauth_mech = {
@@ -43,7 +51,6 @@ const pg_be_sasl_mech pg_be_oauth_mech = {
PG_MAX_AUTH_TOKEN_LENGTH,
};
-
typedef enum
{
OAUTH_STATE_INIT = 0,
@@ -62,7 +69,7 @@ struct oauth_ctx
static char *sanitize_char(char c);
static char *parse_kvpairs_for_auth(char **input);
static void generate_error_response(struct oauth_ctx *ctx, char **output, int *outputlen);
-static bool validate(Port *port, const char *auth, char **logdetail);
+static bool validate(Port *port, const char *auth, const char **logdetail);
static bool run_validator_command(Port *port, const char *token);
static bool check_exit(FILE **fh, const char *command);
static bool unset_cloexec(int fd);
@@ -72,6 +79,86 @@ static bool username_ok_for_shell(const char *username);
#define AUTH_KEY "auth"
#define BEARER_SCHEME "Bearer "
+#include "utils/memutils.h"
+
+/*----------------------------------------------------------------
+ * OAuth Token Validator
+ *----------------------------------------------------------------
+ */
+
+/*
+ * RegistorOAuthProvider registers a OAuth Token Validator to be
+ * used for oauth token validation. It validates the token and adds the valiator
+ * name and it's hooks to a list of loaded token validator. The right validator's
+ * hooks can then be called based on the validator name specified in
+ * pg_hba.conf.
+ *
+ * This function should be called in _PG_init() by any extension looking to
+ * add a custom authentication method.
+ */
+void
+RegistorOAuthProvider(
+ const char *provider_name,
+ OAuthProviderCheck_hook_type OAuthProviderCheck_hook,
+ OAuthProviderError_hook_type OAuthProviderError_hook,
+ OAuthProviderOptions_hook_type OAuthProviderOptions_hook
+)
+{
+ if (!process_shared_preload_libraries_in_progress)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("RegistorOAuthProvider can only be called by a shared_preload_library")));
+ return;
+ }
+
+ MemoryContext oldcxt;
+ if (oauth_provider == NULL)
+ {
+ oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+ oauth_provider = palloc(sizeof(OAuthProvider));
+ oauth_provider->name = pstrdup(provider_name);
+ oauth_provider->oauth_provider_hook = OAuthProviderCheck_hook;
+ oauth_provider->oauth_error_hook = OAuthProviderError_hook;
+ oauth_provider->oauth_options_hook = OAuthProviderOptions_hook;
+ oauth_providers = lappend(oauth_providers, oauth_provider);
+ MemoryContextSwitchTo(oldcxt);
+ }
+ else
+ {
+ if (oauth_provider && oauth_provider->name)
+ {
+ ereport(ERROR,
+ (errmsg("OAuth provider \"%s\" is already loaded.",
+ oauth_provider->name)));
+ }
+ else
+ {
+ ereport(ERROR,
+ (errmsg("OAuth provider is already loaded.")));
+ }
+ }
+}
+
+/*
+ * Returns the oauth provider (which includes it's
+ * callback functions) based on name specified.
+ */
+OAuthProvider *get_provider_by_name(const char *name)
+{
+ ListCell *lc;
+ foreach(lc, oauth_providers)
+ {
+ OAuthProvider *provider = (OAuthProvider *) lfirst(lc);
+ if (strcmp(provider->name, name) == 0)
+ {
+ return provider;
+ }
+ }
+
+ return NULL;
+}
+
static void
oauth_get_mechanisms(Port *port, StringInfo buf)
{
@@ -102,9 +189,32 @@ oauth_init(Port *port, const char *selected_mech, const char *shadow_pass)
return ctx;
}
+static void process_oauth_flow_type(pg_oauth_flow_type flow_type, struct oauth_ctx *ctx, char **output, int *outputlen)
+{
+ StringInfoData buf;
+ initStringInfo(&buf);
+
+ OAuthProviderOptions *oauth_options = oauth_provider->oauth_options_hook(flow_type);
+ ctx->scope = oauth_options->scope;
+ ctx->issuer = oauth_options->issuer_url;
+ appendStringInfo(&buf,
+ "{ "
+ "\"status\": \"invalid_token\", "
+ "\"openid-configuration\": \"%s/.well-known/openid-configuration\","
+ "\"scope\": \"%s\""
+ "}",
+ oauth_options->issuer_url,
+ oauth_options->scope);
+
+ *output = buf.data;
+ *outputlen = buf.len;
+
+ pfree(oauth_options);
+}
+
static int
oauth_exchange(void *opaq, const char *input, int inputlen,
- char **output, int *outputlen, char **logdetail)
+ char **output, int *outputlen, const char **logdetail)
{
char *p;
char cbind_flag;
@@ -247,11 +357,17 @@ oauth_exchange(void *opaq, const char *input, int inputlen,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("malformed OAUTHBEARER message"),
errdetail("Message contains additional data after the final terminator.")));
-
- if (!validate(ctx->port, auth, logdetail))
+
+ /* if not Bearer, process flow_type*/
+ if (strncasecmp(auth, BEARER_SCHEME, strlen(BEARER_SCHEME)))
+ {
+ process_oauth_flow_type(atoi(auth), ctx, output, outputlen);
+ ctx->state = OAUTH_STATE_ERROR;
+ return PG_SASL_EXCHANGE_CONTINUE;
+ }
+ else if(!validate(ctx->port, auth, logdetail))
{
generate_error_response(ctx, output, outputlen);
-
ctx->state = OAUTH_STATE_ERROR;
return PG_SASL_EXCHANGE_CONTINUE;
}
@@ -415,7 +531,7 @@ generate_error_response(struct oauth_ctx *ctx, char **output, int *outputlen)
}
static bool
-validate(Port *port, const char *auth, char **logdetail)
+validate(Port *port, const char *auth, const char **logdetail)
{
static const char * const b64_set = "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
@@ -508,7 +624,7 @@ validate(Port *port, const char *auth, char **logdetail)
return true;
}
- /* Make sure the validator authenticated the user. */
+ /* Make sure the validator authenticated the user. */
if (!MyClientConnectionInfo.authn_id)
{
/* TODO: use logdetail; reduce message duplication */
@@ -518,199 +634,22 @@ validate(Port *port, const char *auth, char **logdetail)
return false;
}
- /* Finally, check the user map. */
- ret = check_usermap(port->hba->usermap, port->user_name,
- MyClientConnectionInfo.authn_id, false);
+ /* Finally, check the user map. */
+ ret = check_usermap(port->hba->usermap, port->user_name,
+ MyClientConnectionInfo.authn_id, false);
return (ret == STATUS_OK);
}
static bool
run_validator_command(Port *port, const char *token)
{
- bool success = false;
- int rc;
- int pipefd[2];
- int rfd = -1;
- int wfd = -1;
-
- StringInfoData command = { 0 };
- char *p;
- FILE *fh = NULL;
-
- ssize_t written;
- char *line = NULL;
- size_t size = 0;
- ssize_t len;
-
- Assert(oauth_validator_command);
-
- if (!oauth_validator_command[0])
- {
- ereport(COMMERROR,
- (errmsg("oauth_validator_command is not set"),
- errhint("To allow OAuth authenticated connections, set "
- "oauth_validator_command in postgresql.conf.")));
- return false;
- }
-
- /*
- * Since popen() is unidirectional, open up a pipe for the other direction.
- * Use CLOEXEC to ensure that our write end doesn't accidentally get copied
- * into child processes, which would prevent us from closing it cleanly.
- *
- * XXX this is ugly. We should just read from the child process's stdout,
- * but that's a lot more code.
- * XXX by bypassing the popen API, we open the potential of process
- * deadlock. Clearly document child process requirements (i.e. the child
- * MUST read all data off of the pipe before writing anything).
- * TODO: port to Windows using _pipe().
- */
- rc = pipe2(pipefd, O_CLOEXEC);
- if (rc < 0)
- {
- ereport(COMMERROR,
- (errcode_for_file_access(),
- errmsg("could not create child pipe: %m")));
- return false;
- }
-
- rfd = pipefd[0];
- wfd = pipefd[1];
-
- /* Allow the read pipe be passed to the child. */
- if (!unset_cloexec(rfd))
- {
- /* error message was already logged */
- goto cleanup;
- }
-
- /*
- * Construct the command, substituting any recognized %-specifiers:
- *
- * %f: the file descriptor of the input pipe
- * %r: the role that the client wants to assume (port->user_name)
- * %%: a literal '%'
- */
- initStringInfo(&command);
-
- for (p = oauth_validator_command; *p; p++)
- {
- if (p[0] == '%')
- {
- switch (p[1])
- {
- case 'f':
- appendStringInfo(&command, "%d", rfd);
- p++;
- break;
- case 'r':
- /*
- * TODO: decide how this string should be escaped. The role
- * is controlled by the client, so if we don't escape it,
- * command injections are inevitable.
- *
- * This is probably an indication that the role name needs
- * to be communicated to the validator process in some other
- * way. For this proof of concept, just be incredibly strict
- * about the characters that are allowed in user names.
- */
- if (!username_ok_for_shell(port->user_name))
- goto cleanup;
-
- appendStringInfoString(&command, port->user_name);
- p++;
- break;
- case '%':
- appendStringInfoChar(&command, '%');
- p++;
- break;
- default:
- appendStringInfoChar(&command, p[0]);
- }
- }
- else
- appendStringInfoChar(&command, p[0]);
- }
-
- /* Execute the command. */
- fh = OpenPipeStream(command.data, "re");
- /* TODO: handle failures */
-
- /* We don't need the read end of the pipe anymore. */
- close(rfd);
- rfd = -1;
-
- /* Give the command the token to validate. */
- written = write(wfd, token, strlen(token));
- if (written != strlen(token))
- {
- /* TODO must loop for short writes, EINTR et al */
- ereport(COMMERROR,
- (errcode_for_file_access(),
- errmsg("could not write token to child pipe: %m")));
- goto cleanup;
- }
-
- close(wfd);
- wfd = -1;
-
- /*
- * Read the command's response.
- *
- * TODO: getline() is probably too new to use, unfortunately.
- * TODO: loop over all lines
- */
- if ((len = getline(&line, &size, fh)) >= 0)
- {
- /* TODO: fail if the authn_id doesn't end with a newline */
- if (len > 0)
- line[len - 1] = '\0';
-
- set_authn_id(port, line);
- }
- else if (ferror(fh))
- {
- ereport(COMMERROR,
- (errcode_for_file_access(),
- errmsg("could not read from command \"%s\": %m",
- command.data)));
- goto cleanup;
- }
-
- /* Make sure the command exits cleanly. */
- if (!check_exit(&fh, command.data))
+ int result = oauth_provider->oauth_provider_hook(port, token);
+ if(result == STATUS_OK)
{
- /* error message already logged */
- goto cleanup;
- }
-
- /* Done. */
- success = true;
-
-cleanup:
- if (line)
- free(line);
-
- /*
- * In the successful case, the pipe fds are already closed. For the error
- * case, always close out the pipe before waiting for the command, to
- * prevent deadlock.
- */
- if (rfd >= 0)
- close(rfd);
- if (wfd >= 0)
- close(wfd);
-
- if (fh)
- {
- Assert(!success);
- check_exit(&fh, command.data);
+ set_authn_id(port, port->user_name);
+ return true;
}
-
- if (command.data)
- pfree(command.data);
-
- return success;
+ return false;
}
static bool
@@ -780,7 +719,7 @@ username_ok_for_shell(const char *username)
/* This set is borrowed from fe_utils' appendShellStringNoError(). */
static const char * const allowed = "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "0123456789-_./:";
+ "0123456789-_./@:";
size_t span;
Assert(username && username[0]); /* should have already been checked */
diff --git a/src/include/libpq/auth.h b/src/include/libpq/auth.h
index b62457d57c..7b7b6ff9aa 100644
--- a/src/include/libpq/auth.h
+++ b/src/include/libpq/auth.h
@@ -28,6 +28,41 @@ extern void set_authn_id(Port *port, const char *id);
/* Hook for plugins to get control in ClientAuthentication() */
typedef void (*ClientAuthentication_hook_type) (Port *, int);
extern PGDLLIMPORT ClientAuthentication_hook_type ClientAuthentication_hook;
+/* Declarations for oAuth authentication providers */
+typedef int (*OAuthProviderCheck_hook_type) (Port *, const char*);
+
+/* Hook for plugins to report error messages in validation_failed() */
+typedef const char * (*OAuthProviderError_hook_type) (Port *);
+
+/* Hook for plugins to validate oauth provider options */
+typedef bool (*OAuthProviderValidateOptions_hook_type)
+ (char *, char *, HbaLine *, char **);
+
+typedef struct OAuthProviderOptions
+{
+ char *issuer_url;
+ char *scope;
+} OAuthProviderOptions;
+
+/* Hook for plugins to get oauth params */
+typedef OAuthProviderOptions *(*OAuthProviderOptions_hook_type) (pg_oauth_flow_type);
+
+typedef struct OAuthProvider
+{
+ const char *name;
+ OAuthProviderCheck_hook_type oauth_provider_hook;
+ OAuthProviderError_hook_type oauth_error_hook;
+ OAuthProviderOptions_hook_type oauth_options_hook;
+} OAuthProvider;
+
+extern void RegistorOAuthProvider
+ (const char *provider_name,
+ OAuthProviderCheck_hook_type OAuthProviderCheck_hook,
+ OAuthProviderError_hook_type OAuthProviderError_hook,
+ OAuthProviderOptions_hook_type OAuthProviderParams_hook
+ );
+
+extern OAuthProvider *get_provider_by_name(const char *name);
#define PG_MAX_AUTH_TOKEN_LENGTH 65535
#endif /* AUTH_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 6d452ec6d9..f7bbb9dcf4 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -68,6 +68,17 @@ typedef enum CAC_state
CAC_TOOMANY
} CAC_state;
+/* OAuth flow types */
+typedef enum pg_oauth_flow_type
+{
+ OAUTH_DEVICE_CODE,
+ OAUTH_CLIENT_CREDENTIALS,
+ OAUTH_AUTH,
+ OAUTH_AUTH_PKCE,
+ OAUTH_REFRESH_TOKEN,
+ OAUTH_NONE
+} pg_oauth_flow_type;
+
/*
* GSSAPI specific state information
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
index 91d2c69f16..1ba2e033c4 100644
--- a/src/interfaces/libpq/fe-auth-oauth.c
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -142,6 +142,43 @@ iddawc_request_error(PGconn *conn, struct _i_session *i, int err, const char *ms
appendPQExpBuffer(&conn->errorMessage, "(%s)\n", error_code);
}
+static pg_oauth_flow_type oauth_get_flow_type(const char *oauthflow)
+{
+ pg_oauth_flow_type flow_type;
+
+ if(!oauthflow)
+ {
+ return OAUTH_NONE;
+ }
+
+ /* client_secret, device_code, auth_code_pkce, refresh_token */
+ if(strcmp(oauthflow, "device_code") == 0)
+ {
+ flow_type = OAUTH_DEVICE_CODE;
+ }
+ else if(strcmp(oauthflow, "client_secret") == 0)
+ {
+ flow_type = OAUTH_CLIENT_CREDENTIALS;
+ }
+ else if(strcmp(oauthflow, "auth_code_pkce") == 0)
+ {
+ flow_type = OAUTH_AUTH_PKCE;
+ }
+ else if(strcmp(oauthflow, "refresh_token") == 0)
+ {
+ flow_type = OAUTH_REFRESH_TOKEN;
+ }
+ else if(strcmp(oauthflow, "auth_code"))
+ {
+ flow_type = OAUTH_AUTH_CODE;
+ }
+ else
+ {
+ flow_type = OAUTH_NONE;
+ }
+ return flow_type;
+}
+
static char *
get_auth_token(PGconn *conn)
{
@@ -150,29 +187,44 @@ get_auth_token(PGconn *conn)
int err;
int auth_method;
bool user_prompted = false;
- const char *verification_uri;
- const char *user_code;
- const char *access_token;
- const char *token_type;
- char *token = NULL;
-
+ char *verification_uri;
+ char *user_code;
+ char *access_token;
+ char *refresh_token;
+ char *token_type;
+ pg_oauth_flow_type flow_type;
+ char *token = NULL;
+ uint session_response_type;
+ PGOAuthMsgObj oauthMsgObj;
+
+ MemSet(&oauthMsgObj, 0x00, sizeof(PGOAuthMsgObj));
+
if (!conn->oauth_discovery_uri)
return strdup(""); /* ask the server for one */
- i_init_session(&session);
-
if (!conn->oauth_client_id)
{
/* We can't talk to a server without a client identifier. */
appendPQExpBufferStr(&conn->errorMessage,
libpq_gettext("no oauth_client_id is set for the connection"));
- goto cleanup;
+ return NULL;
}
- token_buf = createPQExpBuffer();
+ i_init_session(&session);
+ token_buf = createPQExpBuffer();
if (!token_buf)
goto cleanup;
+
+ if(conn->oauth_bearer_token)
+ {
+ appendPQExpBufferStr(token_buf, "Bearer ");
+ appendPQExpBufferStr(token_buf, conn->oauth_bearer_token);
+ if (PQExpBufferBroken(token_buf))
+ goto cleanup;
+ token = strdup(token_buf->data);
+ goto cleanup;
+ }
err = i_set_str_parameter(&session, I_OPT_OPENID_CONFIG_ENDPOINT, conn->oauth_discovery_uri);
if (err)
@@ -181,6 +233,8 @@ get_auth_token(PGconn *conn)
goto cleanup;
}
+ flow_type = oauth_get_flow_type(conn->oauth_flow_type);
+
err = i_get_openid_config(&session);
if (err)
{
@@ -201,18 +255,64 @@ get_auth_token(PGconn *conn)
libpq_gettext("issuer does not support device authorization"));
goto cleanup;
}
+ auth_method = I_TOKEN_AUTH_METHOD_NONE;
+
+ /* for refresh token flow, do not run auth request*/
+ if(flow_type == OAUTH_REFRESH_TOKEN && conn->oauth_refresh_token)
+ {
+ err = i_set_parameter_list(&session,
+ I_OPT_CLIENT_ID, conn->oauth_client_id,
+ I_OPT_REFRESH_TOKEN, conn->oauth_refresh_token,
+ I_OPT_RESPONSE_TYPE, I_RESPONSE_TYPE_REFRESH_TOKEN,
+ I_OPT_TOKEN_METHOD, auth_method,
+ I_OPT_CLIENT_SECRET, conn->oauth_client_secret,
+ I_OPT_SCOPE, conn->oauth_scope,
+ I_OPT_NONE
+ );
+
+ if (err)
+ {
+ iddawc_error(conn, err, "failed to set refresh token flow parameters");
+ goto cleanup;
+ }
- err = i_set_response_type(&session, I_RESPONSE_TYPE_DEVICE_CODE);
+ err = i_run_token_request(&session);
+ if (err)
+ {
+ iddawc_request_error(conn, &session, err,
+ "failed to obtain token authorization with refresh token flow");
+ goto cleanup;
+ }
+
+ access_token = i_get_str_parameter(&session, I_OPT_ACCESS_TOKEN);
+ token_type = i_get_str_parameter(&session, I_OPT_TOKEN_TYPE);
+
+ if (!access_token || !token_type || strcasecmp(token_type, "Bearer"))
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("issuer did not provide a bearer token"));
+ goto cleanup;
+ }
+
+ appendPQExpBufferStr(token_buf, "Bearer ");
+ appendPQExpBufferStr(token_buf, access_token);
+
+ if (PQExpBufferBroken(token_buf))
+ goto cleanup;
+
+ token = strdup(token_buf->data);
+ return token;
+ }
+
+ //default device flow
+ session_response_type = I_RESPONSE_TYPE_DEVICE_CODE;
+ err = i_set_response_type(&session, session_response_type);
if (err)
{
iddawc_error(conn, err, "failed to set device code response type");
goto cleanup;
}
- auth_method = I_TOKEN_AUTH_METHOD_NONE;
- if (conn->oauth_client_secret && *conn->oauth_client_secret)
- auth_method = I_TOKEN_AUTH_METHOD_SECRET_BASIC;
-
err = i_set_parameter_list(&session,
I_OPT_CLIENT_ID, conn->oauth_client_id,
I_OPT_CLIENT_SECRET, conn->oauth_client_secret,
@@ -225,7 +325,7 @@ get_auth_token(PGconn *conn)
iddawc_error(conn, err, "failed to set client identifier");
goto cleanup;
}
-
+
err = i_run_device_auth_request(&session);
if (err)
{
@@ -278,14 +378,15 @@ get_auth_token(PGconn *conn)
if (!user_prompted)
{
+ oauthMsgObj.verification_uri = verification_uri;
+ oauthMsgObj.user_code = user_code;
+ conn->oauthNoticeHooks.noticeRecArg = (void*) &oauthMsgObj;
+
/*
* Now that we know the token endpoint isn't broken, give the user
* the login instructions.
- */
- pqInternalNotice(&conn->noticeHooks,
- "Visit %s and enter the code: %s",
- verification_uri, user_code);
-
+ */
+ pqInternalOAuthNotice(&conn->oauthNoticeHooks, "");
user_prompted = true;
}
@@ -300,7 +401,7 @@ get_auth_token(PGconn *conn)
* A slow_down error requires us to permanently increase our retry
* interval by five seconds. RFC 8628, Sec. 3.5.
*/
- if (!strcmp(error_code, "slow_down"))
+ //if (!strcmp(error_code, "slow_down"))
{
interval += 5;
i_set_int_parameter(&session, I_OPT_DEVICE_AUTH_INTERVAL, interval);
@@ -323,6 +424,14 @@ get_auth_token(PGconn *conn)
access_token = i_get_str_parameter(&session, I_OPT_ACCESS_TOKEN);
token_type = i_get_str_parameter(&session, I_OPT_TOKEN_TYPE);
+ refresh_token = i_get_str_parameter(&session, I_OPT_REFRESH_TOKEN);
+
+ if(refresh_token)
+ {
+ MemSet(&oauthMsgObj, 0x00, sizeof(PGOAuthMsgObj));
+ oauthMsgObj.refresh_token = refresh_token;
+ pqInternalOAuthNotice(&conn->oauthNoticeHooks, "");
+ }
if (!access_token || !token_type || strcasecmp(token_type, "Bearer"))
{
@@ -358,6 +467,8 @@ client_initial_response(PGconn *conn)
PQExpBuffer discovery_buf = NULL;
char *token = NULL;
char *response = NULL;
+ pg_oauth_flow_type flow_type;
+ char oauth_flow_str[3];
token_buf = createPQExpBuffer();
if (!token_buf)
@@ -385,8 +496,26 @@ client_initial_response(PGconn *conn)
token = get_auth_token(conn);
if (!token)
goto cleanup;
-
+
+ if(strcmp(token, "") == 0)
+ {
+ flow_type = oauth_get_flow_type(conn->oauth_flow_type);
+ if(flow_type == OAUTH_NONE)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("value passed in oauth_flow_type is not valid."\
+ "supported flows: client_secret, device_code, auth_code_pkce, refresh_token\n"));
+ goto cleanup;
+ }
+ else
+ {
+ sprintf(oauth_flow_str, "%d", flow_type);
+ token = strdup(oauth_flow_str);
+ }
+ }
appendPQExpBuffer(token_buf, resp_format, token);
+// elog(INFO, "fe-flowtype: %s", token);
+
if (PQExpBufferBroken(token_buf))
goto cleanup;
@@ -406,6 +535,9 @@ cleanup:
#define ERROR_STATUS_FIELD "status"
#define ERROR_SCOPE_FIELD "scope"
#define ERROR_OPENID_CONFIGURATION_FIELD "openid-configuration"
+#define ERROR_ISSUER_URL_FIELD "issuer"
+#define ERROR_AUTH_ENDPOINT_FIELD "authorization_endpoint"
+#define ERROR_TOKEN_ENDPOINT_FIELD "token_endpoint"
struct json_ctx
{
@@ -420,6 +552,9 @@ struct json_ctx
char *status;
char *scope;
char *discovery_uri;
+ char *issuer_url;
+ char *auth_endpoint;
+ char *token_endpoint;
};
#define oauth_json_has_error(ctx) \
@@ -491,6 +626,21 @@ oauth_json_object_field_start(void *state, char *name, bool isnull)
ctx->target_field_name = ERROR_OPENID_CONFIGURATION_FIELD;
ctx->target_field = &ctx->discovery_uri;
}
+ else if(!strcmp(name, ERROR_ISSUER_URL_FIELD))
+ {
+ ctx->target_field_name = ERROR_ISSUER_URL_FIELD;
+ ctx->target_field = &ctx->issuer_url;
+ }
+ else if(!strcmp(name, ERROR_AUTH_ENDPOINT_FIELD))
+ {
+ ctx->target_field_name = ERROR_AUTH_ENDPOINT_FIELD;
+ ctx->target_field = &ctx->auth_endpoint;
+ }
+ else if(!strcmp(name, ERROR_TOKEN_ENDPOINT_FIELD))
+ {
+ ctx->target_field_name = ERROR_TOKEN_ENDPOINT_FIELD;
+ ctx->target_field = &ctx->token_endpoint;
+ }
}
free(name);
@@ -627,6 +777,15 @@ handle_oauth_sasl_error(PGconn *conn, char *msg, int msglen)
conn->oauth_scope = ctx.scope;
}
+
+ if(ctx.issuer_url)
+ {
+ if(conn->oauth_issuer)
+ free(conn->oauth_issuer);
+
+ conn->oauth_issuer = ctx.issuer_url;
+ }
+
/* TODO: missing error scope should clear any existing connection scope */
if (!ctx.status)
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 64f27fee18..e6e8dc48e2 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -358,6 +358,18 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"OAuth-Scope", "", 15,
offsetof(struct pg_conn, oauth_scope)},
+ {"oauth_bearer_token", NULL, NULL, NULL,
+ "OAuth-Bearer", "", 20,
+ offsetof(struct pg_conn, oauth_bearer_token)},
+
+ {"oauth_flow_type", NULL, NULL, NULL,
+ "OAuth-Flow-Type", "", 20,
+ offsetof(struct pg_conn, oauth_flow_type)},
+
+ {"oauth_refresh_token", NULL, NULL, NULL,
+ "OAuth-Refresh-Token", "", 40,
+ offsetof(struct pg_conn, oauth_refresh_token)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
@@ -427,6 +439,7 @@ static PQconninfoOption *conninfo_find(PQconninfoOption *connOptions,
const char *keyword);
static void defaultNoticeReceiver(void *arg, const PGresult *res);
static void defaultNoticeProcessor(void *arg, const char *message);
+static void OAuthMsgObjReceiver(void *arg, const PGresult *res);
static int parseServiceInfo(PQconninfoOption *options,
PQExpBuffer errorMessage);
static int parseServiceFile(const char *serviceFile,
@@ -3926,6 +3939,7 @@ makeEmptyPGconn(void)
/* install default notice hooks */
conn->noticeHooks.noticeRec = defaultNoticeReceiver;
conn->noticeHooks.noticeProc = defaultNoticeProcessor;
+ conn->oauthNoticeHooks.noticeRec = OAuthMsgObjReceiver;
conn->status = CONNECTION_BAD;
conn->asyncStatus = PGASYNC_IDLE;
@@ -4073,6 +4087,12 @@ freePGconn(PGconn *conn)
free(conn->oauth_client_secret);
if (conn->oauth_scope)
free(conn->oauth_scope);
+ if(conn->oauth_bearer_token)
+ free(conn->oauth_bearer_token);
+ if(conn->oauth_flow_type)
+ free(conn->oauth_flow_type);
+ if(conn->oauth_refresh_token)
+ free(conn->oauth_refresh_token);
termPQExpBuffer(&conn->errorMessage);
termPQExpBuffer(&conn->workBuffer);
@@ -6991,6 +7011,32 @@ defaultNoticeProcessor(void *arg, const char *message)
fprintf(stderr, "%s", message);
}
+static void
+OAuthMsgObjReceiver(void *arg, const PGresult *res)
+{
+ PGOAuthMsgObj *oauthMsg = (PGOAuthMsgObj *) arg;
+
+ if(oauthMsg->message)
+ {
+ fprintf(stderr, "%s\n", oauthMsg->message);
+ }
+
+ if(oauthMsg->verification_uri)
+ {
+ fprintf(stderr, "Visit: %s\n", oauthMsg->verification_uri);
+ }
+
+ if(oauthMsg->user_code)
+ {
+ fprintf(stderr, "Enter: %s\n", oauthMsg->user_code);
+ }
+
+ if(oauthMsg->refresh_token)
+ {
+ fprintf(stderr, "Refresh Token: %s\n", oauthMsg->refresh_token);
+ }
+}
+
/*
* returns a pointer to the next token or NULL if the current
* token doesn't match
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index da229d632a..4789c1a1fe 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -976,6 +976,58 @@ pqInternalNotice(const PGNoticeHooks *hooks, const char *fmt,...)
PQclear(res);
}
+/*
+ * pqInternalOAuthNotice - it is similar to pqInternalNotice
+ * except that OAuthNoticeHooks are invoked.
+ */
+void
+pqInternalOAuthNotice(const PGOAuthNoticeHooks *hooks, const char *fmt,...)
+{
+ char msgBuf[1024];
+ va_list args;
+ PGresult *res;
+
+ if (hooks->noticeRec == NULL)
+ return; /* nobody home to receive notice? */
+
+ /* Format the message */
+ va_start(args, fmt);
+ vsnprintf(msgBuf, sizeof(msgBuf), libpq_gettext(fmt), args);
+ va_end(args);
+ msgBuf[sizeof(msgBuf) - 1] = '\0'; /* make real sure it's terminated */
+
+ /* Make a PGresult to pass to the notice receiver */
+ res = PQmakeEmptyPGresult(NULL, PGRES_NONFATAL_ERROR);
+ if (!res)
+ return;
+ res->oauthNoticeHooks = *hooks;
+ res->oauthNoticeHooks.noticeRecArg = hooks->noticeRecArg;
+
+ /*
+ * Set up fields of notice.
+ */
+ pqSaveMessageField(res, PG_DIAG_MESSAGE_PRIMARY, msgBuf);
+ pqSaveMessageField(res, PG_DIAG_SEVERITY, libpq_gettext("NOTICE"));
+ pqSaveMessageField(res, PG_DIAG_SEVERITY_NONLOCALIZED, "NOTICE");
+ /* XXX should provide a SQLSTATE too? */
+
+ /*
+ * Result text is always just the primary message + newline. If we can't
+ * allocate it, substitute "out of memory", as in pqSetResultError.
+ */
+ res->errMsg = (char *) pqResultAlloc(res, strlen(msgBuf) + 2, false);
+ if (res->errMsg)
+ sprintf(res->errMsg, "%s\n", msgBuf);
+ else
+ res->errMsg = libpq_gettext("out of memory\n");
+
+ /*
+ * Pass to receiver, then free it.
+ */
+ res->oauthNoticeHooks.noticeRec(res->oauthNoticeHooks.noticeRecArg, res);
+ PQclear(res);
+}
+
/*
* pqAddTuple
* add a row pointer to the PGresult structure, growing it if necessary
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index b7df3224c0..ee5b2e2b59 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -197,6 +197,9 @@ typedef struct pgNotify
typedef void (*PQnoticeReceiver) (void *arg, const PGresult *res);
typedef void (*PQnoticeProcessor) (void *arg, const char *message);
+/* Function types for notice-handling callbacks */
+typedef void (*PQOAuthNoticeReceiver) (void *arg, const PGresult *res);
+
/* Print options for PQprint() */
typedef char pqbool;
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index ae76ae0e8f..3155d81e00 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -157,6 +157,24 @@ typedef struct
void *noticeProcArg;
} PGNoticeHooks;
+typedef struct
+{
+ char *verification_uri; /* URI the user should go to with the user_code in order to sign in */
+ char *user_code; /* used to identify the session on a secondary device */
+ char *refresh_token;
+ char *message; /* string with instructions for the user. */
+ char *response_error; /*JSON error response (400 Bad Request) */
+ uint expires_in; /* number of seconds before the device_code expire */
+ uint interval; /* number of seconds the client should wait between polling requests */
+} PGOAuthMsgObj;
+
+/* Fields needed for oauth callback handling */
+typedef struct
+{
+ PQOAuthNoticeReceiver noticeRec; /* OAuth notice message receiver */
+ void *noticeRecArg;
+} PGOAuthNoticeHooks;
+
typedef struct PGEvent
{
PGEventProc proc; /* the function to call on events */
@@ -186,6 +204,7 @@ struct pg_result
* on the PGresult don't have to reference the PGconn.
*/
PGNoticeHooks noticeHooks;
+ PGOAuthNoticeHooks oauthNoticeHooks;
PGEvent *events;
int nEvents;
int client_encoding; /* encoding id */
@@ -343,6 +362,17 @@ typedef struct pg_conn_host
* found in password file. */
} pg_conn_host;
+typedef enum pg_oauth_flow_type
+{
+ OAUTH_DEVICE_CODE,
+ OAUTH_CLIENT_CREDENTIALS,
+ OAUTH_AUTH,
+ OAUTH_AUTH_PKCE,
+ OAUTH_REFRESH_TOKEN,
+ OAUTH_AUTH_CODE,
+ OAUTH_NONE
+} pg_oauth_flow_type;
+
/*
* PGconn stores all the state data associated with a single connection
* to a backend.
@@ -403,6 +433,9 @@ struct pg_conn
char *oauth_client_id; /* client identifier */
char *oauth_client_secret; /* client secret */
char *oauth_scope; /* access token scope */
+ char *oauth_bearer_token; /* oauth token */
+ char *oauth_flow_type; /* oauth flow type */
+ char *oauth_refresh_token; /* refresh token */
bool oauth_want_retry; /* should we retry on failure? */
/* Optional file to write trace info to */
@@ -412,6 +445,9 @@ struct pg_conn
/* Callback procedures for notice message processing */
PGNoticeHooks noticeHooks;
+ /* Callback procedures for notifying messages during oauth flows*/
+ PGOAuthNoticeHooks oauthNoticeHooks;
+
/* Event procs registered via PQregisterEventProc */
PGEvent *events; /* expandable array of event data */
int nEvents; /* number of active events */
@@ -677,6 +713,7 @@ extern void pqClearAsyncResult(PGconn *conn);
extern void pqSaveErrorResult(PGconn *conn);
extern PGresult *pqPrepareAsyncResult(PGconn *conn);
extern void pqInternalNotice(const PGNoticeHooks *hooks, const char *fmt,...) pg_attribute_printf(2, 3);
+extern void pqInternalOAuthNotice(const PGOAuthNoticeHooks *hooks, const char *fmt,...);
extern void pqSaveMessageField(PGresult *res, char code,
const char *value);
extern void pqSaveParameterStatus(PGconn *conn, const char *name,
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-27 01:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-27 21:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-30 14:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-30 20:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-10-03 18:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-23 09:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
@ 2022-11-23 20:05 ` Jacob Champion <[email protected]>
2022-11-24 03:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2022-11-23 20:05 UTC (permalink / raw)
To: mahendrakar s <[email protected]>; Andrey Chudnovsky <[email protected]>; +Cc: [email protected] <[email protected]>; [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
On 11/23/22 01:58, mahendrakar s wrote:
> We validated on libpq handling OAuth natively with different flows
> with different OIDC certified providers.
>
> Flows: Device Code, Client Credentials and Refresh Token.
> Providers: Microsoft, Google and Okta.
Great, thank you!
> Also validated with OAuth provider Github.
(How did you get discovery working? I tried this and had to give up
eventually.)
> We propose using OpenID Connect (OIDC) as the protocol, instead of
> OAuth, as it is:
> - Discovery mechanism to bridge the differences and provide metadata.
> - Stricter protocol and certification process to reliably identify
> which providers can be supported.
> - OIDC is designed for authentication, while the main purpose of OAUTH is to
> authorize applications on behalf of the user.
How does this differ from the previous proposal? The OAUTHBEARER SASL
mechanism already relies on OIDC for discovery. (I think that decision
is confusing from an architectural and naming standpoint, but I don't
think they really had an alternative...)
> Github is not OIDC certified, so won’t be supported with this proposal.
> However, it may be supported in the future through the ability for the
> extension to provide custom discovery document content.
Right.
> OpenID configuration has a well-known discovery mechanism
> for the provider configuration URI which is
> defined in OpenID Connect. It allows libpq to fetch
> metadata about provider (i.e endpoints, supported grants, response types, etc).
Sure, but this is already how the original PoC works. The test suite
implements an OIDC provider, for instance. Is there something different
to this that I'm missing?
> In the attached patch (based on V2 patch in the thread and does not
> contain Samay's changes):
> - Provider can configure issuer url and scope through the options hook.)
> - Server passes on an open discovery url and scope to libpq.
> - Libpq handles OAuth flow based on the flow_type sent in the
> connection string [1].
> - Added callbacks to notify a structure to client tools if OAuth flow
> requires user interaction.
> - Pg backend uses hooks to validate bearer token.
Thank you for the sample!
> Note that authentication code flow with PKCE for GUI clients is not
> implemented yet.
>
> Proposed next steps:
> - Broaden discussion to reach agreement on the approach.
High-level thoughts on this particular patch (I assume you're not
looking for low-level implementation comments yet):
0) The original hook proposal upthread, I thought, was about allowing
libpq's flow implementation to be switched out by the application. I
don't see that approach taken here. It's fine if that turned out to be a
bad idea, of course, but this patch doesn't seem to match what we were
talking about.
1) I'm really concerned about the sudden explosion of flows. We went
from one flow (Device Authorization) to six. It's going to be hard
enough to validate that *one* flow is useful and can be securely
deployed by end users; I don't think we're going to be able to maintain
six, especially in combination with my statement that iddawc is not an
appropriate dependency for us.
I'd much rather give applications the ability to use their own OAuth
code, and then maintain within libpq only the flows that are broadly
useful. This ties back to (0) above.
2) Breaking the refresh token into its own pseudoflow is, I think,
passing the buck onto the user for something that's incredibly security
sensitive. The refresh token is powerful; I don't really want it to be
printed anywhere, let alone copy-pasted by the user. Imagine the
phishing opportunities.
If we want to support refresh tokens, I believe we should be developing
a plan to cache and secure them within the client. They should be used
as an accelerator for other flows, not as their own flow.
3) I don't like the departure from the OAUTHBEARER mechanism that's
presented here. For one, since I can't see a sample plugin that makes
use of the "flow type" magic numbers that have been added, I don't
really understand why the extension to the mechanism is necessary.
For two, if we think OAUTHBEARER is insufficient, the people who wrote
it would probably like to hear about it. Claiming support for a spec,
and then implementing an extension without review from the people who
wrote the spec, is not something I'm personally interested in doing.
4) The test suite is still broken, so it's difficult to see these things
in practice for review purposes.
> - Implement libpq changes without iddawc
This in particular will be much easier with a functioning test suite,
and with a smaller number of flows.
> - Prototype GUI flow with pgAdmin
Cool!
Thanks,
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-27 01:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-27 21:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-30 14:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-30 20:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-10-03 18:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-23 09:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-23 20:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2022-11-24 03:45 ` Andrey Chudnovsky <[email protected]>
2022-11-24 08:20 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-29 21:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 2 replies; 194+ messages in thread
From: Andrey Chudnovsky @ 2022-11-24 03:45 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: mahendrakar s <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
> How does this differ from the previous proposal? The OAUTHBEARER SASL
> mechanism already relies on OIDC for discovery. (I think that decision
> is confusing from an architectural and naming standpoint, but I don't
> think they really had an alternative...)
Mostly terminology questions here. OAUTHBEARER SASL appears to be the
spec about using OAUTH2 tokens for Authentication.
While any OAUTH2 can generally work, we propose to specifically
highlight that only OIDC providers can be supported, as we need the
discovery document.
And we won't be able to support Github under that requirement.
Since the original patch used that too - no change on that, just
confirmation that we need OIDC compliance.
> 0) The original hook proposal upthread, I thought, was about allowing
> libpq's flow implementation to be switched out by the application. I
> don't see that approach taken here. It's fine if that turned out to be a
> bad idea, of course, but this patch doesn't seem to match what we were
> talking about.
We still plan to allow the client to pass the token. Which is a
generic way to implement its own OAUTH flows.
> 1) I'm really concerned about the sudden explosion of flows. We went
> from one flow (Device Authorization) to six. It's going to be hard
> enough to validate that *one* flow is useful and can be securely
> deployed by end users; I don't think we're going to be able to maintain
> six, especially in combination with my statement that iddawc is not an
> appropriate dependency for us.
> I'd much rather give applications the ability to use their own OAuth
> code, and then maintain within libpq only the flows that are broadly
> useful. This ties back to (0) above.
We consider the following set of flows to be minimum required:
- Client Credentials - For Service to Service scenarios.
- Authorization Code with PKCE - For rich clients,including pgAdmin.
- Device code - for psql (and possibly other non-GUI clients).
- Refresh code (separate discussion)
Which is pretty much the list described here:
https://oauth.net/2/grant-types/ and in OAUTH2 specs.
Client Credentials is very simple, so does Refresh Code.
If you prefer to pick one of the richer flows, Authorization code for
GUI scenarios is probably much more widely used.
Plus it's easier to implement too, as interaction goes through a
series of callbacks. No polling required.
> 2) Breaking the refresh token into its own pseudoflow is, I think,
> passing the buck onto the user for something that's incredibly security
> sensitive. The refresh token is powerful; I don't really want it to be
> printed anywhere, let alone copy-pasted by the user. Imagine the
> phishing opportunities.
> If we want to support refresh tokens, I believe we should be developing
> a plan to cache and secure them within the client. They should be used
> as an accelerator for other flows, not as their own flow.
It's considered a separate "grant_type" in the specs / APIs.
https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens
For the clients, it would be storing the token and using it to authenticate.
On the question of sensitivity, secure credentials stores are
different for each platform, with a lot of cloud offerings for this.
pgAdmin, for example, has its own way to secure credentials to avoid
asking users for passwords every time the app is opened.
I believe we should delegate the refresh token management to the clients.
>3) I don't like the departure from the OAUTHBEARER mechanism that's
> presented here. For one, since I can't see a sample plugin that makes
> use of the "flow type" magic numbers that have been added, I don't
> really understand why the extension to the mechanism is necessary.
I don't think it's much of a departure, but rather a separation of
responsibilities between libpq and upstream clients.
As libpq can be used in different apps, the client would need
different types of flows/grants.
I.e. those need to be provided to libpq at connection initialization
or some other point.
We will change to "grant_type" though and use string to be closer to the spec.
What do you think is the best way for the client to signal which OAUTH
flow should be used?
On Wed, Nov 23, 2022 at 12:05 PM Jacob Champion <[email protected]> wrote:
>
> On 11/23/22 01:58, mahendrakar s wrote:
> > We validated on libpq handling OAuth natively with different flows
> > with different OIDC certified providers.
> >
> > Flows: Device Code, Client Credentials and Refresh Token.
> > Providers: Microsoft, Google and Okta.
>
> Great, thank you!
>
> > Also validated with OAuth provider Github.
>
> (How did you get discovery working? I tried this and had to give up
> eventually.)
>
> > We propose using OpenID Connect (OIDC) as the protocol, instead of
> > OAuth, as it is:
> > - Discovery mechanism to bridge the differences and provide metadata.
> > - Stricter protocol and certification process to reliably identify
> > which providers can be supported.
> > - OIDC is designed for authentication, while the main purpose of OAUTH is to
> > authorize applications on behalf of the user.
>
> How does this differ from the previous proposal? The OAUTHBEARER SASL
> mechanism already relies on OIDC for discovery. (I think that decision
> is confusing from an architectural and naming standpoint, but I don't
> think they really had an alternative...)
>
> > Github is not OIDC certified, so won’t be supported with this proposal.
> > However, it may be supported in the future through the ability for the
> > extension to provide custom discovery document content.
>
> Right.
>
> > OpenID configuration has a well-known discovery mechanism
> > for the provider configuration URI which is
> > defined in OpenID Connect. It allows libpq to fetch
> > metadata about provider (i.e endpoints, supported grants, response types, etc).
>
> Sure, but this is already how the original PoC works. The test suite
> implements an OIDC provider, for instance. Is there something different
> to this that I'm missing?
>
> > In the attached patch (based on V2 patch in the thread and does not
> > contain Samay's changes):
> > - Provider can configure issuer url and scope through the options hook.)
> > - Server passes on an open discovery url and scope to libpq.
> > - Libpq handles OAuth flow based on the flow_type sent in the
> > connection string [1].
> > - Added callbacks to notify a structure to client tools if OAuth flow
> > requires user interaction.
> > - Pg backend uses hooks to validate bearer token.
>
> Thank you for the sample!
>
> > Note that authentication code flow with PKCE for GUI clients is not
> > implemented yet.
> >
> > Proposed next steps:
> > - Broaden discussion to reach agreement on the approach.
>
> High-level thoughts on this particular patch (I assume you're not
> looking for low-level implementation comments yet):
>
> 0) The original hook proposal upthread, I thought, was about allowing
> libpq's flow implementation to be switched out by the application. I
> don't see that approach taken here. It's fine if that turned out to be a
> bad idea, of course, but this patch doesn't seem to match what we were
> talking about.
>
> 1) I'm really concerned about the sudden explosion of flows. We went
> from one flow (Device Authorization) to six. It's going to be hard
> enough to validate that *one* flow is useful and can be securely
> deployed by end users; I don't think we're going to be able to maintain
> six, especially in combination with my statement that iddawc is not an
> appropriate dependency for us.
>
> I'd much rather give applications the ability to use their own OAuth
> code, and then maintain within libpq only the flows that are broadly
> useful. This ties back to (0) above.
>
> 2) Breaking the refresh token into its own pseudoflow is, I think,
> passing the buck onto the user for something that's incredibly security
> sensitive. The refresh token is powerful; I don't really want it to be
> printed anywhere, let alone copy-pasted by the user. Imagine the
> phishing opportunities.
>
> If we want to support refresh tokens, I believe we should be developing
> a plan to cache and secure them within the client. They should be used
> as an accelerator for other flows, not as their own flow.
>
> 3) I don't like the departure from the OAUTHBEARER mechanism that's
> presented here. For one, since I can't see a sample plugin that makes
> use of the "flow type" magic numbers that have been added, I don't
> really understand why the extension to the mechanism is necessary.
>
> For two, if we think OAUTHBEARER is insufficient, the people who wrote
> it would probably like to hear about it. Claiming support for a spec,
> and then implementing an extension without review from the people who
> wrote the spec, is not something I'm personally interested in doing.
>
> 4) The test suite is still broken, so it's difficult to see these things
> in practice for review purposes.
>
> > - Implement libpq changes without iddawc
>
> This in particular will be much easier with a functioning test suite,
> and with a smaller number of flows.
>
> > - Prototype GUI flow with pgAdmin
>
> Cool!
>
> Thanks,
> --Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-27 01:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-27 21:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-30 14:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-30 20:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-10-03 18:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-23 09:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-23 20:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-11-24 03:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
@ 2022-11-24 08:20 ` mahendrakar s <[email protected]>
2022-11-29 21:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: mahendrakar s @ 2022-11-24 08:20 UTC (permalink / raw)
To: Andrey Chudnovsky <[email protected]>; +Cc: Jacob Champion <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
Hi Jacob,
I had validated Github by skipping the discovery mechanism and letting
the provider extension pass on the endpoints. This is just for
validation purposes.
If it needs to be supported, then need a way to send the discovery
document from extension.
Thanks,
Mahendrakar.
On Thu, 24 Nov 2022 at 09:16, Andrey Chudnovsky <[email protected]> wrote:
>
> > How does this differ from the previous proposal? The OAUTHBEARER SASL
> > mechanism already relies on OIDC for discovery. (I think that decision
> > is confusing from an architectural and naming standpoint, but I don't
> > think they really had an alternative...)
> Mostly terminology questions here. OAUTHBEARER SASL appears to be the
> spec about using OAUTH2 tokens for Authentication.
> While any OAUTH2 can generally work, we propose to specifically
> highlight that only OIDC providers can be supported, as we need the
> discovery document.
> And we won't be able to support Github under that requirement.
> Since the original patch used that too - no change on that, just
> confirmation that we need OIDC compliance.
>
> > 0) The original hook proposal upthread, I thought, was about allowing
> > libpq's flow implementation to be switched out by the application. I
> > don't see that approach taken here. It's fine if that turned out to be a
> > bad idea, of course, but this patch doesn't seem to match what we were
> > talking about.
> We still plan to allow the client to pass the token. Which is a
> generic way to implement its own OAUTH flows.
>
> > 1) I'm really concerned about the sudden explosion of flows. We went
> > from one flow (Device Authorization) to six. It's going to be hard
> > enough to validate that *one* flow is useful and can be securely
> > deployed by end users; I don't think we're going to be able to maintain
> > six, especially in combination with my statement that iddawc is not an
> > appropriate dependency for us.
>
> > I'd much rather give applications the ability to use their own OAuth
> > code, and then maintain within libpq only the flows that are broadly
> > useful. This ties back to (0) above.
> We consider the following set of flows to be minimum required:
> - Client Credentials - For Service to Service scenarios.
> - Authorization Code with PKCE - For rich clients,including pgAdmin.
> - Device code - for psql (and possibly other non-GUI clients).
> - Refresh code (separate discussion)
> Which is pretty much the list described here:
> https://oauth.net/2/grant-types/ and in OAUTH2 specs.
> Client Credentials is very simple, so does Refresh Code.
> If you prefer to pick one of the richer flows, Authorization code for
> GUI scenarios is probably much more widely used.
> Plus it's easier to implement too, as interaction goes through a
> series of callbacks. No polling required.
>
> > 2) Breaking the refresh token into its own pseudoflow is, I think,
> > passing the buck onto the user for something that's incredibly security
> > sensitive. The refresh token is powerful; I don't really want it to be
> > printed anywhere, let alone copy-pasted by the user. Imagine the
> > phishing opportunities.
>
> > If we want to support refresh tokens, I believe we should be developing
> > a plan to cache and secure them within the client. They should be used
> > as an accelerator for other flows, not as their own flow.
> It's considered a separate "grant_type" in the specs / APIs.
> https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens
>
> For the clients, it would be storing the token and using it to authenticate.
> On the question of sensitivity, secure credentials stores are
> different for each platform, with a lot of cloud offerings for this.
> pgAdmin, for example, has its own way to secure credentials to avoid
> asking users for passwords every time the app is opened.
> I believe we should delegate the refresh token management to the clients.
>
> >3) I don't like the departure from the OAUTHBEARER mechanism that's
> > presented here. For one, since I can't see a sample plugin that makes
> > use of the "flow type" magic numbers that have been added, I don't
> > really understand why the extension to the mechanism is necessary.
> I don't think it's much of a departure, but rather a separation of
> responsibilities between libpq and upstream clients.
> As libpq can be used in different apps, the client would need
> different types of flows/grants.
> I.e. those need to be provided to libpq at connection initialization
> or some other point.
> We will change to "grant_type" though and use string to be closer to the spec.
> What do you think is the best way for the client to signal which OAUTH
> flow should be used?
>
> On Wed, Nov 23, 2022 at 12:05 PM Jacob Champion <[email protected]> wrote:
> >
> > On 11/23/22 01:58, mahendrakar s wrote:
> > > We validated on libpq handling OAuth natively with different flows
> > > with different OIDC certified providers.
> > >
> > > Flows: Device Code, Client Credentials and Refresh Token.
> > > Providers: Microsoft, Google and Okta.
> >
> > Great, thank you!
> >
> > > Also validated with OAuth provider Github.
> >
> > (How did you get discovery working? I tried this and had to give up
> > eventually.)
> >
> > > We propose using OpenID Connect (OIDC) as the protocol, instead of
> > > OAuth, as it is:
> > > - Discovery mechanism to bridge the differences and provide metadata.
> > > - Stricter protocol and certification process to reliably identify
> > > which providers can be supported.
> > > - OIDC is designed for authentication, while the main purpose of OAUTH is to
> > > authorize applications on behalf of the user.
> >
> > How does this differ from the previous proposal? The OAUTHBEARER SASL
> > mechanism already relies on OIDC for discovery. (I think that decision
> > is confusing from an architectural and naming standpoint, but I don't
> > think they really had an alternative...)
> >
> > > Github is not OIDC certified, so won’t be supported with this proposal.
> > > However, it may be supported in the future through the ability for the
> > > extension to provide custom discovery document content.
> >
> > Right.
> >
> > > OpenID configuration has a well-known discovery mechanism
> > > for the provider configuration URI which is
> > > defined in OpenID Connect. It allows libpq to fetch
> > > metadata about provider (i.e endpoints, supported grants, response types, etc).
> >
> > Sure, but this is already how the original PoC works. The test suite
> > implements an OIDC provider, for instance. Is there something different
> > to this that I'm missing?
> >
> > > In the attached patch (based on V2 patch in the thread and does not
> > > contain Samay's changes):
> > > - Provider can configure issuer url and scope through the options hook.)
> > > - Server passes on an open discovery url and scope to libpq.
> > > - Libpq handles OAuth flow based on the flow_type sent in the
> > > connection string [1].
> > > - Added callbacks to notify a structure to client tools if OAuth flow
> > > requires user interaction.
> > > - Pg backend uses hooks to validate bearer token.
> >
> > Thank you for the sample!
> >
> > > Note that authentication code flow with PKCE for GUI clients is not
> > > implemented yet.
> > >
> > > Proposed next steps:
> > > - Broaden discussion to reach agreement on the approach.
> >
> > High-level thoughts on this particular patch (I assume you're not
> > looking for low-level implementation comments yet):
> >
> > 0) The original hook proposal upthread, I thought, was about allowing
> > libpq's flow implementation to be switched out by the application. I
> > don't see that approach taken here. It's fine if that turned out to be a
> > bad idea, of course, but this patch doesn't seem to match what we were
> > talking about.
> >
> > 1) I'm really concerned about the sudden explosion of flows. We went
> > from one flow (Device Authorization) to six. It's going to be hard
> > enough to validate that *one* flow is useful and can be securely
> > deployed by end users; I don't think we're going to be able to maintain
> > six, especially in combination with my statement that iddawc is not an
> > appropriate dependency for us.
> >
> > I'd much rather give applications the ability to use their own OAuth
> > code, and then maintain within libpq only the flows that are broadly
> > useful. This ties back to (0) above.
> >
> > 2) Breaking the refresh token into its own pseudoflow is, I think,
> > passing the buck onto the user for something that's incredibly security
> > sensitive. The refresh token is powerful; I don't really want it to be
> > printed anywhere, let alone copy-pasted by the user. Imagine the
> > phishing opportunities.
> >
> > If we want to support refresh tokens, I believe we should be developing
> > a plan to cache and secure them within the client. They should be used
> > as an accelerator for other flows, not as their own flow.
> >
> > 3) I don't like the departure from the OAUTHBEARER mechanism that's
> > presented here. For one, since I can't see a sample plugin that makes
> > use of the "flow type" magic numbers that have been added, I don't
> > really understand why the extension to the mechanism is necessary.
> >
> > For two, if we think OAUTHBEARER is insufficient, the people who wrote
> > it would probably like to hear about it. Claiming support for a spec,
> > and then implementing an extension without review from the people who
> > wrote the spec, is not something I'm personally interested in doing.
> >
> > 4) The test suite is still broken, so it's difficult to see these things
> > in practice for review purposes.
> >
> > > - Implement libpq changes without iddawc
> >
> > This in particular will be much easier with a functioning test suite,
> > and with a smaller number of flows.
> >
> > > - Prototype GUI flow with pgAdmin
> >
> > Cool!
> >
> > Thanks,
> > --Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-27 01:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-27 21:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-30 14:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-30 20:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-10-03 18:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-23 09:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-23 20:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-11-24 03:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-24 08:20 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
@ 2022-11-29 21:19 ` Jacob Champion <[email protected]>
2022-12-06 00:15 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2022-11-29 21:19 UTC (permalink / raw)
To: mahendrakar s <[email protected]>; Andrey Chudnovsky <[email protected]>; +Cc: [email protected] <[email protected]>; [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
On 11/24/22 00:20, mahendrakar s wrote:
> I had validated Github by skipping the discovery mechanism and letting
> the provider extension pass on the endpoints. This is just for
> validation purposes.
> If it needs to be supported, then need a way to send the discovery
> document from extension.
Yeah. I had originally bounced around the idea that we could send a
data:// URL, but I think that opens up problems.
You're supposed to be able to link the issuer URI with the URI you got
the configuration from, and if they're different, you bail out. If a
server makes up its own OpenID configuration, we'd have to bypass that
safety check, and decide what the risks and mitigations are... Not sure
it's worth it.
Especially if you could just lobby GitHub to, say, provide an OpenID
config. (Maybe there's a security-related reason they don't.)
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-27 01:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-27 21:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-30 14:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-30 20:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-10-03 18:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-23 09:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-23 20:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-11-24 03:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-24 08:20 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-29 21:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2022-12-06 00:15 ` Andrey Chudnovsky <[email protected]>
2022-12-07 19:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Andrey Chudnovsky @ 2022-12-06 00:15 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: mahendrakar s <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
Jacob,
Thanks for your feedback.
I think we can focus on the roles and responsibilities of the components first.
Details of the patch can be elaborated. Like "flow type code" is a
mistake on our side, and we will use the term "grant_type" which is
defined by OIDC spec. As well as details of usage of refresh_token.
> Rather than decouple things, I think this proposal drives a spike
> through the client app, libpq, and the server. Please correct me if I've
> misunderstood pieces of the patch, but the following is my view of it:
> What used to be a validator hook on the server side now actively
> participates in the client-side flow for some reason. (I still don't
> understand what the server is supposed to do with that knowledge.
> Changing your authz requirements based on the flow the client wants to
> use seems like a good way to introduce bugs.)
> The client-side hook is now coupled to the application logic: you have
> to know to expect an error from the first PQconnect*() call, then check
> whatever magic your hook has done for you to be able to set up the
> second call to PQconnect*() with the correctly scoped bearer token. So
> if you want to switch between the internal libpq OAuth implementation
> and your own hook, you have to rewrite your app logic.
Basically Yes. We propose an increase of the server side hook responsibility.
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-27 01:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-27 21:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-30 14:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-30 20:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-10-03 18:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-23 09:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-23 20:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-11-24 03:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-24 08:20 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-29 21:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-12-06 00:15 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
@ 2022-12-07 19:06 ` Jacob Champion <[email protected]>
2022-12-07 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2022-12-07 19:06 UTC (permalink / raw)
To: Andrey Chudnovsky <[email protected]>; +Cc: mahendrakar s <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
On Mon, Dec 5, 2022 at 4:15 PM Andrey Chudnovsky <[email protected]> wrote:
> I think we can focus on the roles and responsibilities of the components first.
> Details of the patch can be elaborated. Like "flow type code" is a
> mistake on our side, and we will use the term "grant_type" which is
> defined by OIDC spec. As well as details of usage of refresh_token.
(For the record, whether we call it "flow type" or "grant type"
doesn't address my concern.)
> Basically Yes. We propose an increase of the server side hook responsibility.
> From just validating the token, to also return the provider root URL
> and required audience. And possibly provide more metadata in the
> future.
I think it's okay to have the extension and HBA collaborate to provide
discovery information. Your proposal goes further than that, though,
and makes the server aware of the chosen client flow. That appears to
be an architectural violation: why does an OAuth resource server need
to know the client flow at all?
> Which is in our opinion aligned with SASL protocol, where the server
> side is responsible for telling the client auth requirements based on
> the requested role in the startup packet.
You've proposed an alternative SASL mechanism. There's nothing wrong
with that, per se, but I think it should be clear why we've chosen
something nonstandard.
> Our understanding is that in the original patch that information came
> purely from hba, and we propose extension being able to control that
> metadata.
> As we see extension as being owned by the identity provider, compared
> to HBA which is owned by the server administrator or cloud provider.
That seems reasonable, considering how tightly coupled the Issuer and
the token validation process are.
> 2. Server Owners / PAAS providers (On premise admins, Cloud providers,
> multi-cloud PAAS providers).
> - Install extensions and configure HBA to allow clients to
> authenticate with the identity providers of their choice.
(For a future conversation: they need to set up authorization, too,
with custom scopes or some other magic. It's not enough to check who
the token belongs to; even if Postgres is just using the verified
email from OpenID as an authenticator, you have to also know that the
user authorized the token -- and therefore the client -- to access
Postgres on their behalf.)
> 3. Client Application Developers (Data Wis, integration tools,
> PgAdmin, monitoring tools, e.t.c.)
> - Independent from specific Identity providers or server providers.
> Write one code for all identity providers.
Ideally, yes, but that only works if all identity providers implement
the same flows in compatible ways. We're already seeing instances
where that's not the case and we'll necessarily have to deal with that
up front.
> - Rely on application deployment owners to configure which OIDC
> provider to use across client and server setups.
> 4. Application Deployment Owners (End customers setting up applications)
> - The only actor actually aware of which identity provider to use.
> Configures the stack based on the Identity and PostgreSQL deployments
> they have.
(I have doubts that the roles will be as decoupled in practice as you
have described them, but I'd rather defer that for now.)
> The critical piece of the vision is (3.) above is applications
> agnostic of the identity providers. Those applications rely on
> properly configured servers and rich driver logic (libpq,
> com.postgresql, npgsql) to allow their application to popup auth
> windows or do service-to-service authentication with any provider. In
> our view that would significantly democratize the deployment of OAUTH
> authentication in the community.
That seems to be restating the goal of OAuth and OIDC. Can you explain
how the incompatible change allows you to accomplish this better than
standard implementations?
> In order to allow this separation, we propose:
> 1. HBA + Extension is the single source of truth of Provider root URL
> + Required Audience for each role. If some backfill for missing OIDC
> discovery is needed, the provider-specific extension would be
> providing it.
> 2. Client Application knows which grant_type to use in which scenario.
> But can be coded without knowledge of a specific provider. So can't
> provide discovery details.
> 3. Driver (libpq, others) - coordinate the authentication flow based
> on client grant_type and identity provider metadata to allow client
> applications to use any flow with any provider in a unified way.
>
> Yes, this would require a little more complicated flow between
> components than in your original patch.
Why? I claim that standard OAUTHBEARER can handle all of that. What
does your proposed architecture (the third diagram) enable that my
proposed hook (the second diagram) doesn't?
> And yes, more complexity comes
> with more opportunity to make bugs.
> However, I see PG Server and Libpq as the places which can have more
> complexity. For the purpose of making work for the community
> participants easier and simplify adoption.
>
> Does this make sense to you?
Some of it, but it hasn't really addressed the questions from my last mail.
Thanks,
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-27 01:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-27 21:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-30 14:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-30 20:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-10-03 18:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-23 09:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-23 20:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-11-24 03:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-24 08:20 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-29 21:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-12-06 00:15 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-12-07 19:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2022-12-07 23:22 ` Andrey Chudnovsky <[email protected]>
2022-12-08 04:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Andrey Chudnovsky @ 2022-12-07 23:22 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: mahendrakar s <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
> I think it's okay to have the extension and HBA collaborate to provide
> discovery information. Your proposal goes further than that, though,
> and makes the server aware of the chosen client flow. That appears to
> be an architectural violation: why does an OAuth resource server need
> to know the client flow at all?
Ok. It may have left there from intermediate iterations. We did
consider making extension drive the flow for specific grant_type, but
decided against that idea. For the same reason you point to.
Is it correct that your main concern about use of grant_type was that
it's propagated to the server? Then yes, we will remove sending it to
the server.
> Ideally, yes, but that only works if all identity providers implement
> the same flows in compatible ways. We're already seeing instances
> where that's not the case and we'll necessarily have to deal with that
> up front.
Yes, based on our analysis OIDC spec is detailed enough, that
providers implementing that one, can be supported with generic code in
libpq / client.
Github specifically won't fit there though. Microsoft Azure AD,
Google, Okta (including Auth0) will.
Theoretically discovery documents can be returned from the extension
(server-side) which is provider specific. Though we didn't plan to
prioritize that.
> That seems to be restating the goal of OAuth and OIDC. Can you explain
> how the incompatible change allows you to accomplish this better than
> standard implementations?
Do you refer to passing grant_type to the server? Which we will get
rid of in the next iteration. Or other incompatible changes as well?
> Why? I claim that standard OAUTHBEARER can handle all of that. What
> does your proposed architecture (the third diagram) enable that my
> proposed hook (the second diagram) doesn't?
The hook proposed on the 2nd diagram effectively delegates all Oauth
flows implementations to the client.
We propose libpq takes care of pulling OpenId discovery and coordination.
Which is effectively Diagram 1 + more flows + server hook providing
root url/audience.
Created the diagrams with all components for 3 flows:
1. Authorization code grant (Clients with Browser access):
+----------------------+ +----------+
| +-------+ |
|
| PQconnect | | |
|
| [auth_code] | | |
+-----------+
| -> | | -------------- Empty Token ------------> | >
| |
| | libpq | <----- Error(w\ Root URL + Audience ) -- | <
| Pre-Auth |
| | | |
| Hook |
| | | |
+-----------+
| | | +--------------+ | |
| | | -------[GET]---------> | OIDC | | Postgres |
| +------+ | <--Provider Metadata-- | Discovery | | |
| +- < | Hook | < | +--------------+ |
|
| | +------+ | |
|
| v | | |
|
| [get auth | | |
|
| code] | | |
|
|<user action>| | |
|
| | | | |
|
| + | | |
|
| PQconnect > | +--------+ +--------------+ |
|
| | | iddawc | <-- [ Auth code ]-> | Issuer/ | | |
| | | | <-- Access Token -- | Authz Server | | |
| | +--------+ +--------------+ | |
| | | |
+-----------+
| | | -------------- Access Token -----------> | >
| Validator |
| | | <---- Authorization Success/Failure ---- | <
| Hook |
| +------+ | |
+-----------+
| +-< | Hook | | |
|
| v +------+ | |
|
|[store +-------+ |
|
| refresh_token] +----------+
+----------------------+
2. Device code grant
+----------------------+ +----------+
| +-------+ |
|
| PQconnect | | |
|
| [auth_code] | | |
+-----------+
| -> | | -------------- Empty Token ------------> | >
| |
| | libpq | <----- Error(w\ Root URL + Audience ) -- | <
| Pre-Auth |
| | | |
| Hook |
| | | |
+-----------+
| | | +--------------+ | |
| | | -------[GET]---------> | OIDC | | Postgres |
| +------+ | <--Provider Metadata-- | Discovery | | |
| +- < | Hook | < | +--------------+ |
|
| | +------+ | |
|
| v | | |
|
| [device | +---------+ +--------------+ |
|
| code] | | iddawc | | Issuer/ | |
|
|<user action>| | | --[ Device code ]-> | Authz Server | |
|
| | |<polling>| --[ Device code ]-> | | |
|
| | | | --[ Device code ]-> | | |
|
| | | | | | | |
| | | | <-- Access Token -- | | | |
| | +---------+ +--------------+ | |
| | | |
+-----------+
| | | -------------- Access Token -----------> | >
| Validator |
| | | <---- Authorization Success/Failure ---- | <
| Hook |
| +------+ | |
+-----------+
| +-< | Hook | | |
|
| v +------+ | |
|
|[store +-------+ |
|
| refresh_token] +----------+
+----------------------+
3. Non-interactive flows (Client Secret / Refresh_Token)
+----------------------+ +----------+
| +-------+ |
|
| PQconnect | | |
|
| [grant_type]| | | |
| -> | | |
+-----------+
| | | -------------- Empty Token ------------> | >
| |
| | libpq | <----- Error(w\ Root URL + Audience ) -- | <
| Pre-Auth |
| | | |
| Hook |
| | | |
+-----------+
| | | +--------------+ | |
| | | -------[GET]---------> | OIDC | | Postgres |
| | | <--Provider Metadata-- | Discovery | | |
| | | +--------------+ |
|
| | | |
|
| | +--------+ +--------------+ |
|
| | | iddawc | <-- [ Secret ]----> | Issuer/ | | |
| | | | <-- Access Token -- | Authz Server | | |
| | +--------+ +--------------+ | |
| | | |
+-----------+
| | | -------------- Access Token -----------> | >
| Validator |
| | | <---- Authorization Success/Failure ---- | <
| Hook |
| | | |
+-----------+
| +-------+ +----------+
+----------------------+
I think what was the most confusing in our latest patch is that
flow_type was passed to the server.
We are not proposing this going forward.
> (For a future conversation: they need to set up authorization, too,
> with custom scopes or some other magic. It's not enough to check who
> the token belongs to; even if Postgres is just using the verified
> email from OpenID as an authenticator, you have to also know that the
> user authorized the token -- and therefore the client -- to access
> Postgres on their behalf.)
My understanding is that metadata in the tokens is provider specific,
so server side hook would be the right place to handle that.
Plus I can envision for some providers it can make sense to make a
remote call to pull some information.
The way we implement Azure AD auth today in PAAS PostgreSQL offering:
- Server administrator uses special extension functions to create
Azure AD enabled PostgreSQL roles.
- PostgreSQL extension maps Roles to unique identity Ids (UID) in the Directory.
- Connection flow: If the token is valid and Role => UID mapping
matches, we authenticate as the Role.
- Then its native PostgreSQL role based access control takes care of privileges.
This is the same for both User- and System-to-system authorization.
Though I assume different providers may treat user- and system-
identities differently. So their extension would handle that.
Thanks!
Andrey.
On Wed, Dec 7, 2022 at 11:06 AM Jacob Champion <[email protected]> wrote:
>
> On Mon, Dec 5, 2022 at 4:15 PM Andrey Chudnovsky <[email protected]> wrote:
> > I think we can focus on the roles and responsibilities of the components first.
> > Details of the patch can be elaborated. Like "flow type code" is a
> > mistake on our side, and we will use the term "grant_type" which is
> > defined by OIDC spec. As well as details of usage of refresh_token.
>
> (For the record, whether we call it "flow type" or "grant type"
> doesn't address my concern.)
>
> > Basically Yes. We propose an increase of the server side hook responsibility.
> > From just validating the token, to also return the provider root URL
> > and required audience. And possibly provide more metadata in the
> > future.
>
> I think it's okay to have the extension and HBA collaborate to provide
> discovery information. Your proposal goes further than that, though,
> and makes the server aware of the chosen client flow. That appears to
> be an architectural violation: why does an OAuth resource server need
> to know the client flow at all?
>
> > Which is in our opinion aligned with SASL protocol, where the server
> > side is responsible for telling the client auth requirements based on
> > the requested role in the startup packet.
>
> You've proposed an alternative SASL mechanism. There's nothing wrong
> with that, per se, but I think it should be clear why we've chosen
> something nonstandard.
>
> > Our understanding is that in the original patch that information came
> > purely from hba, and we propose extension being able to control that
> > metadata.
> > As we see extension as being owned by the identity provider, compared
> > to HBA which is owned by the server administrator or cloud provider.
>
> That seems reasonable, considering how tightly coupled the Issuer and
> the token validation process are.
>
> > 2. Server Owners / PAAS providers (On premise admins, Cloud providers,
> > multi-cloud PAAS providers).
> > - Install extensions and configure HBA to allow clients to
> > authenticate with the identity providers of their choice.
>
> (For a future conversation: they need to set up authorization, too,
> with custom scopes or some other magic. It's not enough to check who
> the token belongs to; even if Postgres is just using the verified
> email from OpenID as an authenticator, you have to also know that the
> user authorized the token -- and therefore the client -- to access
> Postgres on their behalf.)
>
> > 3. Client Application Developers (Data Wis, integration tools,
> > PgAdmin, monitoring tools, e.t.c.)
> > - Independent from specific Identity providers or server providers.
> > Write one code for all identity providers.
>
> Ideally, yes, but that only works if all identity providers implement
> the same flows in compatible ways. We're already seeing instances
> where that's not the case and we'll necessarily have to deal with that
> up front.
>
> > - Rely on application deployment owners to configure which OIDC
> > provider to use across client and server setups.
> > 4. Application Deployment Owners (End customers setting up applications)
> > - The only actor actually aware of which identity provider to use.
> > Configures the stack based on the Identity and PostgreSQL deployments
> > they have.
>
> (I have doubts that the roles will be as decoupled in practice as you
> have described them, but I'd rather defer that for now.)
>
> > The critical piece of the vision is (3.) above is applications
> > agnostic of the identity providers. Those applications rely on
> > properly configured servers and rich driver logic (libpq,
> > com.postgresql, npgsql) to allow their application to popup auth
> > windows or do service-to-service authentication with any provider. In
> > our view that would significantly democratize the deployment of OAUTH
> > authentication in the community.
>
> That seems to be restating the goal of OAuth and OIDC. Can you explain
> how the incompatible change allows you to accomplish this better than
> standard implementations?
>
> > In order to allow this separation, we propose:
> > 1. HBA + Extension is the single source of truth of Provider root URL
> > + Required Audience for each role. If some backfill for missing OIDC
> > discovery is needed, the provider-specific extension would be
> > providing it.
> > 2. Client Application knows which grant_type to use in which scenario.
> > But can be coded without knowledge of a specific provider. So can't
> > provide discovery details.
> > 3. Driver (libpq, others) - coordinate the authentication flow based
> > on client grant_type and identity provider metadata to allow client
> > applications to use any flow with any provider in a unified way.
> >
> > Yes, this would require a little more complicated flow between
> > components than in your original patch.
>
> Why? I claim that standard OAUTHBEARER can handle all of that. What
> does your proposed architecture (the third diagram) enable that my
> proposed hook (the second diagram) doesn't?
>
> > And yes, more complexity comes
> > with more opportunity to make bugs.
> > However, I see PG Server and Libpq as the places which can have more
> > complexity. For the purpose of making work for the community
> > participants easier and simplify adoption.
> >
> > Does this make sense to you?
>
> Some of it, but it hasn't really addressed the questions from my last mail.
>
> Thanks,
> --Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-27 01:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-27 21:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-30 14:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-30 20:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-10-03 18:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-23 09:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-23 20:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-11-24 03:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-24 08:20 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-29 21:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-12-06 00:15 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-12-07 19:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-12-07 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
@ 2022-12-08 04:25 ` Andrey Chudnovsky <[email protected]>
2022-12-09 00:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Andrey Chudnovsky @ 2022-12-08 04:25 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: mahendrakar s <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
That being said, the Diagram 2 would look like this with our proposal:
+----------------------+ +----------+
| +-------+ | Postgres |
| PQconnect ->| | |
|
| | | |
+-----------+
| | | -------------- Empty Token ------------> | >
| |
| | libpq | <----- Error(w\ Root URL + Audience ) -- | <
| Pre-Auth |
| +------+ | |
| Hook |
| +- < | Hook | | |
+-----------+
| | +------+ | | |
| v | | |
|
| [get token]| | |
|
| | | | |
|
| + | | |
+-----------+
| PQconnect > | | -------------- Access Token -----------> | >
| Validator |
| | | <---- Authorization Success/Failure ---- | <
| Hook |
| | | |
+-----------+
| +-------+ |
| +----------------------+
+----------+
With the application taking care of all Token acquisition logic. While
the server-side hook is participating in the pre-authentication reply.
That is definitely a required scenario for the long term and the
easiest to implement in the client core.
And if we can do at least that flow in PG16 it will be a strong
foundation to provide more support for specific grants in libpq going
forward.
Does the diagram above look good to you? We can then start cleaning up
the patch to get that in first.
Thanks!
Andrey.
On Wed, Dec 7, 2022 at 3:22 PM Andrey Chudnovsky <[email protected]> wrote:
>
> > I think it's okay to have the extension and HBA collaborate to provide
> > discovery information. Your proposal goes further than that, though,
> > and makes the server aware of the chosen client flow. That appears to
> > be an architectural violation: why does an OAuth resource server need
> > to know the client flow at all?
>
> Ok. It may have left there from intermediate iterations. We did
> consider making extension drive the flow for specific grant_type, but
> decided against that idea. For the same reason you point to.
> Is it correct that your main concern about use of grant_type was that
> it's propagated to the server? Then yes, we will remove sending it to
> the server.
>
> > Ideally, yes, but that only works if all identity providers implement
> > the same flows in compatible ways. We're already seeing instances
> > where that's not the case and we'll necessarily have to deal with that
> > up front.
>
> Yes, based on our analysis OIDC spec is detailed enough, that
> providers implementing that one, can be supported with generic code in
> libpq / client.
> Github specifically won't fit there though. Microsoft Azure AD,
> Google, Okta (including Auth0) will.
> Theoretically discovery documents can be returned from the extension
> (server-side) which is provider specific. Though we didn't plan to
> prioritize that.
>
> > That seems to be restating the goal of OAuth and OIDC. Can you explain
> > how the incompatible change allows you to accomplish this better than
> > standard implementations?
>
> Do you refer to passing grant_type to the server? Which we will get
> rid of in the next iteration. Or other incompatible changes as well?
>
> > Why? I claim that standard OAUTHBEARER can handle all of that. What
> > does your proposed architecture (the third diagram) enable that my
> > proposed hook (the second diagram) doesn't?
>
> The hook proposed on the 2nd diagram effectively delegates all Oauth
> flows implementations to the client.
> We propose libpq takes care of pulling OpenId discovery and coordination.
> Which is effectively Diagram 1 + more flows + server hook providing
> root url/audience.
>
> Created the diagrams with all components for 3 flows:
> 1. Authorization code grant (Clients with Browser access):
> +----------------------+ +----------+
> | +-------+ |
> |
> | PQconnect | | |
> |
> | [auth_code] | | |
> +-----------+
> | -> | | -------------- Empty Token ------------> | >
> | |
> | | libpq | <----- Error(w\ Root URL + Audience ) -- | <
> | Pre-Auth |
> | | | |
> | Hook |
> | | | |
> +-----------+
> | | | +--------------+ | |
> | | | -------[GET]---------> | OIDC | | Postgres |
> | +------+ | <--Provider Metadata-- | Discovery | | |
> | +- < | Hook | < | +--------------+ |
> |
> | | +------+ | |
> |
> | v | | |
> |
> | [get auth | | |
> |
> | code] | | |
> |
> |<user action>| | |
> |
> | | | | |
> |
> | + | | |
> |
> | PQconnect > | +--------+ +--------------+ |
> |
> | | | iddawc | <-- [ Auth code ]-> | Issuer/ | | |
> | | | | <-- Access Token -- | Authz Server | | |
> | | +--------+ +--------------+ | |
> | | | |
> +-----------+
> | | | -------------- Access Token -----------> | >
> | Validator |
> | | | <---- Authorization Success/Failure ---- | <
> | Hook |
> | +------+ | |
> +-----------+
> | +-< | Hook | | |
> |
> | v +------+ | |
> |
> |[store +-------+ |
> |
> | refresh_token] +----------+
> +----------------------+
>
> 2. Device code grant
> +----------------------+ +----------+
> | +-------+ |
> |
> | PQconnect | | |
> |
> | [auth_code] | | |
> +-----------+
> | -> | | -------------- Empty Token ------------> | >
> | |
> | | libpq | <----- Error(w\ Root URL + Audience ) -- | <
> | Pre-Auth |
> | | | |
> | Hook |
> | | | |
> +-----------+
> | | | +--------------+ | |
> | | | -------[GET]---------> | OIDC | | Postgres |
> | +------+ | <--Provider Metadata-- | Discovery | | |
> | +- < | Hook | < | +--------------+ |
> |
> | | +------+ | |
> |
> | v | | |
> |
> | [device | +---------+ +--------------+ |
> |
> | code] | | iddawc | | Issuer/ | |
> |
> |<user action>| | | --[ Device code ]-> | Authz Server | |
> |
> | | |<polling>| --[ Device code ]-> | | |
> |
> | | | | --[ Device code ]-> | | |
> |
> | | | | | | | |
> | | | | <-- Access Token -- | | | |
> | | +---------+ +--------------+ | |
> | | | |
> +-----------+
> | | | -------------- Access Token -----------> | >
> | Validator |
> | | | <---- Authorization Success/Failure ---- | <
> | Hook |
> | +------+ | |
> +-----------+
> | +-< | Hook | | |
> |
> | v +------+ | |
> |
> |[store +-------+ |
> |
> | refresh_token] +----------+
> +----------------------+
>
> 3. Non-interactive flows (Client Secret / Refresh_Token)
> +----------------------+ +----------+
> | +-------+ |
> |
> | PQconnect | | |
> |
> | [grant_type]| | | |
> | -> | | |
> +-----------+
> | | | -------------- Empty Token ------------> | >
> | |
> | | libpq | <----- Error(w\ Root URL + Audience ) -- | <
> | Pre-Auth |
> | | | |
> | Hook |
> | | | |
> +-----------+
> | | | +--------------+ | |
> | | | -------[GET]---------> | OIDC | | Postgres |
> | | | <--Provider Metadata-- | Discovery | | |
> | | | +--------------+ |
> |
> | | | |
> |
> | | +--------+ +--------------+ |
> |
> | | | iddawc | <-- [ Secret ]----> | Issuer/ | | |
> | | | | <-- Access Token -- | Authz Server | | |
> | | +--------+ +--------------+ | |
> | | | |
> +-----------+
> | | | -------------- Access Token -----------> | >
> | Validator |
> | | | <---- Authorization Success/Failure ---- | <
> | Hook |
> | | | |
> +-----------+
> | +-------+ +----------+
> +----------------------+
>
> I think what was the most confusing in our latest patch is that
> flow_type was passed to the server.
> We are not proposing this going forward.
>
> > (For a future conversation: they need to set up authorization, too,
> > with custom scopes or some other magic. It's not enough to check who
> > the token belongs to; even if Postgres is just using the verified
> > email from OpenID as an authenticator, you have to also know that the
> > user authorized the token -- and therefore the client -- to access
> > Postgres on their behalf.)
>
> My understanding is that metadata in the tokens is provider specific,
> so server side hook would be the right place to handle that.
> Plus I can envision for some providers it can make sense to make a
> remote call to pull some information.
>
> The way we implement Azure AD auth today in PAAS PostgreSQL offering:
> - Server administrator uses special extension functions to create
> Azure AD enabled PostgreSQL roles.
> - PostgreSQL extension maps Roles to unique identity Ids (UID) in the Directory.
> - Connection flow: If the token is valid and Role => UID mapping
> matches, we authenticate as the Role.
> - Then its native PostgreSQL role based access control takes care of privileges.
>
> This is the same for both User- and System-to-system authorization.
> Though I assume different providers may treat user- and system-
> identities differently. So their extension would handle that.
>
> Thanks!
> Andrey.
>
> On Wed, Dec 7, 2022 at 11:06 AM Jacob Champion <[email protected]> wrote:
> >
> > On Mon, Dec 5, 2022 at 4:15 PM Andrey Chudnovsky <[email protected]> wrote:
> > > I think we can focus on the roles and responsibilities of the components first.
> > > Details of the patch can be elaborated. Like "flow type code" is a
> > > mistake on our side, and we will use the term "grant_type" which is
> > > defined by OIDC spec. As well as details of usage of refresh_token.
> >
> > (For the record, whether we call it "flow type" or "grant type"
> > doesn't address my concern.)
> >
> > > Basically Yes. We propose an increase of the server side hook responsibility.
> > > From just validating the token, to also return the provider root URL
> > > and required audience. And possibly provide more metadata in the
> > > future.
> >
> > I think it's okay to have the extension and HBA collaborate to provide
> > discovery information. Your proposal goes further than that, though,
> > and makes the server aware of the chosen client flow. That appears to
> > be an architectural violation: why does an OAuth resource server need
> > to know the client flow at all?
> >
> > > Which is in our opinion aligned with SASL protocol, where the server
> > > side is responsible for telling the client auth requirements based on
> > > the requested role in the startup packet.
> >
> > You've proposed an alternative SASL mechanism. There's nothing wrong
> > with that, per se, but I think it should be clear why we've chosen
> > something nonstandard.
> >
> > > Our understanding is that in the original patch that information came
> > > purely from hba, and we propose extension being able to control that
> > > metadata.
> > > As we see extension as being owned by the identity provider, compared
> > > to HBA which is owned by the server administrator or cloud provider.
> >
> > That seems reasonable, considering how tightly coupled the Issuer and
> > the token validation process are.
> >
> > > 2. Server Owners / PAAS providers (On premise admins, Cloud providers,
> > > multi-cloud PAAS providers).
> > > - Install extensions and configure HBA to allow clients to
> > > authenticate with the identity providers of their choice.
> >
> > (For a future conversation: they need to set up authorization, too,
> > with custom scopes or some other magic. It's not enough to check who
> > the token belongs to; even if Postgres is just using the verified
> > email from OpenID as an authenticator, you have to also know that the
> > user authorized the token -- and therefore the client -- to access
> > Postgres on their behalf.)
> >
> > > 3. Client Application Developers (Data Wis, integration tools,
> > > PgAdmin, monitoring tools, e.t.c.)
> > > - Independent from specific Identity providers or server providers.
> > > Write one code for all identity providers.
> >
> > Ideally, yes, but that only works if all identity providers implement
> > the same flows in compatible ways. We're already seeing instances
> > where that's not the case and we'll necessarily have to deal with that
> > up front.
> >
> > > - Rely on application deployment owners to configure which OIDC
> > > provider to use across client and server setups.
> > > 4. Application Deployment Owners (End customers setting up applications)
> > > - The only actor actually aware of which identity provider to use.
> > > Configures the stack based on the Identity and PostgreSQL deployments
> > > they have.
> >
> > (I have doubts that the roles will be as decoupled in practice as you
> > have described them, but I'd rather defer that for now.)
> >
> > > The critical piece of the vision is (3.) above is applications
> > > agnostic of the identity providers. Those applications rely on
> > > properly configured servers and rich driver logic (libpq,
> > > com.postgresql, npgsql) to allow their application to popup auth
> > > windows or do service-to-service authentication with any provider. In
> > > our view that would significantly democratize the deployment of OAUTH
> > > authentication in the community.
> >
> > That seems to be restating the goal of OAuth and OIDC. Can you explain
> > how the incompatible change allows you to accomplish this better than
> > standard implementations?
> >
> > > In order to allow this separation, we propose:
> > > 1. HBA + Extension is the single source of truth of Provider root URL
> > > + Required Audience for each role. If some backfill for missing OIDC
> > > discovery is needed, the provider-specific extension would be
> > > providing it.
> > > 2. Client Application knows which grant_type to use in which scenario.
> > > But can be coded without knowledge of a specific provider. So can't
> > > provide discovery details.
> > > 3. Driver (libpq, others) - coordinate the authentication flow based
> > > on client grant_type and identity provider metadata to allow client
> > > applications to use any flow with any provider in a unified way.
> > >
> > > Yes, this would require a little more complicated flow between
> > > components than in your original patch.
> >
> > Why? I claim that standard OAUTHBEARER can handle all of that. What
> > does your proposed architecture (the third diagram) enable that my
> > proposed hook (the second diagram) doesn't?
> >
> > > And yes, more complexity comes
> > > with more opportunity to make bugs.
> > > However, I see PG Server and Libpq as the places which can have more
> > > complexity. For the purpose of making work for the community
> > > participants easier and simplify adoption.
> > >
> > > Does this make sense to you?
> >
> > Some of it, but it hasn't really addressed the questions from my last mail.
> >
> > Thanks,
> > --Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-27 01:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-27 21:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-30 14:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-30 20:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-10-03 18:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-23 09:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-23 20:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-11-24 03:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-24 08:20 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-29 21:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-12-06 00:15 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-12-07 19:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-12-07 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-12-08 04:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
@ 2022-12-09 00:41 ` Jacob Champion <[email protected]>
2022-12-13 05:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2022-12-09 00:41 UTC (permalink / raw)
To: Andrey Chudnovsky <[email protected]>; +Cc: mahendrakar s <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
On Wed, Dec 7, 2022 at 3:22 PM Andrey Chudnovsky
<[email protected]> wrote:
>
>> I think it's okay to have the extension and HBA collaborate to
>> provide discovery information. Your proposal goes further than
>> that, though, and makes the server aware of the chosen client flow.
>> That appears to be an architectural violation: why does an OAuth
>> resource server need to know the client flow at all?
>
> Ok. It may have left there from intermediate iterations. We did
> consider making extension drive the flow for specific grant_type,
> but decided against that idea. For the same reason you point to. Is
> it correct that your main concern about use of grant_type was that
> it's propagated to the server? Then yes, we will remove sending it
> to the server.
Okay. Yes, that was my primary concern.
>> Ideally, yes, but that only works if all identity providers
>> implement the same flows in compatible ways. We're already seeing
>> instances where that's not the case and we'll necessarily have to
>> deal with that up front.
>
> Yes, based on our analysis OIDC spec is detailed enough, that
> providers implementing that one, can be supported with generic code
> in libpq / client. Github specifically won't fit there though.
> Microsoft Azure AD, Google, Okta (including Auth0) will.
> Theoretically discovery documents can be returned from the extension
> (server-side) which is provider specific. Though we didn't plan to
> prioritize that.
As another example, Google's device authorization grant is incompatible
with the spec (which they co-authored). I want to say I had problems
with Azure AD not following that spec either, but I don't remember
exactly what they were. I wouldn't be surprised to find more tiny
departures once we get deeper into implementation.
>> That seems to be restating the goal of OAuth and OIDC. Can you
>> explain how the incompatible change allows you to accomplish this
>> better than standard implementations?
>
> Do you refer to passing grant_type to the server? Which we will get
> rid of in the next iteration. Or other incompatible changes as well?
Just the grant type, yeah.
>> Why? I claim that standard OAUTHBEARER can handle all of that.
>> What does your proposed architecture (the third diagram) enable
>> that my proposed hook (the second diagram) doesn't?
>
> The hook proposed on the 2nd diagram effectively delegates all Oauth
> flows implementations to the client. We propose libpq takes care of
> pulling OpenId discovery and coordination. Which is effectively
> Diagram 1 + more flows + server hook providing root url/audience.
>
> Created the diagrams with all components for 3 flows: [snip]
(I'll skip ahead to your later mail on this.)
>> (For a future conversation: they need to set up authorization,
>> too, with custom scopes or some other magic. It's not enough to
>> check who the token belongs to; even if Postgres is just using the
>> verified email from OpenID as an authenticator, you have to also
>> know that the user authorized the token -- and therefore the client
>> -- to access Postgres on their behalf.)
>
> My understanding is that metadata in the tokens is provider
> specific, so server side hook would be the right place to handle
> that. Plus I can envision for some providers it can make sense to
> make a remote call to pull some information.
The server hook is the right place to check the scopes, yes, but I think
the DBA should be able to specify what those scopes are to begin with.
The provider of the extension shouldn't be expected by the architecture
to hardcode those decisions, even if Azure AD chooses to short-circuit
that choice and provide magic instead.
On 12/7/22 20:25, Andrey Chudnovsky wrote:
> That being said, the Diagram 2 would look like this with our proposal:
> [snip]
>
> With the application taking care of all Token acquisition logic. While
> the server-side hook is participating in the pre-authentication reply.
>
> That is definitely a required scenario for the long term and the
> easiest to implement in the client core.> And if we can do at least that flow in PG16 it will be a strong
> foundation to provide more support for specific grants in libpq going
> forward.
Agreed.
> Does the diagram above look good to you? We can then start cleaning up
> the patch to get that in first.
I maintain that the hook doesn't need to hand back artifacts to the
client for a second PQconnect call. It can just use those artifacts to
obtain the access token and hand that right back to libpq. (I think any
requirement that clients be rewritten to call PQconnect twice will
probably be a sticking point for adoption of an OAuth patch.)
That said, now that your proposal is also compatible with OAUTHBEARER, I
can pony up some code to hopefully prove my point. (I don't know if I'll
be able to do that by the holidays though.)
Thanks!
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-27 01:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-27 21:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-30 14:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-30 20:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-10-03 18:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-23 09:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-23 20:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-11-24 03:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-24 08:20 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-29 21:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-12-06 00:15 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-12-07 19:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-12-07 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-12-08 04:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-12-09 00:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2022-12-13 05:06 ` Andrey Chudnovsky <[email protected]>
2022-12-16 23:18 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Andrey Chudnovsky @ 2022-12-13 05:06 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: mahendrakar s <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
> The server hook is the right place to check the scopes, yes, but I think
> the DBA should be able to specify what those scopes are to begin with.
> The provider of the extension shouldn't be expected by the architecture
> to hardcode those decisions, even if Azure AD chooses to short-circuit
> that choice and provide magic instead.
Hardcode is definitely not expected, but customization for identity
provider specific, I think, should be allowed.
I can provide a couple of advanced use cases which happen in the cloud
deployments world, and require per-role management:
- Multi-tenant deployments, when root provider URL would be different
for different roles, based on which tenant they come from.
- Federation to multiple providers. Solutions like Amazon Cognito
which offer a layer of abstraction with several providers
transparently supported.
If your concern is extension not honoring the DBA configured values:
Would a server-side logic to prefer HBA value over extension-provided
resolve this concern?
We are definitely biased towards the cloud deployment scenarios, where
direct access to .hba files is usually not offered at all.
Let's find the middle ground here.
A separate reason for creating this pre-authentication hook is further
extensibility to support more metadata.
Specifically when we add support for OAUTH flows to libpq, server-side
extensions can help bridge the gap between the identity provider
implementation and OAUTH/OIDC specs.
For example, that could allow the Github extension to provide an OIDC
discovery document.
I definitely see identity providers as institutional actors here which
can be given some power through the extension hooks to customize the
behavior within the framework.
> I maintain that the hook doesn't need to hand back artifacts to the
> client for a second PQconnect call. It can just use those artifacts to
> obtain the access token and hand that right back to libpq. (I think any
> requirement that clients be rewritten to call PQconnect twice will
> probably be a sticking point for adoption of an OAuth patch.)
Obtaining a token is an asynchronous process with a human in the loop.
Not sure if expecting a hook function to return a token synchronously
is the best option here.
Can that be an optional return value of the hook in cases when a token
can be obtained synchronously?
On Thu, Dec 8, 2022 at 4:41 PM Jacob Champion <[email protected]> wrote:
>
> On Wed, Dec 7, 2022 at 3:22 PM Andrey Chudnovsky
> <[email protected]> wrote:
> >
> >> I think it's okay to have the extension and HBA collaborate to
> >> provide discovery information. Your proposal goes further than
> >> that, though, and makes the server aware of the chosen client flow.
> >> That appears to be an architectural violation: why does an OAuth
> >> resource server need to know the client flow at all?
> >
> > Ok. It may have left there from intermediate iterations. We did
> > consider making extension drive the flow for specific grant_type,
> > but decided against that idea. For the same reason you point to. Is
> > it correct that your main concern about use of grant_type was that
> > it's propagated to the server? Then yes, we will remove sending it
> > to the server.
>
> Okay. Yes, that was my primary concern.
>
> >> Ideally, yes, but that only works if all identity providers
> >> implement the same flows in compatible ways. We're already seeing
> >> instances where that's not the case and we'll necessarily have to
> >> deal with that up front.
> >
> > Yes, based on our analysis OIDC spec is detailed enough, that
> > providers implementing that one, can be supported with generic code
> > in libpq / client. Github specifically won't fit there though.
> > Microsoft Azure AD, Google, Okta (including Auth0) will.
> > Theoretically discovery documents can be returned from the extension
> > (server-side) which is provider specific. Though we didn't plan to
> > prioritize that.
>
> As another example, Google's device authorization grant is incompatible
> with the spec (which they co-authored). I want to say I had problems
> with Azure AD not following that spec either, but I don't remember
> exactly what they were. I wouldn't be surprised to find more tiny
> departures once we get deeper into implementation.
>
> >> That seems to be restating the goal of OAuth and OIDC. Can you
> >> explain how the incompatible change allows you to accomplish this
> >> better than standard implementations?
> >
> > Do you refer to passing grant_type to the server? Which we will get
> > rid of in the next iteration. Or other incompatible changes as well?
>
> Just the grant type, yeah.
>
> >> Why? I claim that standard OAUTHBEARER can handle all of that.
> >> What does your proposed architecture (the third diagram) enable
> >> that my proposed hook (the second diagram) doesn't?
> >
> > The hook proposed on the 2nd diagram effectively delegates all Oauth
> > flows implementations to the client. We propose libpq takes care of
> > pulling OpenId discovery and coordination. Which is effectively
> > Diagram 1 + more flows + server hook providing root url/audience.
> >
> > Created the diagrams with all components for 3 flows: [snip]
>
> (I'll skip ahead to your later mail on this.)
>
> >> (For a future conversation: they need to set up authorization,
> >> too, with custom scopes or some other magic. It's not enough to
> >> check who the token belongs to; even if Postgres is just using the
> >> verified email from OpenID as an authenticator, you have to also
> >> know that the user authorized the token -- and therefore the client
> >> -- to access Postgres on their behalf.)
> >
> > My understanding is that metadata in the tokens is provider
> > specific, so server side hook would be the right place to handle
> > that. Plus I can envision for some providers it can make sense to
> > make a remote call to pull some information.
>
> The server hook is the right place to check the scopes, yes, but I think
> the DBA should be able to specify what those scopes are to begin with.
> The provider of the extension shouldn't be expected by the architecture
> to hardcode those decisions, even if Azure AD chooses to short-circuit
> that choice and provide magic instead.
>
> On 12/7/22 20:25, Andrey Chudnovsky wrote:
> > That being said, the Diagram 2 would look like this with our proposal:
> > [snip]
> >
> > With the application taking care of all Token acquisition logic. While
> > the server-side hook is participating in the pre-authentication reply.
> >
> > That is definitely a required scenario for the long term and the
> > easiest to implement in the client core.> And if we can do at least that flow in PG16 it will be a strong
> > foundation to provide more support for specific grants in libpq going
> > forward.
>
> Agreed.
> > Does the diagram above look good to you? We can then start cleaning up
> > the patch to get that in first.
>
> I maintain that the hook doesn't need to hand back artifacts to the
> client for a second PQconnect call. It can just use those artifacts to
> obtain the access token and hand that right back to libpq. (I think any
> requirement that clients be rewritten to call PQconnect twice will
> probably be a sticking point for adoption of an OAuth patch.)
>
> That said, now that your proposal is also compatible with OAUTHBEARER, I
> can pony up some code to hopefully prove my point. (I don't know if I'll
> be able to do that by the holidays though.)
>
> Thanks!
> --Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-27 01:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-27 21:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-30 14:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-30 20:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-10-03 18:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-23 09:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-23 20:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-11-24 03:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-24 08:20 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-29 21:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-12-06 00:15 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-12-07 19:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-12-07 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-12-08 04:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-12-09 00:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-12-13 05:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
@ 2022-12-16 23:18 ` Jacob Champion <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2022-12-16 23:18 UTC (permalink / raw)
To: Andrey Chudnovsky <[email protected]>; +Cc: mahendrakar s <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
On Mon, Dec 12, 2022 at 9:06 PM Andrey Chudnovsky
<[email protected]> wrote:
> If your concern is extension not honoring the DBA configured values:
> Would a server-side logic to prefer HBA value over extension-provided
> resolve this concern?
Yeah. It also seals the role of the extension here as "optional".
> We are definitely biased towards the cloud deployment scenarios, where
> direct access to .hba files is usually not offered at all.
> Let's find the middle ground here.
Sure. I don't want to make this difficult in cloud scenarios --
obviously I'd like for Timescale Cloud to be able to make use of this
too. But if we make this easy for a lone DBA (who doesn't have any
institutional power with the providers) to use correctly and securely,
then it should follow that the providers who _do_ have power and
resources will have an easy time of it as well. The reverse isn't
necessarily true. So I'm definitely planning to focus on the DBA case
first.
> A separate reason for creating this pre-authentication hook is further
> extensibility to support more metadata.
> Specifically when we add support for OAUTH flows to libpq, server-side
> extensions can help bridge the gap between the identity provider
> implementation and OAUTH/OIDC specs.
> For example, that could allow the Github extension to provide an OIDC
> discovery document.
>
> I definitely see identity providers as institutional actors here which
> can be given some power through the extension hooks to customize the
> behavior within the framework.
We'll probably have to make some compromises in this area, but I think
they should be carefully considered exceptions and not a core feature
of the mechanism. The gaps you point out are just fragmentation, and
adding custom extensions to deal with it leads to further
fragmentation instead of providing pressure on providers to just
implement the specs. Worst case, we open up new exciting security
flaws, and then no one can analyze them independently because no one
other than the provider knows how the two sides work together anymore.
Don't get me wrong; it would be naive to proceed as if the OAUTHBEARER
spec were perfect, because it's clearly not. But if we need to make
extensions to it, we can participate in IETF discussions and make our
case publicly for review, rather than enshrining MS/GitHub/Google/etc.
versions of the RFC and enabling that proliferation as a Postgres core
feature.
> Obtaining a token is an asynchronous process with a human in the loop.
> Not sure if expecting a hook function to return a token synchronously
> is the best option here.
> Can that be an optional return value of the hook in cases when a token
> can be obtained synchronously?
I don't think the hook is generally going to be able to return a token
synchronously, and I expect the final design to be async-first. As far
as I know, this will need to be solved for the builtin flows as well
(you don't want a synchronous HTTP call to block your PQconnectPoll
architecture), so the hook should be able to make use of whatever
solution we land on for that.
This is hand-wavy, and I don't expect it to be easy to solve. I just
don't think we have to solve it twice.
Have a good end to the year!
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-27 01:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-27 21:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-30 14:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-30 20:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-10-03 18:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-23 09:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-23 20:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-11-24 03:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
@ 2022-11-29 21:12 ` Jacob Champion <[email protected]>
1 sibling, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2022-11-29 21:12 UTC (permalink / raw)
To: Andrey Chudnovsky <[email protected]>; +Cc: mahendrakar s <[email protected]>; [email protected] <[email protected]>; [email protected] <[email protected]>; pgsql-hackers; [email protected] <[email protected]>
On 11/23/22 19:45, Andrey Chudnovsky wrote:
> Mostly terminology questions here. OAUTHBEARER SASL appears to be the
> spec about using OAUTH2 tokens for Authentication.
> While any OAUTH2 can generally work, we propose to specifically
> highlight that only OIDC providers can be supported, as we need the
> discovery document.
*If* you're using in-band discovery, yes. But I thought your use case
was explicitly tailored to out-of-band token retrieval:
> The client knows how to get a token for a particular principal
> and doesn't need any additional information other than human readable
> messages.
In that case, isn't OAuth sufficient? There's definitely a need to
document the distinction, but I don't think we have to require OIDC as
long as the client application makes up for the missing information.
(OAUTHBEARER makes the openid-configuration error member optional,
presumably for this reason.)
>> 0) The original hook proposal upthread, I thought, was about allowing
>> libpq's flow implementation to be switched out by the application. I
>> don't see that approach taken here. It's fine if that turned out to be a
>> bad idea, of course, but this patch doesn't seem to match what we were
>> talking about.
> We still plan to allow the client to pass the token. Which is a
> generic way to implement its own OAUTH flows.
Okay. But why push down the implementation into the server?
To illustrate what I mean, here's the architecture of my proposed patchset:
+-------+ +----------+
| | -------------- Empty Token ------------> | |
| libpq | <----- Error Result (w/ Discovery ) ---- | |
| | | |
| +--------+ +--------------+ | |
| | iddawc | <--- [ Flow ] ----> | Issuer/ | | Postgres |
| | | <-- Access Token -- | Authz Server | | |
| +--------+ +--------------+ | +-----------+
| | | | |
| | -------------- Access Token -----------> | > | Validator |
| | <---- Authorization Success/Failure ---- | < | |
| | | +-----------+
+-------+ +----------+
In this implementation, there's only one black box: the validator, which
is responsible for taking an access token from an untrusted client,
verifying that it was issued correctly for the Postgres service, and
either 1) determining whether the bearer is authorized to access the
database, or 2) determining the authenticated ID of the bearer so that
the HBA can decide whether they're authorized. (Or both.)
This approach is limited by the flows that we explicitly enable within
libpq and its OAuth implementation library. You mentioned that you
wanted to support other flows, including clients with out-of-band
knowledge, and I suggested:
> If you wanted to override [iddawc's]
> behavior as a client, you could replace the builtin flow with your
> own, by registering a set of callbacks.
In other words, the hooks would replace iddawc in the above diagram.
In my mind, something like this:
+-------+ +----------+
+------+ | ----------- Empty Token ------------> | Postgres |
| | < | <---------- Error Result ------------ | |
| Hook | | | +-----------+
| | | | | |
+------+ > | ------------ Access Token ----------> | > | Validator |
| | <--- Authorization Success/Failure -- | < | |
| libpq | | +-----------+
+-------+ +----------+
Now there's a second black box -- the client hook -- which takes an
OAUTHBEARER error result (which may or may not have OIDC discovery
information) and returns the access token. How it does this is
unspecified -- it'll probably use some OAuth 2.0 flow, but maybe not.
Maybe it sends the user to a web browser; maybe it uses some of the
magic provider-specific libraries you mentioned upthread. It might have
a refresh token cached so it doesn't have to involve the user at all.
Crucially, though, the two black boxes remain independent of each other.
They have well-defined inputs and outputs (the client hook could be
roughly described as "implement get_auth_token()"). Their correctness
can be independently verified against published OAuth specs and/or
provider documentation. And the client application still makes a single
call to PQconnect*().
Compare this to the architecture proposed by your patch:
Client App
+----------------------+
| +-------+ +----------+
| | libpq | | Postgres |
| PQconnect > | | | +-------+
| +------+ | ------- Flow Type (!) -------> | > | |
| +- < | Hook | < | <------- Error Result -------- | < | |
| [ get +------+ | | | |
| token ] | | | | |
| | | | | | Hooks |
| v | | | | |
| PQconnect > | ----> | ------ Access Token ---------> | > | |
| | | <--- Authz Success/Failure --- | < | |
| +-------+ | +-------+
+----------------------+ +----------+
Rather than decouple things, I think this proposal drives a spike
through the client app, libpq, and the server. Please correct me if I've
misunderstood pieces of the patch, but the following is my view of it:
What used to be a validator hook on the server side now actively
participates in the client-side flow for some reason. (I still don't
understand what the server is supposed to do with that knowledge.
Changing your authz requirements based on the flow the client wants to
use seems like a good way to introduce bugs.)
The client-side hook is now coupled to the application logic: you have
to know to expect an error from the first PQconnect*() call, then check
whatever magic your hook has done for you to be able to set up the
second call to PQconnect*() with the correctly scoped bearer token. So
if you want to switch between the internal libpq OAuth implementation
and your own hook, you have to rewrite your app logic.
On top of all that, the "flow type code" being sent is a custom
extension to OAUTHBEARER that appears to be incompatible with the RFC's
discovery exchange (which is done by sending an empty auth token during
the first round trip).
> We consider the following set of flows to be minimum required:
> - Client Credentials - For Service to Service scenarios.
Okay, that's simple enough that I think it could probably be maintained
inside libpq with minimal cost. At the same time, is it complicated
enough that you need libpq to do it for you?
Maybe once we get the hooks ironed out, it'll be more obvious what the
tradeoff is...
> If you prefer to pick one of the richer flows, Authorization code for
> GUI scenarios is probably much more widely used.
> Plus it's easier to implement too, as interaction goes through a
> series of callbacks. No polling required.
I don't think flows requiring the invocation of web browsers and custom
URL handlers are a clear fit for libpq. For a first draft, at least, I
think that use case should be pushed upward into the client application
via a custom hook.
>> If we want to support refresh tokens, I believe we should be developing
>> a plan to cache and secure them within the client. They should be used
>> as an accelerator for other flows, not as their own flow.
> It's considered a separate "grant_type" in the specs / APIs.
> https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens
Yes, but that doesn't mean we have to expose it to users via a
connection option. You don't get a refresh token out of the blue; you
get it by going through some other flow, and then you use it in
preference to going through that flow again later.
> For the clients, it would be storing the token and using it to authenticate.
> On the question of sensitivity, secure credentials stores are
> different for each platform, with a lot of cloud offerings for this.
> pgAdmin, for example, has its own way to secure credentials to avoid
> asking users for passwords every time the app is opened.
> I believe we should delegate the refresh token management to the clients.
Delegating to client apps would be fine (and implicitly handled by a
token hook, because the client app would receive the refresh token
directly rather than going through libpq). Delegating to end users, not
so much. Printing a refresh token to stderr as proposed here is, I
think, making things unnecessarily difficult (and/or dangerous) for users.
>> 3) I don't like the departure from the OAUTHBEARER mechanism that's
>> presented here. For one, since I can't see a sample plugin that makes
>> use of the "flow type" magic numbers that have been added, I don't
>> really understand why the extension to the mechanism is necessary.
> I don't think it's much of a departure, but rather a separation of
> responsibilities between libpq and upstream clients.
Given the proposed architectures above, 1) I think this is further
coupling the components, not separating them; and 2) I can't agree that
an incompatible discovery mechanism is "not much of a departure". If
OAUTHBEARER's functionality isn't good enough for some reason, let's
talk about why.
> As libpq can be used in different apps, the client would need
> different types of flows/grants.
> I.e. those need to be provided to libpq at connection initialization
> or some other point.
Why do libpq (or the server!) need to know those things at all, if
they're not going to implement the flow?
> We will change to "grant_type" though and use string to be closer to the spec.
> What do you think is the best way for the client to signal which OAUTH
> flow should be used?
libpq should not need to know the grant type in use if the client is
bypassing its internal implementation entirely.
Thanks,
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2022-09-20 05:03 ` mahendrakar s <[email protected]>
2022-09-20 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2 siblings, 1 reply; 194+ messages in thread
From: mahendrakar s @ 2022-09-20 05:03 UTC (permalink / raw)
To: pgsql-hackers; Jacob Champion <[email protected]>; +Cc: [email protected] <[email protected]>; [email protected] <[email protected]>; [email protected]; [email protected]
Hi Hackers,
We are trying to implement AAD(Azure AD) support in PostgreSQL and it
can be achieved with support of the OAuth method. To support AAD on
top of OAuth in a generic fashion (i.e for all other OAuth providers),
we are proposing this patch. It basically exposes two new hooks (one
for error reporting and one for OAuth provider specific token
validation) and passing OAuth bearer token to backend. It also adds
support for client credentials flow of OAuth additional to device code
flow which Jacob has proposed.
The changes for each component are summarized below.
1. Provider-specific extension:
Each OAuth provider implements their own token validator as an
extension. Extension registers an OAuth provider hook which is matched
to a line in the HBA file.
2. Add support to pass on the OAuth bearer token. In this
obtaining the bearer token is left to 3rd party application or user.
./psql -U <username> -d 'dbname=postgres
oauth_client_id=<client_id> oauth_bearer_token=<token>
3. HBA: An additional param ‘provider’ is added for the oauth method.
Defining "oauth" as method + passing provider, issuer endpoint
and expected audience
* * * * oauth provider=<token validation extension>
issuer=.... scope=....
4. Engine Backend:
Support for generic OAUTHBEARER type, requesting client to
provide token and passing to token for provider-specific extension.
5. Engine Frontend: Two-tiered approach.
a) libpq transparently passes on the token received
from 3rd party client as is to the backend.
b) libpq optionally compiled for the clients which
explicitly need libpq to orchestrate OAuth communication with the
issuer (it depends heavily on 3rd party library iddawc as Jacob
already pointed out. The library seems to be supporting all the OAuth
flows.)
Please let us know your thoughts as the proposed method supports
different OAuth flows with the use of provider specific hooks. We
think that the proposal would be useful for various OAuth providers.
Thanks,
Mahendrakar.
On Tue, 20 Sept 2022 at 10:18, Jacob Champion <[email protected]> wrote:
>
> On Tue, 2021-06-22 at 23:22 +0000, Jacob Champion wrote:
> > On Fri, 2021-06-18 at 11:31 +0300, Heikki Linnakangas wrote:
> > >
> > > A few small things caught my eye in the backend oauth_exchange function:
> > >
> > > > + /* Handle the client's initial message. */
> > > > + p = strdup(input);
> > >
> > > this strdup() should be pstrdup().
> >
> > Thanks, I'll fix that in the next re-roll.
> >
> > > In the same function, there are a bunch of reports like this:
> > >
> > > > ereport(ERROR,
> > > > + (errcode(ERRCODE_PROTOCOL_VIOLATION),
> > > > + errmsg("malformed OAUTHBEARER message"),
> > > > + errdetail("Comma expected, but found character \"%s\".",
> > > > + sanitize_char(*p))));
> > >
> > > I don't think the double quotes are needed here, because sanitize_char
> > > will return quotes if it's a single character. So it would end up
> > > looking like this: ... found character "'x'".
> >
> > I'll fix this too. Thanks!
>
> v2, attached, incorporates Heikki's suggested fixes and also rebases on
> top of latest HEAD, which had the SASL refactoring changes committed
> last month.
>
> The biggest change from the last patchset is 0001, an attempt at
> enabling jsonapi in the frontend without the use of palloc(), based on
> suggestions by Michael and Tom from last commitfest. I've also made
> some improvements to the pytest suite. No major changes to the OAuth
> implementation yet.
>
> --Jacob
Attachments:
[application/x-patch] v1-0001-oauth-provider-support.patch (17.5K, ../../CABkiuWrg7eiP=g9pqG9Y3w5SZuj4i=Kmsak6+ex-13xppHaBiA@mail.gmail.com/2-v1-0001-oauth-provider-support.patch)
download | inline diff:
diff --git a/src/backend/libpq/auth-oauth.c b/src/backend/libpq/auth-oauth.c
index c47211132c..86f820482b 100644
--- a/src/backend/libpq/auth-oauth.c
+++ b/src/backend/libpq/auth-oauth.c
@@ -24,7 +24,9 @@
#include "libpq/hba.h"
#include "libpq/oauth.h"
#include "libpq/sasl.h"
+#include "miscadmin.h"
#include "storage/fd.h"
+#include "utils/memutils.h"
/* GUC */
char *oauth_validator_command;
@@ -34,6 +36,13 @@ static void *oauth_init(Port *port, const char *selected_mech, const char *shado
static int oauth_exchange(void *opaq, const char *input, int inputlen,
char **output, int *outputlen, char **logdetail);
+/*----------------------------------------------------------------
+ * OAuth Authentication
+ *----------------------------------------------------------------
+ */
+static List *oauth_providers = NIL;
+static OAuthProvider* oauth_provider = NULL;
+
/* Mechanism declaration */
const pg_be_sasl_mech pg_be_oauth_mech = {
oauth_get_mechanisms,
@@ -63,15 +72,90 @@ static char *sanitize_char(char c);
static char *parse_kvpairs_for_auth(char **input);
static void generate_error_response(struct oauth_ctx *ctx, char **output, int *outputlen);
static bool validate(Port *port, const char *auth, char **logdetail);
-static bool run_validator_command(Port *port, const char *token);
+static const char* run_validator_command(Port *port, const char *token);
static bool check_exit(FILE **fh, const char *command);
static bool unset_cloexec(int fd);
-static bool username_ok_for_shell(const char *username);
#define KVSEP 0x01
#define AUTH_KEY "auth"
#define BEARER_SCHEME "Bearer "
+/*----------------------------------------------------------------
+ * OAuth Token Validator
+ *----------------------------------------------------------------
+ */
+
+/*
+ * RegisterOAuthProvider registers a OAuth Token Validator to be
+ * used for oauth token validation. It validates the token and adds the valiator
+ * name and it's hooks to a list of loaded token validator. The right validator's
+ * hooks can then be called based on the validator name specified in
+ * pg_hba.conf.
+ *
+ * This function should be called in _PG_init() by any extension looking to
+ * add a custom authentication method.
+ */
+void
+RegisterOAuthProvider(
+ const char *provider_name,
+ OAuthProviderCheck_hook_type OAuthProviderCheck_hook,
+ OAuthProviderError_hook_type OAuthProviderError_hook
+)
+{
+ if (!process_shared_preload_libraries_in_progress)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("RegisterOAuthProvider can only be called by a shared_preload_library")));
+ return;
+ }
+
+ MemoryContext oldcxt;
+ if (oauth_provider == NULL)
+ {
+ oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+ oauth_provider = palloc(sizeof(OAuthProvider));
+ oauth_provider->name = pstrdup(provider_name);
+ oauth_provider->oauth_provider_hook = OAuthProviderCheck_hook;
+ oauth_provider->oauth_error_hook = OAuthProviderError_hook;
+ oauth_providers = lappend(oauth_providers, oauth_provider);
+ MemoryContextSwitchTo(oldcxt);
+ }
+ else
+ {
+ if (oauth_provider && oauth_provider->name)
+ {
+ ereport(ERROR,
+ (errmsg("OAuth provider \"%s\" is already loaded.",
+ oauth_provider->name)));
+ }
+ else
+ {
+ ereport(ERROR,
+ (errmsg("OAuth provider is already loaded.")));
+ }
+ }
+}
+
+/*
+ * Returns the oauth provider (which includes it's
+ * callback functions) based on name specified.
+ */
+OAuthProvider *get_provider_by_name(const char *name)
+{
+ ListCell *lc;
+ foreach(lc, oauth_providers)
+ {
+ OAuthProvider *provider = (OAuthProvider *) lfirst(lc);
+ if (strcmp(provider->name, name) == 0)
+ {
+ return provider;
+ }
+ }
+
+ return NULL;
+}
+
static void
oauth_get_mechanisms(Port *port, StringInfo buf)
{
@@ -494,17 +578,17 @@ validate(Port *port, const char *auth, char **logdetail)
}
/* Have the validator check the token. */
- if (!run_validator_command(port, token))
+ if (run_validator_command(port, token) == NULL)
return false;
-
+
if (port->hba->oauth_skip_usermap)
{
/*
- * If the validator is our authorization authority, we're done.
- * Authentication may or may not have been performed depending on the
- * validator implementation; all that matters is that the validator says
- * the user can log in with the target role.
- */
+ * If the validator is our authorization authority, we're done.
+ * Authentication may or may not have been performed depending on the
+ * validator implementation; all that matters is that the validator says
+ * the user can log in with the target role.
+ */
return true;
}
@@ -524,193 +608,26 @@ validate(Port *port, const char *auth, char **logdetail)
return (ret == STATUS_OK);
}
-static bool
+static const char*
run_validator_command(Port *port, const char *token)
{
- bool success = false;
- int rc;
- int pipefd[2];
- int rfd = -1;
- int wfd = -1;
-
- StringInfoData command = { 0 };
- char *p;
- FILE *fh = NULL;
-
- ssize_t written;
- char *line = NULL;
- size_t size = 0;
- ssize_t len;
-
- Assert(oauth_validator_command);
-
- if (!oauth_validator_command[0])
- {
- ereport(COMMERROR,
- (errmsg("oauth_validator_command is not set"),
- errhint("To allow OAuth authenticated connections, set "
- "oauth_validator_command in postgresql.conf.")));
- return false;
- }
-
- /*
- * Since popen() is unidirectional, open up a pipe for the other direction.
- * Use CLOEXEC to ensure that our write end doesn't accidentally get copied
- * into child processes, which would prevent us from closing it cleanly.
- *
- * XXX this is ugly. We should just read from the child process's stdout,
- * but that's a lot more code.
- * XXX by bypassing the popen API, we open the potential of process
- * deadlock. Clearly document child process requirements (i.e. the child
- * MUST read all data off of the pipe before writing anything).
- * TODO: port to Windows using _pipe().
- */
- rc = pipe2(pipefd, O_CLOEXEC);
- if (rc < 0)
+ if(oauth_provider->oauth_provider_hook == NULL)
{
- ereport(COMMERROR,
- (errcode_for_file_access(),
- errmsg("could not create child pipe: %m")));
return false;
}
- rfd = pipefd[0];
- wfd = pipefd[1];
-
- /* Allow the read pipe be passed to the child. */
- if (!unset_cloexec(rfd))
+ char *id = oauth_provider->
+ oauth_provider_hook(port, token);
+ if(id == NULL)
{
- /* error message was already logged */
- goto cleanup;
- }
-
- /*
- * Construct the command, substituting any recognized %-specifiers:
- *
- * %f: the file descriptor of the input pipe
- * %r: the role that the client wants to assume (port->user_name)
- * %%: a literal '%'
- */
- initStringInfo(&command);
-
- for (p = oauth_validator_command; *p; p++)
- {
- if (p[0] == '%')
- {
- switch (p[1])
- {
- case 'f':
- appendStringInfo(&command, "%d", rfd);
- p++;
- break;
- case 'r':
- /*
- * TODO: decide how this string should be escaped. The role
- * is controlled by the client, so if we don't escape it,
- * command injections are inevitable.
- *
- * This is probably an indication that the role name needs
- * to be communicated to the validator process in some other
- * way. For this proof of concept, just be incredibly strict
- * about the characters that are allowed in user names.
- */
- if (!username_ok_for_shell(port->user_name))
- goto cleanup;
-
- appendStringInfoString(&command, port->user_name);
- p++;
- break;
- case '%':
- appendStringInfoChar(&command, '%');
- p++;
- break;
- default:
- appendStringInfoChar(&command, p[0]);
- }
- }
- else
- appendStringInfoChar(&command, p[0]);
- }
-
- /* Execute the command. */
- fh = OpenPipeStream(command.data, "re");
- /* TODO: handle failures */
-
- /* We don't need the read end of the pipe anymore. */
- close(rfd);
- rfd = -1;
-
- /* Give the command the token to validate. */
- written = write(wfd, token, strlen(token));
- if (written != strlen(token))
- {
- /* TODO must loop for short writes, EINTR et al */
- ereport(COMMERROR,
- (errcode_for_file_access(),
- errmsg("could not write token to child pipe: %m")));
- goto cleanup;
- }
-
- close(wfd);
- wfd = -1;
-
- /*
- * Read the command's response.
- *
- * TODO: getline() is probably too new to use, unfortunately.
- * TODO: loop over all lines
- */
- if ((len = getline(&line, &size, fh)) >= 0)
- {
- /* TODO: fail if the authn_id doesn't end with a newline */
- if (len > 0)
- line[len - 1] = '\0';
-
- set_authn_id(port, line);
- }
- else if (ferror(fh))
- {
- ereport(COMMERROR,
- (errcode_for_file_access(),
- errmsg("could not read from command \"%s\": %m",
- command.data)));
- goto cleanup;
- }
-
- /* Make sure the command exits cleanly. */
- if (!check_exit(&fh, command.data))
- {
- /* error message already logged */
- goto cleanup;
- }
-
- /* Done. */
- success = true;
-
-cleanup:
- if (line)
- free(line);
-
- /*
- * In the successful case, the pipe fds are already closed. For the error
- * case, always close out the pipe before waiting for the command, to
- * prevent deadlock.
- */
- if (rfd >= 0)
- close(rfd);
- if (wfd >= 0)
- close(wfd);
-
- if (fh)
- {
- Assert(!success);
- check_exit(&fh, command.data);
+ ereport(LOG,
+ (errmsg("OAuth bearer token validation failed" )));
+ return NULL;
}
- if (command.data)
- pfree(command.data);
-
- return success;
+ set_authn_id(port, id);
+
+ return id;
}
static bool
@@ -769,29 +686,3 @@ unset_cloexec(int fd)
return true;
}
-
-/*
- * XXX This should go away eventually and be replaced with either a proper
- * escape or a different strategy for communication with the validator command.
- */
-static bool
-username_ok_for_shell(const char *username)
-{
- /* This set is borrowed from fe_utils' appendShellStringNoError(). */
- static const char * const allowed = "abcdefghijklmnopqrstuvwxyz"
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "0123456789-_./:";
- size_t span;
-
- Assert(username && username[0]); /* should have already been checked */
-
- span = strspn(username, allowed);
- if (username[span] != '\0')
- {
- ereport(COMMERROR,
- (errmsg("PostgreSQL user name contains unsafe characters and cannot be passed to the OAuth validator")));
- return false;
- }
-
- return true;
-}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 333051ad3c..0bbcf231d2 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -296,8 +296,14 @@ auth_failed(Port *port, int status, const char *logdetail)
errstr = gettext_noop("RADIUS authentication failed for user \"%s\"");
break;
case uaOAuth:
- errstr = gettext_noop("OAuth bearer authentication failed for user \"%s\"");
- break;
+ {
+ OAuthProvider *provider = get_provider_by_name(port->hba->oauth_provider);
+ if(provider->oauth_error_hook)
+ errstr = provider->oauth_error_hook(port);
+ else
+ errstr = gettext_noop("OAuth bearer authentication failed for user \"%s\"");
+ break;
+ }
default:
errstr = gettext_noop("authentication failed for user \"%s\": invalid authentication method");
break;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 943e78ddff..94fb5d434d 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1663,6 +1663,14 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
parsedline->clientcert = clientCertFull;
}
+ /*
+ * Ensure that the token validation provider name is specified as provider for oauth method.
+ */
+ if (parsedline->auth_method == uaOAuth)
+ {
+ MANDATORY_AUTH_ARG(parsedline->oauth_provider, "provider", "oauth");
+ }
+
return parsedline;
}
@@ -2095,6 +2103,31 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
else
hbaline->oauth_skip_usermap = false;
}
+ else if (strcmp(name, "provider") == 0)
+ {
+ REQUIRE_AUTH_OPTION(uaOAuth, "provider", "oauth");
+ if (hbaline->auth_method != uaOAuth)
+ INVALID_AUTH_OPTION("provider", gettext_noop("oauth"));
+ /*
+ * Verify that the token validation mentioned is loaded via shared_preload_libraries.
+ */
+ if (get_provider_by_name(val) == NULL)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("cannot use oauth provider %s",val),
+ errhint("Load provider token validation via shared_preload_libraries."),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, HbaFileName)));
+ *err_msg = psprintf("cannot use oauth provider %s", val);
+
+ return false;
+ }
+ else
+ {
+ hbaline->oauth_provider = pstrdup(val);
+ }
+ }
else
{
ereport(elevel,
diff --git a/src/include/libpq/auth.h b/src/include/libpq/auth.h
index 485e48970e..938ac399dc 100644
--- a/src/include/libpq/auth.h
+++ b/src/include/libpq/auth.h
@@ -44,4 +44,29 @@ extern void set_authn_id(Port *port, const char *id);
typedef void (*ClientAuthentication_hook_type) (Port *, int);
extern PGDLLIMPORT ClientAuthentication_hook_type ClientAuthentication_hook;
+/* Declarations for oAuth authentication providers */
+typedef const char* (*OAuthProviderCheck_hook_type) (Port *, const char*);
+
+/* Hook for plugins to report error messages in validation_failed() */
+typedef const char * (*OAuthProviderError_hook_type) (Port *);
+
+/* Hook for plugins to validate oauth provider options */
+typedef bool (*OAuthProviderValidateOptions_hook_type)
+ (char *, char *, HbaLine *, char **);
+
+typedef struct OAuthProvider
+{
+ const char *name;
+ OAuthProviderCheck_hook_type oauth_provider_hook;
+ OAuthProviderError_hook_type oauth_error_hook;
+} OAuthProvider;
+
+extern void RegisterOAuthProvider
+ (const char *provider_name,
+ OAuthProviderCheck_hook_type OAuthProviderCheck_hook,
+ OAuthProviderError_hook_type OAuthProviderError_hook
+ );
+
+extern OAuthProvider *get_provider_by_name(const char *name);
+
#endif /* AUTH_H */
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index c1b1313989..d65395cc22 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -123,6 +123,7 @@ typedef struct HbaLine
char *radiusports_s;
char *oauth_issuer;
char *oauth_scope;
+ char *oauth_provider;
bool oauth_skip_usermap;
} HbaLine;
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
index 91d2c69f16..61a0b80b7e 100644
--- a/src/interfaces/libpq/fe-auth-oauth.c
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -174,6 +174,16 @@ get_auth_token(PGconn *conn)
if (!token_buf)
goto cleanup;
+ if(conn->oauth_bearer_token)
+ {
+ appendPQExpBufferStr(token_buf, "Bearer ");
+ appendPQExpBufferStr(token_buf, conn->oauth_bearer_token);
+ if (PQExpBufferBroken(token_buf))
+ goto cleanup;
+ token = strdup(token_buf->data);
+ goto cleanup;
+ }
+
err = i_set_str_parameter(&session, I_OPT_OPENID_CONFIG_ENDPOINT, conn->oauth_discovery_uri);
if (err)
{
@@ -201,18 +211,22 @@ get_auth_token(PGconn *conn)
libpq_gettext("issuer does not support device authorization"));
goto cleanup;
}
+
+ //default device flow
+ int session_response_type = I_RESPONSE_TYPE_DEVICE_CODE;
+ auth_method = I_TOKEN_AUTH_METHOD_NONE;
+ if (conn->oauth_client_secret && *conn->oauth_client_secret)
+ {
+ auth_method = I_TOKEN_AUTH_METHOD_SECRET_BASIC;
+ }
- err = i_set_response_type(&session, I_RESPONSE_TYPE_DEVICE_CODE);
+ err = i_set_response_type(&session, session_response_type);
if (err)
{
iddawc_error(conn, err, "failed to set device code response type");
goto cleanup;
}
- auth_method = I_TOKEN_AUTH_METHOD_NONE;
- if (conn->oauth_client_secret && *conn->oauth_client_secret)
- auth_method = I_TOKEN_AUTH_METHOD_SECRET_BASIC;
-
err = i_set_parameter_list(&session,
I_OPT_CLIENT_ID, conn->oauth_client_id,
I_OPT_CLIENT_SECRET, conn->oauth_client_secret,
@@ -250,6 +264,18 @@ get_auth_token(PGconn *conn)
goto cleanup;
}
+ if (conn->oauth_client_secret && *conn->oauth_client_secret)
+ {
+ session_response_type = I_RESPONSE_TYPE_CLIENT_CREDENTIALS;
+ }
+
+ err = i_set_response_type(&session, session_response_type);
+ if (err)
+ {
+ iddawc_error(conn, err, "failed to set session response type");
+ goto cleanup;
+ }
+
/*
* Poll the token endpoint until either the user logs in and authorizes the
* use of a token, or a hard failure occurs. We perform one ping _before_
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 2ff450ce05..5d804c8c0d 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -361,6 +361,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"OAuth-Scope", "", 15,
offsetof(struct pg_conn, oauth_scope)},
+ {"oauth_bearer_token", NULL, NULL, NULL,
+ "OAuth-Bearer", "", 20,
+ offsetof(struct pg_conn, oauth_bearer_token)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
@@ -4200,6 +4204,8 @@ freePGconn(PGconn *conn)
free(conn->oauth_discovery_uri);
if (conn->oauth_client_id)
free(conn->oauth_client_id);
+ if(conn->oauth_bearer_token)
+ free(conn->oauth_bearer_token);
if (conn->oauth_client_secret)
free(conn->oauth_client_secret);
if (conn->oauth_scope)
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1b4de3dff0..91e71afe14 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -402,6 +402,7 @@ struct pg_conn
char *oauth_client_id; /* client identifier */
char *oauth_client_secret; /* client secret */
char *oauth_scope; /* access token scope */
+ char *oauth_bearer_token; /* oauth token */
bool oauth_want_retry; /* should we retry on failure? */
/* Optional file to write trace info to */
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-20 05:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
@ 2022-09-20 23:19 ` Jacob Champion <[email protected]>
2022-09-21 16:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2022-09-20 23:19 UTC (permalink / raw)
To: mahendrakar s <[email protected]>; +Cc: pgsql-hackers; [email protected] <[email protected]>; [email protected] <[email protected]>; [email protected]; [email protected]
Hi Mahendrakar, thanks for your interest and for the patch!
On Mon, Sep 19, 2022 at 10:03 PM mahendrakar s
<[email protected]> wrote:
> The changes for each component are summarized below.
>
> 1. Provider-specific extension:
> Each OAuth provider implements their own token validator as an
> extension. Extension registers an OAuth provider hook which is matched
> to a line in the HBA file.
How easy is it to write a Bearer validator using C? My limited
understanding was that most providers were publishing libraries in
higher-level languages.
Along those lines, sample validators will need to be provided, both to
help in review and to get the pytest suite green again. (And coverage
for the new code is important, too.)
> 2. Add support to pass on the OAuth bearer token. In this
> obtaining the bearer token is left to 3rd party application or user.
>
> ./psql -U <username> -d 'dbname=postgres
> oauth_client_id=<client_id> oauth_bearer_token=<token>
This hurts, but I think people are definitely going to ask for it, given
the frightening practice of copy-pasting these (incredibly sensitive
secret) tokens all over the place... Ideally I'd like to implement
sender constraints for the Bearer token, to *prevent* copy-pasting (or,
you know, outright theft). But I'm not sure that sender constraints are
well-implemented yet for the major providers.
> 3. HBA: An additional param ‘provider’ is added for the oauth method.
> Defining "oauth" as method + passing provider, issuer endpoint
> and expected audience
>
> * * * * oauth provider=<token validation extension>
> issuer=.... scope=....
Naming aside (this conflicts with Samay's previous proposal, I think), I
have concerns about the implementation. There's this code:
> + if (oauth_provider && oauth_provider->name)
> + {
> + ereport(ERROR,
> + (errmsg("OAuth provider \"%s\" is already loaded.",
> + oauth_provider->name)));
> + }
which appears to prevent loading more than one global provider. But
there's also code that deals with a provider list? (Again, it'd help to
have test code covering the new stuff.)
> b) libpq optionally compiled for the clients which
> explicitly need libpq to orchestrate OAuth communication with the
> issuer (it depends heavily on 3rd party library iddawc as Jacob
> already pointed out. The library seems to be supporting all the OAuth
> flows.)
Speaking of iddawc, I don't think it's a dependency we should choose to
rely on. For all the code that it has, it doesn't seem to provide
compatibility with several real-world providers.
Google, for one, chose not to follow the IETF spec it helped author, and
iddawc doesn't support its flavor of Device Authorization. At another
point, I think iddawc tried to decode Azure's Bearer tokens, which is
incorrect...
I haven't been able to check if those problems have been fixed in a
recent version, but if we're going to tie ourselves to a huge
dependency, I'd at least like to believe that said dependency is
battle-tested and solid, and personally I don't feel like iddawc is.
> - auth_method = I_TOKEN_AUTH_METHOD_NONE;
> - if (conn->oauth_client_secret && *conn->oauth_client_secret)
> - auth_method = I_TOKEN_AUTH_METHOD_SECRET_BASIC;
This code got moved, but I'm not sure why? It doesn't appear to have
made a change to the logic.
> + if (conn->oauth_client_secret && *conn->oauth_client_secret)
> + {
> + session_response_type = I_RESPONSE_TYPE_CLIENT_CREDENTIALS;
> + }
Is this an Azure-specific requirement? Ideally a public client (which
psql is) shouldn't have to provide a secret to begin with, if I
understand that bit of the protocol correctly. I think Google also
required provider-specific changes in this part of the code, and
unfortunately I don't think they looked the same as yours.
We'll have to figure all that out... Standards are great; everyone has
one of their own. :)
Thanks,
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-20 05:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-09-20 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2022-09-21 16:03 ` Jacob Champion <[email protected]>
2022-09-21 22:10 ` RE: [EXTERNAL] Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovskiy <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2022-09-21 16:03 UTC (permalink / raw)
To: mahendrakar s <[email protected]>; +Cc: pgsql-hackers; [email protected] <[email protected]>; [email protected] <[email protected]>; [email protected]; [email protected]
On Tue, Sep 20, 2022 at 4:19 PM Jacob Champion <[email protected]> wrote:
> > 2. Add support to pass on the OAuth bearer token. In this
> > obtaining the bearer token is left to 3rd party application or user.
> >
> > ./psql -U <username> -d 'dbname=postgres
> > oauth_client_id=<client_id> oauth_bearer_token=<token>
>
> This hurts, but I think people are definitely going to ask for it, given
> the frightening practice of copy-pasting these (incredibly sensitive
> secret) tokens all over the place...
After some further thought -- in this case, you already have an opaque
Bearer token (and therefore you already know, out of band, which
provider needs to be used), you're willing to copy-paste it from
whatever service you got it from, and you have an extension plugged
into Postgres on the backend that verifies this Bearer blob using some
procedure that Postgres knows nothing about.
Why do you need the OAUTHBEARER mechanism logic at that point? Isn't
that identical to a custom password scheme? It seems like that could
be handled completely by Samay's pluggable auth proposal.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* RE: [EXTERNAL] Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-20 05:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-09-20 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-21 16:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2022-09-21 22:10 ` Andrey Chudnovskiy <[email protected]>
2022-09-21 22:31 ` Re: [EXTERNAL] Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Andrey Chudnovskiy @ 2022-09-21 22:10 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; mahendrakar s <[email protected]>; +Cc: pgsql-hackers; [email protected] <[email protected]>; [email protected] <[email protected]>; Mahendrakar Srinivasarao <[email protected]>
We can support both passing the token from an upstream client and libpq implementing OAUTH2 protocol to obtaining one.
Libpq implementing OAUTHBEARER is needed for community/3rd party tools to have user-friendly authentication experience:
1. For community client tools, like pg_admin, psql etc.
Example experience: pg_admin would be able to open a popup dialog to authenticate customer and keep refresh token to avoid asking the user frequently.
2. For 3rd party connectors supporting generic OAUTH with any provider. Useful for datawiz clients, like Tableau or ETL tools. Those can support both user and client OAUTH flows.
Libpq passing toked directly from an upstream client is useful in other scenarios:
1. Enterprise clients, built with .Net / Java and using provider-specific authentication libraries, like MSAL for AAD. Those can also support more advance provider-specific token acquisition flows.
2. Resource-tight (like IoT) clients. Those can be compiled without optional libpq flag not including the iddawc or other dependency.
Thanks!
Andrey.
-----Original Message-----
From: Jacob Champion <[email protected]>
Sent: Wednesday, September 21, 2022 9:03 AM
To: mahendrakar s <[email protected]>
Cc: [email protected]; [email protected]; [email protected]; Andrey Chudnovskiy <[email protected]>; Mahendrakar Srinivasarao <[email protected]>
Subject: [EXTERNAL] Re: [PoC] Federated Authn/z with OAUTHBEARER
[You don't often get email from [email protected]. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
On Tue, Sep 20, 2022 at 4:19 PM Jacob Champion <[email protected]> wrote:
> > 2. Add support to pass on the OAuth bearer token. In this
> > obtaining the bearer token is left to 3rd party application or user.
> >
> > ./psql -U <username> -d 'dbname=postgres
> > oauth_client_id=<client_id> oauth_bearer_token=<token>
>
> This hurts, but I think people are definitely going to ask for it,
> given the frightening practice of copy-pasting these (incredibly
> sensitive
> secret) tokens all over the place...
After some further thought -- in this case, you already have an opaque Bearer token (and therefore you already know, out of band, which provider needs to be used), you're willing to copy-paste it from whatever service you got it from, and you have an extension plugged into Postgres on the backend that verifies this Bearer blob using some procedure that Postgres knows nothing about.
Why do you need the OAUTHBEARER mechanism logic at that point? Isn't that identical to a custom password scheme? It seems like that could be handled completely by Samay's pluggable auth proposal.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [EXTERNAL] Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-20 05:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-09-20 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-21 16:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-21 22:10 ` RE: [EXTERNAL] Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovskiy <[email protected]>
@ 2022-09-21 22:31 ` Jacob Champion <[email protected]>
2022-09-22 04:55 ` Re: [EXTERNAL] Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2022-09-21 22:31 UTC (permalink / raw)
To: Andrey Chudnovskiy <[email protected]>; +Cc: mahendrakar s <[email protected]>; pgsql-hackers; [email protected] <[email protected]>; [email protected] <[email protected]>; Mahendrakar Srinivasarao <[email protected]>
On Wed, Sep 21, 2022 at 3:10 PM Andrey Chudnovskiy
<[email protected]> wrote:
> We can support both passing the token from an upstream client and libpq implementing OAUTH2 protocol to obtaining one.
Right, I agree that we could potentially do both.
> Libpq passing toked directly from an upstream client is useful in other scenarios:
> 1. Enterprise clients, built with .Net / Java and using provider-specific authentication libraries, like MSAL for AAD. Those can also support more advance provider-specific token acquisition flows.
> 2. Resource-tight (like IoT) clients. Those can be compiled without optional libpq flag not including the iddawc or other dependency.
What I don't understand is how the OAUTHBEARER mechanism helps you in
this case. You're short-circuiting the negotiation where the server
tells the client what provider to use and what scopes to request, and
instead you're saying "here's a secret string, just take it and
validate it with magic."
I realize the ability to pass an opaque token may be useful, but from
the server's perspective, I don't see what differentiates it from the
password auth method plus a custom authenticator plugin. Why pay for
the additional complexity of OAUTHBEARER if you're not going to use
it?
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [EXTERNAL] Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-20 05:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-09-20 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-21 16:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-21 22:10 ` RE: [EXTERNAL] Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovskiy <[email protected]>
2022-09-21 22:31 ` Re: [EXTERNAL] Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2022-09-22 04:55 ` Andrey Chudnovsky <[email protected]>
2022-09-22 21:53 ` Re: [EXTERNAL] Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Andrey Chudnovsky @ 2022-09-22 04:55 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Andrey Chudnovskiy <[email protected]>; mahendrakar s <[email protected]>; pgsql-hackers; [email protected] <[email protected]>; [email protected] <[email protected]>; Mahendrakar Srinivasarao <[email protected]>
First, My message from corp email wasn't displayed in the thread,
That is what Jacob replied to, let me post it here for context:
> We can support both passing the token from an upstream client and libpq implementing OAUTH2 protocol to obtain one.
>
> Libpq implementing OAUTHBEARER is needed for community/3rd party tools to have user-friendly authentication experience:
>
> 1. For community client tools, like pg_admin, psql etc.
> Example experience: pg_admin would be able to open a popup dialog to authenticate customers and keep refresh tokens to avoid asking the user frequently.
> 2. For 3rd party connectors supporting generic OAUTH with any provider. Useful for datawiz clients, like Tableau or ETL tools. Those can support both user and client OAUTH flows.
>
> Libpq passing toked directly from an upstream client is useful in other scenarios:
> 1. Enterprise clients, built with .Net / Java and using provider-specific authentication libraries, like MSAL for AAD. Those can also support more advanced provider-specific token acquisition flows.
> 2. Resource-tight (like IoT) clients. Those can be compiled without the optional libpq flag not including the iddawc or other dependency.
-----------------------------------------------------------------------------------------------------
On this:
> What I don't understand is how the OAUTHBEARER mechanism helps you in
> this case. You're short-circuiting the negotiation where the server
> tells the client what provider to use and what scopes to request, and
> instead you're saying "here's a secret string, just take it and
> validate it with magic."
>
> I realize the ability to pass an opaque token may be useful, but from
> the server's perspective, I don't see what differentiates it from the
> password auth method plus a custom authenticator plugin. Why pay for
> the additional complexity of OAUTHBEARER if you're not going to use
> it?
Yes, passing a token as a new auth method won't make much sense in
isolation. However:
1. Since OAUTHBEARER is supported in the ecosystem, passing a token as
a way to authenticate with OAUTHBEARER is more consistent (IMO), then
passing it as a password.
2. Validation on the backend side doesn't depend on whether the token
is obtained by libpq or transparently passed by the upstream client.
3. Single OAUTH auth method on the server side for both scenarios,
would allow both enterprise clients with their own Token acquisition
and community clients using libpq flows to connect as the same PG
users/roles.
On Wed, Sep 21, 2022 at 8:36 PM Jacob Champion <[email protected]> wrote:
>
> On Wed, Sep 21, 2022 at 3:10 PM Andrey Chudnovskiy
> <[email protected]> wrote:
> > We can support both passing the token from an upstream client and libpq implementing OAUTH2 protocol to obtaining one.
>
> Right, I agree that we could potentially do both.
>
> > Libpq passing toked directly from an upstream client is useful in other scenarios:
> > 1. Enterprise clients, built with .Net / Java and using provider-specific authentication libraries, like MSAL for AAD. Those can also support more advance provider-specific token acquisition flows.
> > 2. Resource-tight (like IoT) clients. Those can be compiled without optional libpq flag not including the iddawc or other dependency.
>
> What I don't understand is how the OAUTHBEARER mechanism helps you in
> this case. You're short-circuiting the negotiation where the server
> tells the client what provider to use and what scopes to request, and
> instead you're saying "here's a secret string, just take it and
> validate it with magic."
>
> I realize the ability to pass an opaque token may be useful, but from
> the server's perspective, I don't see what differentiates it from the
> password auth method plus a custom authenticator plugin. Why pay for
> the additional complexity of OAUTHBEARER if you're not going to use
> it?
>
> --Jacob
>
>
>
>
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [EXTERNAL] Re: [PoC] Federated Authn/z with OAUTHBEARER
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-20 05:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-09-20 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-21 16:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-21 22:10 ` RE: [EXTERNAL] Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovskiy <[email protected]>
2022-09-21 22:31 ` Re: [EXTERNAL] Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-22 04:55 ` Re: [EXTERNAL] Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
@ 2022-09-22 21:53 ` Jacob Champion <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2022-09-22 21:53 UTC (permalink / raw)
To: Andrey Chudnovsky <[email protected]>; +Cc: Andrey Chudnovskiy <[email protected]>; mahendrakar s <[email protected]>; pgsql-hackers; [email protected] <[email protected]>; [email protected] <[email protected]>; Mahendrakar Srinivasarao <[email protected]>
On 9/21/22 21:55, Andrey Chudnovsky wrote:
> First, My message from corp email wasn't displayed in the thread,
I see it on the public archives [1]. Your client is choosing some pretty
confusing quoting tactics, though, which you may want to adjust. :D
I have what I'll call some "skeptical curiosity" here -- you don't need
to defend your use cases to me by any means, but I'd love to understand
more about them.
> Yes, passing a token as a new auth method won't make much sense in
> isolation. However:
> 1. Since OAUTHBEARER is supported in the ecosystem, passing a token as
> a way to authenticate with OAUTHBEARER is more consistent (IMO), then
> passing it as a password.
Agreed. It's probably not a very strong argument for the new mechanism,
though, especially if you're not using the most expensive code inside it.
> 2. Validation on the backend side doesn't depend on whether the token
> is obtained by libpq or transparently passed by the upstream client.
Sure.
> 3. Single OAUTH auth method on the server side for both scenarios,
> would allow both enterprise clients with their own Token acquisition
> and community clients using libpq flows to connect as the same PG
> users/roles.
Okay, this is a stronger argument. With that in mind, I want to revisit
your examples and maybe provide some counterproposals:
>> Libpq passing toked directly from an upstream client is useful in other scenarios:
>> 1. Enterprise clients, built with .Net / Java and using provider-specific authentication libraries, like MSAL for AAD. Those can also support more advanced provider-specific token acquisition flows.
I can see that providing a token directly would help you work around
limitations in libpq's "standard" OAuth flows, whether we use iddawc or
not. And it's cheap in terms of implementation. But I have a feeling it
would fall apart rapidly with error cases, where the server is giving
libpq information via the OAUTHBEARER mechanism, but libpq can only
communicate to your wrapper through human-readable error messages on stderr.
This seems like clear motivation for client-side SASL plugins (which
were also discussed on Samay's proposal thread). That's a lot more
expensive to implement in libpq, but if it were hypothetically
available, wouldn't you rather your provider-specific code be able to
speak OAUTHBEARER directly with the server?
>> 2. Resource-tight (like IoT) clients. Those can be compiled without the optional libpq flag not including the iddawc or other dependency.
I want to dig into this much more; resource-constrained systems are near
and dear to me. I can see two cases here:
Case 1: The device is an IoT client that wants to connect on its own
behalf. Why would you want to use OAuth in that case? And how would the
IoT device get its Bearer token to begin with? I'm much more used to
architectures that provision high-entropy secrets for this, whether
they're incredibly long passwords per device (in which case,
channel-bound SCRAM should be a fairly strong choice?) or client certs
(which can be better decentralized, but make for a lot of bookkeeping).
If the answer to that is, "we want an IoT client to be able to connect
using the same role as a person", then I think that illustrates a clear
need for SASL negotiation. That would let the IoT client choose
SCRAM-*-PLUS or EXTERNAL, and the person at the keyboard can choose
OAUTHBEARER. Then we have incredible flexibility, because you don't have
to engineer one mechanism to handle them all.
Case 2: The constrained device is being used as a jump point. So there's
an actual person at a keyboard, trying to get into a backend server
(maybe behind a firewall layer, etc.), and the middlebox is either not
web-connected or is incredibly tiny for some reason. That might be a
good use case for a copy-pasted Bearer token, but is there actual demand
for that use case? What motivation would you (or your end user) have for
choosing a fairly heavy, web-centric authentication method in such a
constrained environment?
Are there other resource-constrained use cases I've missed?
Thanks,
--Jacob
[1]
https://www.postgresql.org/message-id/MN0PR21MB31694BAC193ECE1807FD45358F4F9%40MN0PR21MB3169.namprd2...
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
@ 2025-03-07 00:35 ` Jacob Champion <[email protected]>
2 siblings, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2025-03-07 00:35 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Andres Freund <[email protected]>; Daniel Gustafsson <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
On Thu, Mar 6, 2025 at 12:57 PM Jacob Champion
<[email protected]> wrote:
> Problem 1 is a simple patch. I am working on a fix for Problem 2, but
> I got stuck trying to get a "perfect" solution working yesterday...
> Since this is a partial(?) blocker for getting NetBSD going, I'm going
> to pivot to an ugly-but-simple approach today.
Attached:
- 0001 fixes IPv6 failures,
- 0002 fixes set_timer() on Mac, and
- 0003-0005 are the existing followup patches from upthread.
Thanks!
--Jacob
Attachments:
[application/octet-stream] 0001-oauth-Use-IPv4-only-issuer-in-oauth_validator-tests.patch (3.2K, ../../CAOYmi+=4htNLXvuTS58GL96qwBptToDop09PWFy6uzDHNz4qTw@mail.gmail.com/2-0001-oauth-Use-IPv4-only-issuer-in-oauth_validator-tests.patch)
download | inline diff:
From 4e1024eb47a3d19145a8db42a48d55d608ad4054 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Tue, 4 Mar 2025 09:41:19 -0800
Subject: [PATCH 1/5] oauth: Use IPv4-only issuer in oauth_validator tests
The test authorization server implemented in oauth_server.py does not
listen on IPv6. Most of the time, libcurl happily falls back to IPv4
after failing its initial connection, but on NetBSD, something is
consistently showing up on the unreserved IPv6 port and causing a test
failure.
Rather than deal with dual-stack details across all test platforms,
change the issuer to enforce the use of IPv4 only. (This elicits more
punishing timeout behavior from libcurl, so it's a useful change from
the testing perspective as well.)
Reported-by: Thomas Munro <[email protected]>
Discussion: https://postgr.es/m/CAOYmi%2Bn4EDOOUL27_OqYT2-F2rS6S%2B3mK-ppWb2Ec92UEoUbYA%40mail.gmail.com
---
src/test/modules/oauth_validator/t/001_server.pl | 2 +-
src/test/modules/oauth_validator/t/OAuth/Server.pm | 4 ++--
src/test/modules/oauth_validator/t/oauth_server.py | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/test/modules/oauth_validator/t/001_server.pl b/src/test/modules/oauth_validator/t/001_server.pl
index 6fa59fbeb25..30295364ebd 100644
--- a/src/test/modules/oauth_validator/t/001_server.pl
+++ b/src/test/modules/oauth_validator/t/001_server.pl
@@ -68,7 +68,7 @@ END
}
my $port = $webserver->port();
-my $issuer = "http://localhost:$port";
+my $issuer = "http://127.0.0.1:$port";
unlink($node->data_dir . '/pg_hba.conf');
$node->append_conf(
diff --git a/src/test/modules/oauth_validator/t/OAuth/Server.pm b/src/test/modules/oauth_validator/t/OAuth/Server.pm
index 655b2870b0b..52ae7afa991 100644
--- a/src/test/modules/oauth_validator/t/OAuth/Server.pm
+++ b/src/test/modules/oauth_validator/t/OAuth/Server.pm
@@ -15,7 +15,7 @@ OAuth::Server - runs a mock OAuth authorization server for testing
$server->run;
my $port = $server->port;
- my $issuer = "http://localhost:$port";
+ my $issuer = "http://127.0.0.1:$port";
# test against $issuer...
@@ -28,7 +28,7 @@ daemon implemented in t/oauth_server.py. (Python has a fairly usable HTTP server
in its standard library, so the implementation was ported from Perl.)
This authorization server does not use TLS (it implements a nonstandard, unsafe
-issuer at "http://localhost:<port>"), so libpq in particular will need to set
+issuer at "http://127.0.0.1:<port>"), so libpq in particular will need to set
PGOAUTHDEBUG=UNSAFE to be able to talk to it.
=cut
diff --git a/src/test/modules/oauth_validator/t/oauth_server.py b/src/test/modules/oauth_validator/t/oauth_server.py
index 4faf3323d38..5bc30be87fd 100755
--- a/src/test/modules/oauth_validator/t/oauth_server.py
+++ b/src/test/modules/oauth_validator/t/oauth_server.py
@@ -251,7 +251,7 @@ class OAuthHandler(http.server.BaseHTTPRequestHandler):
def config(self) -> JsonObject:
port = self.server.socket.getsockname()[1]
- issuer = f"http://localhost:{port}"
+ issuer = f"http://127.0.0.1:{port}"
if self._alt_issuer:
issuer += "/alternate"
elif self._parameterized:
--
2.34.1
[application/octet-stream] 0002-oauth-Fix-postcondition-for-set_timer-on-BSD.patch (3.6K, ../../CAOYmi+=4htNLXvuTS58GL96qwBptToDop09PWFy6uzDHNz4qTw@mail.gmail.com/3-0002-oauth-Fix-postcondition-for-set_timer-on-BSD.patch)
download | inline diff:
From 410dce1670a04de81d533dd1b5456e363b144ca8 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Thu, 6 Mar 2025 15:02:37 -0800
Subject: [PATCH 2/5] oauth: Fix postcondition for set_timer on BSD
On macOS, readding an EVFILT_TIMER to a kqueue does not appear to clear
out previously queued timer events, so checks for timer expiration do
not work correctly during token retrieval. Switching to IPv4-only
communication exposes the problem, because libcurl is no longer clearing
out other timeouts related to Happy Eyeballs dual-stack handling.
Fully remove and re-register the kqueue timer events during each call to
set_timer(), to clear out any stale expirations.
Discussion: https://postgr.es/m/CAOYmi%2Bn4EDOOUL27_OqYT2-F2rS6S%2B3mK-ppWb2Ec92UEoUbYA%40mail.gmail.com
---
src/interfaces/libpq/fe-auth-oauth-curl.c | 48 +++++++++++++++++------
1 file changed, 35 insertions(+), 13 deletions(-)
diff --git a/src/interfaces/libpq/fe-auth-oauth-curl.c b/src/interfaces/libpq/fe-auth-oauth-curl.c
index 6e60a81574d..2d6d4b1a123 100644
--- a/src/interfaces/libpq/fe-auth-oauth-curl.c
+++ b/src/interfaces/libpq/fe-auth-oauth-curl.c
@@ -1326,6 +1326,10 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
* in the set at all times and just disarm it when it's not needed. For kqueue,
* the timer is removed completely when disabled to prevent stale timeouts from
* remaining in the queue.
+ *
+ * To meet Curl requirements for the CURLMOPT_TIMERFUNCTION, implementations of
+ * set_timer must handle repeated calls by fully discarding any previous running
+ * or expired timer.
*/
static bool
set_timer(struct async_ctx *actx, long timeout)
@@ -1373,26 +1377,44 @@ set_timer(struct async_ctx *actx, long timeout)
timeout = 1;
#endif
- /* Enable/disable the timer itself. */
- EV_SET(&ev, 1, EVFILT_TIMER, timeout < 0 ? EV_DELETE : (EV_ADD | EV_ONESHOT),
- 0, timeout, 0);
+ /*
+ * Always disable the timer, and remove it from the multiplexer, to clear
+ * out any already-queued events. (On some BSDs, adding an EVFILT_TIMER to
+ * a kqueue that already has one will clear stale events, but not on
+ * macOS.)
+ *
+ * If there was no previous timer set, the kevent calls will result in
+ * ENOENT, which is fine.
+ */
+ EV_SET(&ev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, 0);
if (kevent(actx->timerfd, &ev, 1, NULL, 0, NULL) < 0 && errno != ENOENT)
{
- actx_error(actx, "setting kqueue timer to %ld: %m", timeout);
+ actx_error(actx, "deleting kqueue timer: %m", timeout);
return false;
}
- /*
- * Add/remove the timer to/from the mux. (In contrast with epoll, if we
- * allowed the timer to remain registered here after being disabled, the
- * mux queue would retain any previous stale timeout notifications and
- * remain readable.)
- */
- EV_SET(&ev, actx->timerfd, EVFILT_READ, timeout < 0 ? EV_DELETE : EV_ADD,
- 0, 0, 0);
+ EV_SET(&ev, actx->timerfd, EVFILT_READ, EV_DELETE, 0, 0, 0);
if (kevent(actx->mux, &ev, 1, NULL, 0, NULL) < 0 && errno != ENOENT)
{
- actx_error(actx, "could not update timer on kqueue: %m");
+ actx_error(actx, "removing kqueue timer from multiplexer: %m");
+ return false;
+ }
+
+ /* If we're not adding a timer, we're done. */
+ if (timeout < 0)
+ return true;
+
+ EV_SET(&ev, 1, EVFILT_TIMER, (EV_ADD | EV_ONESHOT), 0, timeout, 0);
+ if (kevent(actx->timerfd, &ev, 1, NULL, 0, NULL) < 0)
+ {
+ actx_error(actx, "setting kqueue timer to %ld: %m", timeout);
+ return false;
+ }
+
+ EV_SET(&ev, actx->timerfd, EVFILT_READ, EV_ADD, 0, 0, 0);
+ if (kevent(actx->mux, &ev, 1, NULL, 0, NULL) < 0)
+ {
+ actx_error(actx, "adding kqueue timer to multiplexer: %m");
return false;
}
--
2.34.1
[application/octet-stream] 0003-oauth-Disallow-synchronous-DNS-in-libcurl.patch (4.5K, ../../CAOYmi+=4htNLXvuTS58GL96qwBptToDop09PWFy6uzDHNz4qTw@mail.gmail.com/4-0003-oauth-Disallow-synchronous-DNS-in-libcurl.patch)
download | inline diff:
From c2e098c592a8f2d2c3d8f12e82b2736a630ca282 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Mon, 24 Feb 2025 15:02:01 -0800
Subject: [PATCH 3/5] oauth: Disallow synchronous DNS in libcurl
There is concern that a blocking DNS lookup in libpq could stall a
backend process (say, via FDW). Since there's currently no strong
evidence that synchronous DNS is a popular option, disallow it entirely
rather than warning at configure time. We can revisit if anyone
complains.
Per query from Andres Freund.
Discussion: https://postgr.es/m/p4bd7mn6dxr2zdak74abocyltpfdxif4pxqzixqpxpetjwt34h%40qc6jgfmoddvq
---
config/programs.m4 | 10 +++++-----
configure | 14 +++++---------
meson.build | 18 ++++++------------
3 files changed, 16 insertions(+), 26 deletions(-)
diff --git a/config/programs.m4 b/config/programs.m4
index 061b13376ac..0a07feb37cc 100644
--- a/config/programs.m4
+++ b/config/programs.m4
@@ -316,7 +316,7 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
[Define to 1 if curl_global_init() is guaranteed to be thread-safe.])
fi
- # Warn if a thread-friendly DNS resolver isn't built.
+ # Fail if a thread-friendly DNS resolver isn't built.
AC_CACHE_CHECK([for curl support for asynchronous DNS], [pgac_cv__libcurl_async_dns],
[AC_RUN_IFELSE([AC_LANG_PROGRAM([
#include <curl/curl.h>
@@ -332,10 +332,10 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
[pgac_cv__libcurl_async_dns=yes],
[pgac_cv__libcurl_async_dns=no],
[pgac_cv__libcurl_async_dns=unknown])])
- if test x"$pgac_cv__libcurl_async_dns" != xyes ; then
- AC_MSG_WARN([
+ if test x"$pgac_cv__libcurl_async_dns" = xno ; then
+ AC_MSG_ERROR([
*** The installed version of libcurl does not support asynchronous DNS
-*** lookups. Connection timeouts will not be honored during DNS resolution,
-*** which may lead to hangs in client programs.])
+*** lookups. Rebuild libcurl with the AsynchDNS feature enabled in order
+*** to use it with libpq.])
fi
])# PGAC_CHECK_LIBCURL
diff --git a/configure b/configure
index 93fddd69981..559f535f5cd 100755
--- a/configure
+++ b/configure
@@ -12493,7 +12493,7 @@ $as_echo "#define HAVE_THREADSAFE_CURL_GLOBAL_INIT 1" >>confdefs.h
fi
- # Warn if a thread-friendly DNS resolver isn't built.
+ # Fail if a thread-friendly DNS resolver isn't built.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl support for asynchronous DNS" >&5
$as_echo_n "checking for curl support for asynchronous DNS... " >&6; }
if ${pgac_cv__libcurl_async_dns+:} false; then :
@@ -12535,15 +12535,11 @@ fi
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv__libcurl_async_dns" >&5
$as_echo "$pgac_cv__libcurl_async_dns" >&6; }
- if test x"$pgac_cv__libcurl_async_dns" != xyes ; then
- { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING:
-*** The installed version of libcurl does not support asynchronous DNS
-*** lookups. Connection timeouts will not be honored during DNS resolution,
-*** which may lead to hangs in client programs." >&5
-$as_echo "$as_me: WARNING:
+ if test x"$pgac_cv__libcurl_async_dns" = xno ; then
+ as_fn_error $? "
*** The installed version of libcurl does not support asynchronous DNS
-*** lookups. Connection timeouts will not be honored during DNS resolution,
-*** which may lead to hangs in client programs." >&2;}
+*** lookups. Rebuild libcurl with the AsynchDNS feature enabled in order
+*** to use it with libpq." "$LINENO" 5
fi
fi
diff --git a/meson.build b/meson.build
index 13c13748e5d..b6daa5b7040 100644
--- a/meson.build
+++ b/meson.build
@@ -909,9 +909,7 @@ if not libcurlopt.disabled()
cdata.set('HAVE_THREADSAFE_CURL_GLOBAL_INIT', 1)
endif
- # Warn if a thread-friendly DNS resolver isn't built.
- libcurl_async_dns = false
-
+ # Fail if a thread-friendly DNS resolver isn't built.
if not meson.is_cross_build()
r = cc.run('''
#include <curl/curl.h>
@@ -931,16 +929,12 @@ if not libcurlopt.disabled()
)
assert(r.compiled())
- if r.returncode() == 0
- libcurl_async_dns = true
- endif
- endif
-
- if not libcurl_async_dns
- warning('''
+ if r.returncode() != 0
+ error('''
*** The installed version of libcurl does not support asynchronous DNS
-*** lookups. Connection timeouts will not be honored during DNS resolution,
-*** which may lead to hangs in client programs.''')
+*** lookups. Rebuild libcurl with the AsynchDNS feature enabled in order
+*** to use it with libpq.''')
+ endif
endif
endif
--
2.34.1
[application/octet-stream] 0004-oauth-Improve-validator-docs-on-interruptibility.patch (1.9K, ../../CAOYmi+=4htNLXvuTS58GL96qwBptToDop09PWFy6uzDHNz4qTw@mail.gmail.com/5-0004-oauth-Improve-validator-docs-on-interruptibility.patch)
download | inline diff:
From d9f12352eec4002d30aa397dd3921f4340401c08 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Tue, 25 Feb 2025 07:42:43 -0800
Subject: [PATCH 4/5] oauth: Improve validator docs on interruptibility
Andres pointed out that EINTR handling is inadequate for real-world use
cases. Direct module writers to our wait APIs instead.
Discussion: https://postgr.es/m/p4bd7mn6dxr2zdak74abocyltpfdxif4pxqzixqpxpetjwt34h%40qc6jgfmoddvq
---
doc/src/sgml/oauth-validators.sgml | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/doc/src/sgml/oauth-validators.sgml b/doc/src/sgml/oauth-validators.sgml
index 356f11d3bd8..704089dd7b3 100644
--- a/doc/src/sgml/oauth-validators.sgml
+++ b/doc/src/sgml/oauth-validators.sgml
@@ -209,11 +209,13 @@
<para>
Modules must remain interruptible by signals so that the server can
correctly handle authentication timeouts and shutdown signals from
- <application>pg_ctl</application>. For example, a module receiving
- <symbol>EINTR</symbol>/<symbol>EAGAIN</symbol> from a blocking call
- should call <function>CHECK_FOR_INTERRUPTS()</function> before retrying.
- The same should be done during any long-running loops. Failure to follow
- this guidance may result in unresponsive backend sessions.
+ <application>pg_ctl</application>. For example, blocking calls on sockets
+ should generally be replaced with code that handles both socket events
+ and interrupts without races (see <function>WaitLatchOrSocket()</function>,
+ <function>WaitEventSetWait()</function>, et al), and long-running loops
+ should periodically call <function>CHECK_FOR_INTERRUPTS()</function>.
+ Failure to follow this guidance may result in unresponsive backend
+ sessions.
</para>
</listitem>
</varlistentry>
--
2.34.1
[application/octet-stream] 0005-oauth-Simplify-copy-of-PGoauthBearerRequest.patch (992B, ../../CAOYmi+=4htNLXvuTS58GL96qwBptToDop09PWFy6uzDHNz4qTw@mail.gmail.com/6-0005-oauth-Simplify-copy-of-PGoauthBearerRequest.patch)
download | inline diff:
From b5beb488b7727a0ec88401833f7d08a37438bbf4 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Mon, 24 Feb 2025 15:43:09 -0800
Subject: [PATCH 5/5] oauth: Simplify copy of PGoauthBearerRequest
Follow-up to 03366b61d. Since there are no more const members in the
PGoauthBearerRequest struct, the previous memcpy() can be replaced with
simple assignment.
---
src/interfaces/libpq/fe-auth-oauth.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
index fb1e9a1a8aa..cf1a25e2ccc 100644
--- a/src/interfaces/libpq/fe-auth-oauth.c
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -781,7 +781,7 @@ setup_token_request(PGconn *conn, fe_oauth_state *state)
goto fail;
}
- memcpy(request_copy, &request, sizeof(request));
+ *request_copy = request;
conn->async_auth = run_user_oauth_flow;
conn->cleanup_async_auth = cleanup_user_oauth_flow;
--
2.34.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
@ 2025-03-07 05:12 ` Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2 siblings, 1 reply; 194+ messages in thread
From: Thomas Munro @ 2025-03-07 05:12 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Andres Freund <[email protected]>; Daniel Gustafsson <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
On Fri, Mar 7, 2025 at 9:57 AM Jacob Champion
<[email protected]> wrote:
> 2) macOS's EVFILT_TIMER implementation seems to be different from the
> other BSDs. On Mac, when you re-add a timer to a kqueue, any existing
> timer-fired events for it are not cleared out and the kqueue might
> remain readable. This breaks a postcondition of our set_timer()
> function, which is that new timeouts are supposed to completely
> replace previous timeouts.
I don't see that behaviour on my Mac with a simple program, and that
seems like it couldn't possibly be intended. Hmm... <browses source
code painfully> I wonder if this atomic generation scheme has a hole
in it, under concurrency...
https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/bsd/ker...
The code on the other OSes just dequeues it when reprogramming the
timer, which involves a lock and no doubt a few more cycles, and is
clearly not quite as exciting but ...
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
@ 2025-03-07 17:30 ` Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-03-07 17:30 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Andres Freund <[email protected]>; Daniel Gustafsson <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
On Thu, Mar 6, 2025 at 9:13 PM Thomas Munro <[email protected]> wrote:
> I don't see that behaviour on my Mac with a simple program, and that
> seems like it couldn't possibly be intended.
What version of macOS?
Just to make sure I'm not chasing ghosts, I've attached my test
program. Here are my CI results for running it:
= FreeBSD =
[ 6 us] timer is set
[ 1039 us] kqueue is readable
[ 1050 us] timer is reset
[ 1052 us] kqueue is not readable
= NetBSD =
[ 3 us] timer is set
[ 14993 us] kqueue is readable
[ 15000 us] timer is reset
[ 15002 us] kqueue is not readable
= OpenBSD =
[ 24 us] timer is set
[ 19660 us] kqueue is readable
[ 19709 us] timer is reset
[ 19712 us] kqueue is not readable
= macOS Sonoma =
[ 4 us] timer is set
[ 1282 us] kqueue is readable
[ 1286 us] timer is reset
[ 1287 us] kqueue is still readable
--Jacob
Attachments:
[application/octet-stream] kqueue_test.c (1.6K, ../../CAOYmi+kC9232rEPTMUV8NsGZOFWw-2dmPs=Zz0MT4HXmoBwPqQ@mail.gmail.com/2-kqueue_test.c)
download
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-03-07 21:51 ` Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Thomas Munro @ 2025-03-07 21:51 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Andres Freund <[email protected]>; Daniel Gustafsson <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
On Sat, Mar 8, 2025 at 6:31 AM Jacob Champion
<[email protected]> wrote:
> On Thu, Mar 6, 2025 at 9:13 PM Thomas Munro <[email protected]> wrote:
> > I don't see that behaviour on my Mac with a simple program, and that
> > seems like it couldn't possibly be intended.
>
> What version of macOS?
>
> Just to make sure I'm not chasing ghosts, I've attached my test
> program. Here are my CI results for running it:
Ah, right, yeah I see that here too. I thought you were saying that
kevent() could report an already triggered alarm even though we'd
replaced it (it doesn') but of course you meant poll(kq) as libpq
does.
I believe I know exactly why: kqueues are considered readable (by
poll/select/other kqueues) if there are any events queued[1]. Apple's
EVFILT_TIMER implementation is doing that trick[2] where it leaves
them queued, but filt_timerprocess() filters them out if its own
private _FIRED flag isn't set, so kevent() itself won't wake up or
return them. That trick doesn't survive nesting. I think I would
call that a bug. (I think I would keep the atomic CAS piece -- it
means you don't have to drain the timer callout synchronously when
reprogramming it which is a cool trick, but I think they overshot when
they left the knote queued.)
Maybe just do the delete-and-add in one call?
EV_SET(&ev[0], 1, EVFILT_TIMER, EV_DELETE, 0, 0, 0);
EV_SET(&ev[1], 1, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, timeout, 0);
if (kevent(kq, &ev[0], 2, NULL, 0, NULL) < 0)
[1] https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/bsd/ker...
[2] https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/bsd/ker...
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
@ 2025-03-07 23:02 ` Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-03-07 23:02 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Andres Freund <[email protected]>; Daniel Gustafsson <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
On Fri, Mar 7, 2025 at 1:52 PM Thomas Munro <[email protected]> wrote:
> I believe I know exactly why: kqueues are considered readable (by
> poll/select/other kqueues) if there are any events queued[1]. Apple's
> EVFILT_TIMER implementation is doing that trick[2] where it leaves
> them queued, but filt_timerprocess() filters them out if its own
> private _FIRED flag isn't set, so kevent() itself won't wake up or
> return them. That trick doesn't survive nesting. I think I would
> call that a bug.
Bleh. Thank you for the analysis!
> Maybe just do the delete-and-add in one call?
>
> EV_SET(&ev[0], 1, EVFILT_TIMER, EV_DELETE, 0, 0, 0);
> EV_SET(&ev[1], 1, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, timeout, 0);
> if (kevent(kq, &ev[0], 2, NULL, 0, NULL) < 0)
I think that requires me to copy the EV_RECEIPT handling from
register_socket(), to make sure an ENOENT is correctly ignored on
delete but doesn't mask failures from the addition. Do you prefer that
to the separate calls? (Or, better yet, is it easier than I'm making
it?)
Thanks!
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-03-17 11:36 ` Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Nazir Bilal Yavuz @ 2025-03-17 11:36 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Thomas Munro <[email protected]>; Andres Freund <[email protected]>; Daniel Gustafsson <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
Hi,
I just wanted to report that the 'oauth_validator/t/001_server.pl'
test failed on FreeBSD in one of my local CI runs [1]. I looked at the
thread but could not find the same error report; if this is already
known, please excuse me.
HEAD was at 3943f5cff6 and there were no other changes. Sharing the
failure here for visibility:
[11:09:56.548] stderr:
[11:09:56.548] # Failed test 'stress-async: stdout matches'
[11:09:56.548] # at
/tmp/cirrus-ci-build/src/test/modules/oauth_validator/t/001_server.pl
line 409.
[11:09:56.548] # ''
[11:09:56.548] # doesn't match '(?^:connection succeeded)'
[11:09:56.548] # Failed test 'stress-async: stderr matches'
[11:09:56.548] # at
/tmp/cirrus-ci-build/src/test/modules/oauth_validator/t/001_server.pl
line 410.
[11:09:56.548] # '[libcurl] * Host localhost:39251
was resolved.
[11:09:56.548] # [libcurl] * IPv6: ::1
[11:09:56.548] # [libcurl] * IPv4: 127.0.0.1
[11:09:56.548] # [libcurl] * Trying [::1]:39251...
[11:09:56.548] # [libcurl] * Immediate connect fail for ::1: Connection refused
[11:09:56.548] # [libcurl] * Trying 127.0.0.1:39251...
[11:09:56.548] # [libcurl] * Connected to localhost (127.0.0.1) port 39251
[11:09:56.548] # [libcurl] * using HTTP/1.x
[11:09:56.548] # [libcurl] > GET
/param/.well-known/openid-configuration HTTP/1.1
[11:09:56.548] # [libcurl] > Host: localhost:39251
[11:09:56.548] # [libcurl] >
[11:09:56.548] # [libcurl] * Request completely sent off
[11:09:56.548] # [libcurl] * HTTP 1.0, assume close after body
[11:09:56.548] # [libcurl] < HTTP/1.0 200 OK
[11:09:56.548] # [libcurl] < Server: BaseHTTP/0.6 Python/3.11.11
[11:09:56.548] # [libcurl] < Date: Mon, 17 Mar 2025 11:09:55 GMT
[11:09:56.548] # [libcurl] < Content-Type: application/json
[11:09:56.548] # [libcurl] < Content-Length: 400
[11:09:56.548] # [libcurl] <
[11:09:56.548] # [libcurl] < {"issuer":
"http://localhost:39251/param";, "token_endpoint":
"http://localhost:39251/param/token";, "device_authorization_endpoint":
"http://localhost:39251/param/authorize";, "response_types_supported":
["token"], "subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"],
"grant_types_supported": ["authorization_code",
"urn:ietf:params:oauth:grant-type:device_code"]}
[11:09:56.548] # [libcurl] * shutting down connection #0
[11:09:56.548] # [libcurl] * Hostname localhost was found in DNS cache
[11:09:56.548] # [libcurl] * Trying [::1]:39251...
[11:09:56.548] # [libcurl] * Immediate connect fail for ::1: Connection refused
[11:09:56.548] # [libcurl] * Trying 127.0.0.1:39251...
[11:09:56.548] # [libcurl] * Connected to localhost (127.0.0.1) port 39251
[11:09:56.548] # [libcurl] * using HTTP/1.x
[11:09:56.548] # [libcurl] > POST /param/authorize HTTP/1.1
[11:09:56.548] # [libcurl] > Host: localhost:39251
[11:09:56.548] # [libcurl] > Content-Length: 92
[11:09:56.548] # [libcurl] > Content-Type: application/x-www-form-urlencoded
[11:09:56.548] # [libcurl] >
[11:09:56.548] # [libcurl] >
scope=openid+postgres&client_id=eyJpbnRlcnZhbCI6MSwicmV0cmllcyI6MSwic3RhZ2UiOiJhbGwifQ%3D%3D
[11:09:56.548] # [libcurl] * upload completely sent off: 92 bytes
[11:09:56.548] # [libcurl] * HTTP 1.0, assume close after body
[11:09:56.548] # [libcurl] < HTTP/1.0 200 OK
[11:09:56.548] # [libcurl] < Server: BaseHTTP/0.6 Python/3.11.11
[11:09:56.548] # [libcurl] < Date: Mon, 17 Mar 2025 11:09:55 GMT
[11:09:56.548] # [libcurl] < Content-Type: application/json
[11:09:56.548] # [libcurl] < Content-Length: 132
[11:09:56.548] # [libcurl] <
[11:09:56.548] # [libcurl] < {"device_code": "postgres", "user_code":
"postgresuser", "verification_uri": "https://example.com/";,
"expires_in": 5, "interval": 1}
[11:09:56.548] # [libcurl] * shutting down connection #1
[11:09:56.548] # [libcurl] * Hostname localhost was found in DNS cache
[11:09:56.548] # [libcurl] * Trying [::1]:39251...
[11:09:56.548] # [libcurl] * Connected to localhost (::1) port 39251
[11:09:56.548] # [libcurl] * using HTTP/1.x
[11:09:56.548] # [libcurl] > POST /param/token HTTP/1.1
[11:09:56.548] # [libcurl] > Host: localhost:39251
[11:09:56.548] # [libcurl] > Content-Length: 157
[11:09:56.548] # [libcurl] > Content-Type: application/x-www-form-urlencoded
[11:09:56.548] # [libcurl] >
[11:09:56.548] # [libcurl] >
device_code=postgres&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code&client_id=eyJpbnRlcnZhbCI6MSwicmV0cmllcyI6MSwic3RhZ2UiOiJhbGwifQ%3D%3D
[11:09:56.548] # [libcurl] * upload completely sent off: 157 bytes
[11:09:56.548] # [libcurl] * Received HTTP/0.9 when not allowed
[11:09:56.548] # [libcurl] * closing connection #2
[11:09:56.548] # connection to database failed: connection to server
on socket "/tmp/xZKYtq40nL/.s.PGSQL.21400" failed: failed to obtain
access token: Unsupported protocol (libcurl: Received HTTP/0.9 when
not allowed)
[11:09:56.548] # '
[11:09:56.548] # matches '(?^:connection to database failed)'
[11:09:56.548] # Looks like you failed 2 tests of 121.
[11:09:56.548]
[11:09:56.548] (test program exited with status code 2)
[1] https://cirrus-ci.com/task/4621590844932096
--
Regards,
Nazir Bilal Yavuz
Microsoft
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
@ 2025-03-17 15:08 ` Jacob Champion <[email protected]>
2025-03-19 04:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
0 siblings, 2 replies; 194+ messages in thread
From: Jacob Champion @ 2025-03-17 15:08 UTC (permalink / raw)
To: Nazir Bilal Yavuz <[email protected]>; +Cc: Thomas Munro <[email protected]>; Andres Freund <[email protected]>; Daniel Gustafsson <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
On Mon, Mar 17, 2025 at 4:37 AM Nazir Bilal Yavuz <[email protected]> wrote:
>
> Hi,
>
> I just wanted to report that the 'oauth_validator/t/001_server.pl'
> test failed on FreeBSD in one of my local CI runs [1]. I looked at the
> thread but could not find the same error report; if this is already
> known, please excuse me.
Thanks for the report! Yes, this looks like the issue that NetBSD was having:
> [11:09:56.548] # [libcurl] * Trying [::1]:39251...
> [11:09:56.548] # [libcurl] * Connected to localhost (::1) port 39251
Curl should not have connected to ::1 (the test server isn't listening
on IPv6). Whatever is talking on that port doesn't understand HTTP,
and we later fail with the "HTTP/0.9" error -- a slightly confusing
way to describe a protocol violation.
0001 will fix that. I think we should get that and 0002 in, ASAP. (And
the others.) Thomas has shown me a side quest to get rid of the second
kqueue instance, but so far that is not bearing fruit and we shouldn't
wait on it.
Thanks again!
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-03-19 04:09 ` Thomas Munro <[email protected]>
2025-04-03 18:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Thomas Munro @ 2025-03-19 04:09 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Andres Freund <[email protected]>; Daniel Gustafsson <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
On Tue, Mar 18, 2025 at 4:08 AM Jacob Champion
<[email protected]> wrote:
> 0001 will fix that. I think we should get that and 0002 in, ASAP. (And
> the others.)
All pushed (wasn't sure if Daniel was going to but once I got tangled
up in all that kqueue stuff he probably quite reasonably assumed that
I would :-)).
> Thomas has shown me a side quest to get rid of the second
> kqueue instance, but so far that is not bearing fruit and we shouldn't
> wait on it.
Cool, thanks for looking into it anyway. (Feel free to post a
nonworking patch with a got-stuck-here-because problem statement...)
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
@ 2025-04-03 18:02 ` Jacob Champion <[email protected]>
2025-04-03 19:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-03 18:02 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Andres Freund <[email protected]>; Daniel Gustafsson <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
On Tue, Mar 18, 2025 at 9:09 PM Thomas Munro <[email protected]> wrote:
> All pushed (wasn't sure if Daniel was going to but once I got tangled
> up in all that kqueue stuff he probably quite reasonably assumed that
> I would :-)).
Attached are two more followups, separate from the libcurl split:
- 0001 is a patch originally proposed at [1]. Christoph pointed out
that the build fails on a platform that tries to enable Curl without
having either epoll() or kqueue(), due to a silly mistake I made in
the #ifdefs.
- 0002 should fix some timeouts in 002_client.pl reported by Andres on
Discord. I allowed a short connect_timeout to propagate into tests
which should not have it.
(The goal of 0001 is to get things building for now. After I finish
splitting the implementation into its own module, it'll make more
sense to simply not build that module on platforms that can't
implement a useful flow.)
Thanks,
--Jacob
[1] https://postgr.es/m/CAOYmi%2B%3D4898tXuTvb2LstorRo9JsAnBcn8LE%3DqrgVPiPW8ZfCw%40mail.gmail.com
Attachments:
[application/octet-stream] 0002-oauth-Remove-unneeded-timeouts-from-t-002_client.patch (1.1K, ../../CAOYmi+mHu_5i2waPRzCiX906gg2HNR3OpSGR1Vz=faLrCoAWcg@mail.gmail.com/2-0002-oauth-Remove-unneeded-timeouts-from-t-002_client.patch)
download | inline diff:
From 65c03c649084f9a7b54d172dc14f442e68b3aab0 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Thu, 3 Apr 2025 10:12:45 -0700
Subject: [PATCH 2/2] oauth: Remove unneeded timeouts from t/002_client
The connect_timeout=1 setting for the --hang-forever test was kept in
place for later tests, causing unexpected timeouts on slower buildfarm
animals. Remove it.
Reported-by: Andres Freund <[email protected]>
---
src/test/modules/oauth_validator/t/002_client.pl | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/test/modules/oauth_validator/t/002_client.pl b/src/test/modules/oauth_validator/t/002_client.pl
index ab83258d736..54769f12f57 100644
--- a/src/test/modules/oauth_validator/t/002_client.pl
+++ b/src/test/modules/oauth_validator/t/002_client.pl
@@ -122,6 +122,9 @@ test(
flags => ["--hang-forever"],
expected_stderr => qr/failed: timeout expired/);
+# Remove the timeout for later tests.
+$common_connstr = "$base_connstr oauth_issuer=$issuer oauth_client_id=myID";
+
# Test various misbehaviors of the client hook.
my @cases = (
{
--
2.34.1
[application/octet-stream] 0001-oauth-Fix-build-on-platforms-without-epoll-kqueue.patch (2.4K, ../../CAOYmi+mHu_5i2waPRzCiX906gg2HNR3OpSGR1Vz=faLrCoAWcg@mail.gmail.com/3-0001-oauth-Fix-build-on-platforms-without-epoll-kqueue.patch)
download | inline diff:
From a1da0ea92c77fdc59c4f14e3af3b5b0f93cfe4df Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Mon, 31 Mar 2025 16:07:33 -0700
Subject: [PATCH 1/2] oauth: Fix build on platforms without epoll/kqueue
register_socket() missed a variable declaration if neither
HAVE_SYS_EPOLL_H nor HAVE_SYS_EVENT_H was defined.
While we're fixing that, adjust the tests to check pg_config.h for one
of the multiplexer implementations, rather than assuming that Windows is
the only platform without support. (Christoph reported this on
hurd-amd64, an experimental Debian.)
Reported-by: Christoph Berg <[email protected]>
Discussion: https://postgr.es/m/Z-sPFl27Y0ZC-VBl%40msg.df7cb.de
---
src/interfaces/libpq/fe-auth-oauth-curl.c | 4 ++--
src/test/modules/oauth_validator/t/001_server.pl | 6 ++++--
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/interfaces/libpq/fe-auth-oauth-curl.c b/src/interfaces/libpq/fe-auth-oauth-curl.c
index 9e0e8a9f2be..cd9c0323bb6 100644
--- a/src/interfaces/libpq/fe-auth-oauth-curl.c
+++ b/src/interfaces/libpq/fe-auth-oauth-curl.c
@@ -1172,8 +1172,9 @@ static int
register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
void *socketp)
{
-#ifdef HAVE_SYS_EPOLL_H
struct async_ctx *actx = ctx;
+
+#ifdef HAVE_SYS_EPOLL_H
struct epoll_event ev = {0};
int res;
int op = EPOLL_CTL_ADD;
@@ -1231,7 +1232,6 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
return 0;
#endif
#ifdef HAVE_SYS_EVENT_H
- struct async_ctx *actx = ctx;
struct kevent ev[2] = {0};
struct kevent ev_out[2];
struct timespec timeout = {0};
diff --git a/src/test/modules/oauth_validator/t/001_server.pl b/src/test/modules/oauth_validator/t/001_server.pl
index 30295364ebd..d88994abc24 100644
--- a/src/test/modules/oauth_validator/t/001_server.pl
+++ b/src/test/modules/oauth_validator/t/001_server.pl
@@ -26,9 +26,11 @@ if (!$ENV{PG_TEST_EXTRA} || $ENV{PG_TEST_EXTRA} !~ /\boauth\b/)
'Potentially unsafe test oauth not enabled in PG_TEST_EXTRA';
}
-if ($windows_os)
+unless (check_pg_config("#define HAVE_SYS_EVENT_H 1")
+ or check_pg_config("#define HAVE_SYS_EPOLL_H 1"))
{
- plan skip_all => 'OAuth server-side tests are not supported on Windows';
+ plan skip_all =>
+ 'OAuth server-side tests are not supported on this platform';
}
if ($ENV{with_libcurl} ne 'yes')
--
2.34.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-04-03 18:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-03 19:50 ` Daniel Gustafsson <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Daniel Gustafsson @ 2025-04-03 19:50 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
> On 3 Apr 2025, at 20:02, Jacob Champion <[email protected]> wrote:
>
> On Tue, Mar 18, 2025 at 9:09 PM Thomas Munro <[email protected]> wrote:
>> All pushed (wasn't sure if Daniel was going to but once I got tangled
>> up in all that kqueue stuff he probably quite reasonably assumed that
>> I would :-)).
>
> Attached are two more followups, separate from the libcurl split:
> - 0001 is a patch originally proposed at [1]. Christoph pointed out
> that the build fails on a platform that tries to enable Curl without
> having either epoll() or kqueue(), due to a silly mistake I made in
> the #ifdefs.
> - 0002 should fix some timeouts in 002_client.pl reported by Andres on
> Discord. I allowed a short connect_timeout to propagate into tests
> which should not have it.
Thanks, both LGTM so pushed.
--
Daniel Gustafsson
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-03-19 04:17 ` Tom Lane <[email protected]>
2025-03-19 04:34 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
1 sibling, 2 replies; 194+ messages in thread
From: Tom Lane @ 2025-03-19 04:17 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Jacob Champion <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Andres Freund <[email protected]>; Daniel Gustafsson <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
Thomas Munro <[email protected]> writes:
> All pushed
You may have noticed it already, but indri reports that this
printf-like call isn't right:
fe-auth-oauth-curl.c:1392:49: error: data argument not used by format string [-Werror,-Wformat-extra-args]
1392 | actx_error(actx, "deleting kqueue timer: %m", timeout);
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^
fe-auth-oauth-curl.c:324:59: note: expanded from macro 'actx_error'
324 | appendPQExpBuffer(&(ACTX)->errbuf, libpq_gettext(FMT), ##__VA_ARGS__)
| ~~~ ^
"timeout" isn't being used anymore.
regards, tom lane
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
@ 2025-03-19 04:34 ` Thomas Munro <[email protected]>
2025-03-19 13:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Thomas Munro @ 2025-03-19 04:34 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Jacob Champion <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Andres Freund <[email protected]>; Daniel Gustafsson <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
On Wed, Mar 19, 2025 at 5:17 PM Tom Lane <[email protected]> wrote:
> fe-auth-oauth-curl.c:1392:49: error: data argument not used by format string [-Werror,-Wformat-extra-args]
> 1392 | actx_error(actx, "deleting kqueue timer: %m", timeout);
> | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^
> fe-auth-oauth-curl.c:324:59: note: expanded from macro 'actx_error'
> 324 | appendPQExpBuffer(&(ACTX)->errbuf, libpq_gettext(FMT), ##__VA_ARGS__)
> | ~~~ ^
>
> "timeout" isn't being used anymore.
Yeah. Thanks, fixed.
Now I'm wondering about teaching CI to fail on compiler warnings, ie
not just the special warnings task but also in the Mac etc builds.
The reason it doesn't is because it's sort of annoying to stop the
main tests because of a format string snafu, but we must be able to
put a new step at the end after all tests that scans the build logs
for warning and then raises the alarm...
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:34 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
@ 2025-03-19 13:46 ` Andres Freund <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Andres Freund @ 2025-03-19 13:46 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Tom Lane <[email protected]>; Jacob Champion <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Daniel Gustafsson <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
Hi,
On 2025-03-19 17:34:18 +1300, Thomas Munro wrote:
> On Wed, Mar 19, 2025 at 5:17 PM Tom Lane <[email protected]> wrote:
> > fe-auth-oauth-curl.c:1392:49: error: data argument not used by format string [-Werror,-Wformat-extra-args]
> > 1392 | actx_error(actx, "deleting kqueue timer: %m", timeout);
> > | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^
> > fe-auth-oauth-curl.c:324:59: note: expanded from macro 'actx_error'
> > 324 | appendPQExpBuffer(&(ACTX)->errbuf, libpq_gettext(FMT), ##__VA_ARGS__)
> > | ~~~ ^
> >
> > "timeout" isn't being used anymore.
>
> Yeah. Thanks, fixed.
>
> Now I'm wondering about teaching CI to fail on compiler warnings, ie
> not just the special warnings task but also in the Mac etc builds.
> The reason it doesn't is because it's sort of annoying to stop the
> main tests because of a format string snafu, but we must be able to
> put a new step at the end after all tests that scans the build logs
> for warning and then raises the alarm...
The best way would probably be to tee the output of the build to a log file
and then have a script at the end to check for errors in that.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
@ 2025-03-19 04:57 ` Tom Lane <[email protected]>
2025-03-19 13:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
1 sibling, 2 replies; 194+ messages in thread
From: Tom Lane @ 2025-03-19 04:57 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Jacob Champion <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Andres Freund <[email protected]>; Daniel Gustafsson <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
BTW, I was pretty seriously disheartened just now to realize that
this feature was implemented by making libpq depend on libcurl.
I'd misread the relevant commit messages to say that libcurl was
just being used as test infrastructure; but nope, it's a genuine
build and runtime dependency. I wonder how much big-picture
thinking went into that. I can see at least two objections:
* This represents a pretty large expansion of dependency footprint,
not just for us but for the umpteen hundred packages that depend on
libpq. libcurl alone maybe wouldn't be so bad, but have you looked
at libcurl's dependencies? On RHEL8,
$ ldd /usr/lib64/libcurl.so.4.5.0
linux-vdso.so.1 (0x00007fffd3075000)
libnghttp2.so.14 => /lib64/libnghttp2.so.14 (0x00007f992097a000)
libidn2.so.0 => /lib64/libidn2.so.0 (0x00007f992075c000)
libssh.so.4 => /lib64/libssh.so.4 (0x00007f99204ec000)
libpsl.so.5 => /lib64/libpsl.so.5 (0x00007f99202db000)
libssl.so.1.1 => /lib64/libssl.so.1.1 (0x00007f9920046000)
libcrypto.so.1.1 => /lib64/libcrypto.so.1.1 (0x00007f991fb5b000)
libgssapi_krb5.so.2 => /lib64/libgssapi_krb5.so.2 (0x00007f991f906000)
libkrb5.so.3 => /lib64/libkrb5.so.3 (0x00007f991f61b000)
libk5crypto.so.3 => /lib64/libk5crypto.so.3 (0x00007f991f404000)
libcom_err.so.2 => /lib64/libcom_err.so.2 (0x00007f991f200000)
libldap-2.4.so.2 => /lib64/libldap-2.4.so.2 (0x00007f991efb1000)
liblber-2.4.so.2 => /lib64/liblber-2.4.so.2 (0x00007f991eda1000)
libbrotlidec.so.1 => /lib64/libbrotlidec.so.1 (0x00007f991eb94000)
libz.so.1 => /lib64/libz.so.1 (0x00007f991e97c000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f991e75c000)
libc.so.6 => /lib64/libc.so.6 (0x00007f991e386000)
libunistring.so.2 => /lib64/libunistring.so.2 (0x00007f991e005000)
librt.so.1 => /lib64/librt.so.1 (0x00007f991ddfd000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9920e30000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f991dbf9000)
libkrb5support.so.0 => /lib64/libkrb5support.so.0 (0x00007f991d9e8000)
libkeyutils.so.1 => /lib64/libkeyutils.so.1 (0x00007f991d7e4000)
libresolv.so.2 => /lib64/libresolv.so.2 (0x00007f991d5cc000)
libsasl2.so.3 => /lib64/libsasl2.so.3 (0x00007f991d3ae000)
libm.so.6 => /lib64/libm.so.6 (0x00007f991d02c000)
libbrotlicommon.so.1 => /lib64/libbrotlicommon.so.1 (0x00007f991ce0b000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f991cbe0000)
libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007f991c9b7000)
libpcre2-8.so.0 => /lib64/libpcre2-8.so.0 (0x00007f991c733000)
* Given libcurl's very squishy portfolio:
libcurl is a free and easy-to-use client-side URL transfer library, supporting
FTP, FTPS, HTTP, HTTPS, SCP, SFTP, TFTP, TELNET, DICT, LDAP, LDAPS, FILE, IMAP,
SMTP, POP3 and RTSP. libcurl supports SSL certificates, HTTP POST, HTTP PUT,
FTP uploading, HTTP form based upload, proxies, cookies, user+password
authentication (Basic, Digest, NTLM, Negotiate, Kerberos4), file transfer
resume, http proxy tunneling and more.
it's not exactly hard to imagine them growing a desire to handle
"postgresql://" URLs, which they would surely do by invoking libpq.
Then we'll have circular build dependencies and circular runtime
dependencies, not to mention inter-library recursion at runtime.
This is not quite a hill that I wish to die on, but I will
flatly predict that we will regret this.
regards, tom lane
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
@ 2025-03-19 13:31 ` Bruce Momjian <[email protected]>
1 sibling, 0 replies; 194+ messages in thread
From: Bruce Momjian @ 2025-03-19 13:31 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Thomas Munro <[email protected]>; Jacob Champion <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Andres Freund <[email protected]>; Daniel Gustafsson <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
On Wed, Mar 19, 2025 at 12:57:29AM -0400, Tom Lane wrote:
> * Given libcurl's very squishy portfolio:
>
> libcurl is a free and easy-to-use client-side URL transfer library, supporting
> FTP, FTPS, HTTP, HTTPS, SCP, SFTP, TFTP, TELNET, DICT, LDAP, LDAPS, FILE, IMAP,
> SMTP, POP3 and RTSP. libcurl supports SSL certificates, HTTP POST, HTTP PUT,
> FTP uploading, HTTP form based upload, proxies, cookies, user+password
> authentication (Basic, Digest, NTLM, Negotiate, Kerberos4), file transfer
> resume, http proxy tunneling and more.
>
> it's not exactly hard to imagine them growing a desire to handle
> "postgresql://" URLs, which they would surely do by invoking libpq.
> Then we'll have circular build dependencies and circular runtime
> dependencies, not to mention inter-library recursion at runtime.
>
>
> This is not quite a hill that I wish to die on, but I will
> flatly predict that we will regret this.
I regularly see curl security fixes in my Debian updates, so there is a
security issue that any serious curl bug could also make Postgres
vulnerable. I might be willing to die on that hill.
--
Bruce Momjian <[email protected]> https://momjian.us
EDB https://enterprisedb.com
Do not let urgent matters crowd out time for investment in the future.
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
@ 2025-03-19 13:38 ` Daniel Gustafsson <[email protected]>
2025-03-19 20:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-19 21:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
1 sibling, 3 replies; 194+ messages in thread
From: Daniel Gustafsson @ 2025-03-19 13:38 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Thomas Munro <[email protected]>; Jacob Champion <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
> On 19 Mar 2025, at 05:57, Tom Lane <[email protected]> wrote:
>
> BTW, I was pretty seriously disheartened just now to realize that
> this feature was implemented by making libpq depend on libcurl.
> I'd misread the relevant commit messages to say that libcurl was
> just being used as test infrastructure; but nope, it's a genuine
> build and runtime dependency. I wonder how much big-picture
> thinking went into that.
A considerable amount.
libcurl is not a dependency for OAuth support in libpq, the support was
designed to be exensible such that clients can hook in their own flow
implementations. This part does not require libcurl. It is however a
dependency for the RFC 8628 implementation which is included when building with
--with-libcurl, this in order to ship something which can be used out of the
box (for actual connections *and* testing) without clients being forced to
provide their own implementation.
This obviously means that the RFC8628 part could be moved to contrib/, but I
fear we wouldn't make life easier for packagers by doing that.
> * Given libcurl's very squishy portfolio:
> ...
> it's not exactly hard to imagine them growing a desire to handle
> "postgresql://" URLs,
While there is no guarantee that such a pull request wont be submitted,
speaking as a (admittedly not very active at the moment) libcurl maintainer I
consider it highly unlikely that it would be accepted. A postgres connnection
does not fit into what libcurl/curl is and wants to be.
--
Daniel Gustafsson
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
@ 2025-03-19 20:11 ` Thomas Munro <[email protected]>
2 siblings, 0 replies; 194+ messages in thread
From: Thomas Munro @ 2025-03-19 20:11 UTC (permalink / raw)
To: Daniel Gustafsson <[email protected]>; +Cc: Tom Lane <[email protected]>; Jacob Champion <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
On Thu, Mar 20, 2025 at 2:38 AM Daniel Gustafsson <[email protected]> wrote:
> > On 19 Mar 2025, at 05:57, Tom Lane <[email protected]> wrote:
> >
> > BTW, I was pretty seriously disheartened just now to realize that
> > this feature was implemented by making libpq depend on libcurl.
> > I'd misread the relevant commit messages to say that libcurl was
> > just being used as test infrastructure; but nope, it's a genuine
> > build and runtime dependency. I wonder how much big-picture
> > thinking went into that.
>
> A considerable amount.
>
> libcurl is not a dependency for OAuth support in libpq, the support was
> designed to be exensible such that clients can hook in their own flow
> implementations. This part does not require libcurl. It is however a
> dependency for the RFC 8628 implementation which is included when building with
> --with-libcurl, this in order to ship something which can be used out of the
> box (for actual connections *and* testing) without clients being forced to
> provide their own implementation.
>
> This obviously means that the RFC8628 part could be moved to contrib/, but I
> fear we wouldn't make life easier for packagers by doing that.
How feasible/fragile/weird would it be to dlopen() it on demand?
Looks like it'd take ~20 function pointers:
U curl_easy_cleanup
U curl_easy_escape
U curl_easy_getinfo
U curl_easy_init
U curl_easy_setopt
U curl_easy_strerror
U curl_free
U curl_global_init
U curl_multi_add_handle
U curl_multi_cleanup
U curl_multi_info_read
U curl_multi_init
U curl_multi_remove_handle
U curl_multi_setopt
U curl_multi_socket_action
U curl_multi_socket_all
U curl_multi_strerror
U curl_slist_append
U curl_slist_free_all
U curl_version_info
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
@ 2025-03-19 21:03 ` Tom Lane <[email protected]>
2025-03-19 22:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2 siblings, 1 reply; 194+ messages in thread
From: Tom Lane @ 2025-03-19 21:03 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; Jacob Champion <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
Thomas Munro <[email protected]> writes:
> On Thu, Mar 20, 2025 at 2:38 AM Daniel Gustafsson <[email protected]> wrote:
>> On 19 Mar 2025, at 05:57, Tom Lane <[email protected]> wrote:
>>> BTW, I was pretty seriously disheartened just now to realize that
>>> this feature was implemented by making libpq depend on libcurl.
> How feasible/fragile/weird would it be to dlopen() it on demand?
FWIW, that would not really move the needle one bit so far as
my worries are concerned. What I'm unhappy about is the very
sizable expansion of our build dependency footprint as well
as the sizable expansion of the 'package requires' footprint.
The fact that the new dependencies are mostly indirect doesn't
soften that blow at all.
To address that (without finding some less kitchen-sink-y OAuth
implementation to depend on), we'd need to shove the whole thing
into a separately-built, separately-installable package.
What I expect is likely to happen is that packagers will try to do
that themselves to avoid the dependency bloat. AFAICT our current
setup will make that quite painful for them, and in any case I
don't believe it's work we should make them do. If they fail to
do that, the burden of the extra dependencies will fall on end
users. Either way, it's not going to make us look good.
regards, tom lane
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 21:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
@ 2025-03-19 22:02 ` Thomas Munro <[email protected]>
2025-03-19 22:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-19 22:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
0 siblings, 2 replies; 194+ messages in thread
From: Thomas Munro @ 2025-03-19 22:02 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; Jacob Champion <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
On Thu, Mar 20, 2025 at 10:04 AM Tom Lane <[email protected]> wrote:
> Thomas Munro <[email protected]> writes:
> > How feasible/fragile/weird would it be to dlopen() it on demand?
>
> FWIW, that would not really move the needle one bit so far as
> my worries are concerned. What I'm unhappy about is the very
> sizable expansion of our build dependency footprint as well
> as the sizable expansion of the 'package requires' footprint.
> The fact that the new dependencies are mostly indirect doesn't
> soften that blow at all.
>
> To address that (without finding some less kitchen-sink-y OAuth
> implementation to depend on), we'd need to shove the whole thing
> into a separately-built, separately-installable package.
>
> What I expect is likely to happen is that packagers will try to do
> that themselves to avoid the dependency bloat. AFAICT our current
> setup will make that quite painful for them, and in any case I
> don't believe it's work we should make them do. If they fail to
> do that, the burden of the extra dependencies will fall on end
> users. Either way, it's not going to make us look good.
It would increase the build dependencies, assuming a package
maintainer wants to enable as many features as possible, but it would
*not* increase the 'package requires' footprint, merely the 'package
suggests' footprint (as Debian calls it), and it's up to the user
whether they install suggested extra packages, no?
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 21:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 22:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
@ 2025-03-19 22:14 ` Thomas Munro <[email protected]>
1 sibling, 0 replies; 194+ messages in thread
From: Thomas Munro @ 2025-03-19 22:14 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; Jacob Champion <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
On Thu, Mar 20, 2025 at 11:02 AM Thomas Munro <[email protected]> wrote:
> On Thu, Mar 20, 2025 at 10:04 AM Tom Lane <[email protected]> wrote:
> > Thomas Munro <[email protected]> writes:
> > > How feasible/fragile/weird would it be to dlopen() it on demand?
. o O { There may also be security reasons to reject the idea, would
need to look into that... }
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 21:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 22:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
@ 2025-03-19 22:19 ` Tom Lane <[email protected]>
2025-03-19 22:28 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Tom Lane @ 2025-03-19 22:19 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; Jacob Champion <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
Thomas Munro <[email protected]> writes:
> It would increase the build dependencies, assuming a package
> maintainer wants to enable as many features as possible, but it would
> *not* increase the 'package requires' footprint, merely the 'package
> suggests' footprint (as Debian calls it), and it's up to the user
> whether they install suggested extra packages, no?
Maybe I'm confused, but what I saw was a hard dependency on libcurl,
as well as several of its dependencies:
$ ./configure --with-libcurl
...
$ make
...
$ ldd src/interfaces/libpq/libpq.so.5.18
linux-vdso.so.1 (0x00007ffc145fe000)
libcurl.so.4 => /lib64/libcurl.so.4 (0x00007f2c2fa36000)
libm.so.6 => /lib64/libm.so.6 (0x00007f2c2f95b000)
libc.so.6 => /lib64/libc.so.6 (0x00007f2c2f600000)
libnghttp2.so.14 => /lib64/libnghttp2.so.14 (0x00007f2c2f931000)
libidn2.so.0 => /lib64/libidn2.so.0 (0x00007f2c2f910000)
libssh.so.4 => /lib64/libssh.so.4 (0x00007f2c2f89b000)
libpsl.so.5 => /lib64/libpsl.so.5 (0x00007f2c2f885000)
libssl.so.3 => /lib64/libssl.so.3 (0x00007f2c2f51a000)
libcrypto.so.3 => /lib64/libcrypto.so.3 (0x00007f2c2f000000)
libgssapi_krb5.so.2 => /lib64/libgssapi_krb5.so.2 (0x00007f2c2f82f000)
libkrb5.so.3 => /lib64/libkrb5.so.3 (0x00007f2c2ef26000)
libk5crypto.so.3 => /lib64/libk5crypto.so.3 (0x00007f2c2f816000)
libcom_err.so.2 => /lib64/libcom_err.so.2 (0x00007f2c2f80d000)
libldap.so.2 => /lib64/libldap.so.2 (0x00007f2c2eebf000)
liblber.so.2 => /lib64/liblber.so.2 (0x00007f2c2eead000)
libbrotlidec.so.1 => /lib64/libbrotlidec.so.1 (0x00007f2c2ee9f000)
libz.so.1 => /lib64/libz.so.1 (0x00007f2c2ee85000)
/lib64/ld-linux-x86-64.so.2 (0x00007f2c2fb43000)
libunistring.so.2 => /lib64/libunistring.so.2 (0x00007f2c2ed00000)
libkrb5support.so.0 => /lib64/libkrb5support.so.0 (0x00007f2c2ecef000)
libkeyutils.so.1 => /lib64/libkeyutils.so.1 (0x00007f2c2ece8000)
libresolv.so.2 => /lib64/libresolv.so.2 (0x00007f2c2ecd4000)
libevent-2.1.so.7 => /lib64/libevent-2.1.so.7 (0x00007f2c2ec7b000)
libsasl2.so.3 => /lib64/libsasl2.so.3 (0x00007f2c2ec5b000)
libbrotlicommon.so.1 => /lib64/libbrotlicommon.so.1 (0x00007f2c2ec38000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f2c2ec0b000)
libcrypt.so.2 => /lib64/libcrypt.so.2 (0x00007f2c2ebd1000)
libpcre2-8.so.0 => /lib64/libpcre2-8.so.0 (0x00007f2c2eb35000)
I don't think that will be satisfied by 'package suggests'.
Even if it somehow manages to load, the result of trying to
use OAuth would be a segfault rather than any useful message.
regards, tom lane
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 21:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 22:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-19 22:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
@ 2025-03-19 22:28 ` Thomas Munro <[email protected]>
2025-03-19 23:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Thomas Munro @ 2025-03-19 22:28 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; Jacob Champion <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
On Thu, Mar 20, 2025 at 11:19 AM Tom Lane <[email protected]> wrote:
> Thomas Munro <[email protected]> writes:
> > It would increase the build dependencies, assuming a package
> > maintainer wants to enable as many features as possible, but it would
> > *not* increase the 'package requires' footprint, merely the 'package
> > suggests' footprint (as Debian calls it), and it's up to the user
> > whether they install suggested extra packages, no?
>
> Maybe I'm confused, but what I saw was a hard dependency on libcurl,
> as well as several of its dependencies:
> I don't think that will be satisfied by 'package suggests'.
> Even if it somehow manages to load, the result of trying to
> use OAuth would be a segfault rather than any useful message.
I was imagining that it would just error out if you try to use that
stuff and it fails to open libcurl. Then it's up to end users: if
they want to use libpq + OAuth, they have to install both libpq5 and
libcurl packages, and if they don't their connections will just fail,
presumably with some error message explaining why. Or something like
that...
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 21:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 22:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-19 22:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 22:28 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
@ 2025-03-19 23:11 ` Bruce Momjian <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Bruce Momjian @ 2025-03-19 23:11 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Tom Lane <[email protected]>; Daniel Gustafsson <[email protected]>; Jacob Champion <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
On Thu, Mar 20, 2025 at 11:28:50AM +1300, Thomas Munro wrote:
> On Thu, Mar 20, 2025 at 11:19 AM Tom Lane <[email protected]> wrote:
> > Thomas Munro <[email protected]> writes:
> > > It would increase the build dependencies, assuming a package
> > > maintainer wants to enable as many features as possible, but it would
> > > *not* increase the 'package requires' footprint, merely the 'package
> > > suggests' footprint (as Debian calls it), and it's up to the user
> > > whether they install suggested extra packages, no?
> >
> > Maybe I'm confused, but what I saw was a hard dependency on libcurl,
> > as well as several of its dependencies:
>
> > I don't think that will be satisfied by 'package suggests'.
> > Even if it somehow manages to load, the result of trying to
> > use OAuth would be a segfault rather than any useful message.
>
> I was imagining that it would just error out if you try to use that
> stuff and it fails to open libcurl. Then it's up to end users: if
> they want to use libpq + OAuth, they have to install both libpq5 and
> libcurl packages, and if they don't their connections will just fail,
> presumably with some error message explaining why. Or something like
> that...
Am I understanding that curl is being used just to honor the RFC and it
is only for testing? That seems like a small reason to add such a
dependency.
--
Bruce Momjian <[email protected]> https://momjian.us
EDB https://enterprisedb.com
Do not let urgent matters crowd out time for investment in the future.
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
@ 2025-03-19 23:59 ` Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2 siblings, 1 reply; 194+ messages in thread
From: Bruce Momjian @ 2025-03-19 23:59 UTC (permalink / raw)
To: Daniel Gustafsson <[email protected]>; +Cc: Tom Lane <[email protected]>; Thomas Munro <[email protected]>; Jacob Champion <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
On Wed, Mar 19, 2025 at 02:38:08PM +0100, Daniel Gustafsson wrote:
> > On 19 Mar 2025, at 05:57, Tom Lane <[email protected]> wrote:
> >
> > BTW, I was pretty seriously disheartened just now to realize that
> > this feature was implemented by making libpq depend on libcurl.
> > I'd misread the relevant commit messages to say that libcurl was
> > just being used as test infrastructure; but nope, it's a genuine
> > build and runtime dependency. I wonder how much big-picture
> > thinking went into that.
>
> A considerable amount.
>
> libcurl is not a dependency for OAuth support in libpq, the support was
> designed to be exensible such that clients can hook in their own flow
> implementations. This part does not require libcurl. It is however a
> dependency for the RFC 8628 implementation which is included when building with
> --with-libcurl, this in order to ship something which can be used out of the
> box (for actual connections *and* testing) without clients being forced to
> provide their own implementation.
>
> This obviously means that the RFC8628 part could be moved to contrib/, but I
> fear we wouldn't make life easier for packagers by doing that.
I see it now ---- without having RFC 8628 built into the server, clients
have to implement it. Do we know what percentage would need to do that?
The spec:
https://datatracker.ietf.org/doc/html/rfc8628
Do we think packagers will use the --with-libcurl configure option?
It does kind of make sense for curl to handle OAUTH since curl has to
simulate a browser. I assume we can't call a shell to invoke curl from
the command line.
--
Bruce Momjian <[email protected]> https://momjian.us
EDB https://enterprisedb.com
Do not let urgent matters crowd out time for investment in the future.
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
@ 2025-03-20 20:33 ` Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-03-20 20:33 UTC (permalink / raw)
To: Bruce Momjian <[email protected]>; pgsql-hackers; Tom Lane <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>
Hi all,
With the understanding that the patchset is no longer just "my" baby...
= Dependencies =
I like seeing risk/reward discussions. I agonized over the choice of
HTTP dependency, and I transitioned from an "easier" OAuth library
over to Curl early on because of the same tradeoffs.
That said... Tom, I think the dependency list you've presented is not
quite fair, because it doesn't show what libpq's dependency list was
before adding Curl. From your email, and my local Rocky 9 install, I
think these are the net-new dependencies that we (and packagers) need
to worry about:
libcurl.so.4
libnghttp2.so.14
libidn2.so.0
libssh.so.4
libpsl.so.5
libunistring.so.2
libbrotlidec.so.1
libbrotlicommon.so.1
libz.so.1
That's more than I'd like, to be perfectly honest. I'm least happy
about libssh, because we're not using SFTP but we have to pay for it.
And the Deb-alikes add librtmp, which I'm not thrilled about either.
The rest are, IMO, natural dependencies of a mature HTTP client: the
HTTP/1 and HTTP/2 engines, Punycode, the Public Suffix List, UTF
handling, and common response compression types. Those are kind of
part and parcel of communicating on the web. (If we find an HTTP
client that does all those things itself, awesome, but then we have to
ask how well they did it.)
So one question for the collective is -- putting Curl itself aside --
is having a basic-but-usable OAuth flow, out of the box, worth the
costs of a generic HTTP client? A non-trivial footprint *will* be
there, whether it's one library or several, whether we delay-load it
or not, whether we have the unused SFTP/RTMP dependencies or not. But
we could still find ways to reduce that cost for people who aren't
using it, if necessary.
= Asides =
I would also like to point out: End users opt into this by
preregistering a client ID with an OAuth issuer ID, then providing
that pair of IDs in the connection string. We will not just start
crawling the web because a server tells us to. I don't want to
downplay the additional risk of having it in the address space, but
the design goal is that vulnerabilities in the HTTP logic should not
affect users who have not explicitly consented to the use of OAuth.
There were some other questions/statements made upthread that I want
to clarify too:
On Wed, Mar 19, 2025 at 4:11 PM Bruce Momjian <[email protected]> wrote:
> Am I understanding that curl is being used just to honor the RFC and it
> is only for testing?
No. (I see you found it later, but to state clearly for the record:
it's not just for testing.) libcurl is used for the Device
Authorization flow implementation. You don't have to use Device
Authorization to use OAuth, but we don't provide any alternative flows
in-tree; you'd have to use the libpq API to insert your own flow.
> I see it now ---- without having RFC 8628 built into the server,
(libpq, not the server. We do not ship server-side plugins at all, yet.)
> clients
> have to implement it. Do we know what percentage would need to do that?
For version 1 of the feature, Device Authorization is the only option
for our utilities (psql et al). I can't really speculate on
percentages; it depends on what percentage want to use OAuth and don't
like (or can't use) our builtin flow. Obviously the percentage goes up
to 100% if we don't provide one. Plus we lose significant testability,
plus no one can use it from psql.
> Do we think packagers will use the --with-libcurl configure option?
Well, hopefully, yes. The tradeoffs of the builtin flow were chosen
explicitly so that existing clients could use it with minimal-to-no
code changes.
Thanks!
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-03-20 20:50 ` Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Bruce Momjian @ 2025-03-20 20:50 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: pgsql-hackers; Tom Lane <[email protected]>; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>
On Thu, Mar 20, 2025 at 01:33:26PM -0700, Jacob Champion wrote:
> That's more than I'd like, to be perfectly honest. I'm least happy
> about libssh, because we're not using SFTP but we have to pay for it.
> And the Deb-alikes add librtmp, which I'm not thrilled about either.
>
> The rest are, IMO, natural dependencies of a mature HTTP client: the
> HTTP/1 and HTTP/2 engines, Punycode, the Public Suffix List, UTF
> handling, and common response compression types. Those are kind of
> part and parcel of communicating on the web. (If we find an HTTP
> client that does all those things itself, awesome, but then we have to
> ask how well they did it.)
>
> So one question for the collective is -- putting Curl itself aside --
> is having a basic-but-usable OAuth flow, out of the box, worth the
> costs of a generic HTTP client? A non-trivial footprint *will* be
> there, whether it's one library or several, whether we delay-load it
> or not, whether we have the unused SFTP/RTMP dependencies or not. But
> we could still find ways to reduce that cost for people who aren't
> using it, if necessary.
One observation is that security scanning tools are going to see the
curl dependency and look at any CSVs related to them and ask us, whether
they are using OAUTH or not.
--
Bruce Momjian <[email protected]> https://momjian.us
EDB https://enterprisedb.com
Do not let urgent matters crowd out time for investment in the future.
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
@ 2025-03-20 21:08 ` Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Tom Lane @ 2025-03-20 21:08 UTC (permalink / raw)
To: Bruce Momjian <[email protected]>; +Cc: Jacob Champion <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>
Bruce Momjian <[email protected]> writes:
> On Thu, Mar 20, 2025 at 01:33:26PM -0700, Jacob Champion wrote:
>> So one question for the collective is -- putting Curl itself aside --
>> is having a basic-but-usable OAuth flow, out of the box, worth the
>> costs of a generic HTTP client?
> One observation is that security scanning tools are going to see the
> curl dependency and look at any CSVs related to them and ask us, whether
> they are using OAUTH or not.
Yes. Also, none of this has addressed my complaint about the extent
of the build and install dependencies. Yes, simply not selecting
--with-libcurl removes the problem ... but most packagers are under
very heavy pressure to enable all features of a package.
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
@ 2025-03-20 23:26 ` Andres Freund <[email protected]>
2025-03-21 12:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrew Dunstan <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
0 siblings, 2 replies; 194+ messages in thread
From: Andres Freund @ 2025-03-20 23:26 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Bruce Momjian <[email protected]>; Jacob Champion <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>
Hi,
On 2025-03-20 17:08:54 -0400, Tom Lane wrote:
> Bruce Momjian <[email protected]> writes:
> > On Thu, Mar 20, 2025 at 01:33:26PM -0700, Jacob Champion wrote:
> >> So one question for the collective is -- putting Curl itself aside --
> >> is having a basic-but-usable OAuth flow, out of the box, worth the
> >> costs of a generic HTTP client?
>
> > One observation is that security scanning tools are going to see the
> > curl dependency and look at any CSVs related to them and ask us, whether
> > they are using OAUTH or not.
>
> Yes. Also, none of this has addressed my complaint about the extent
> of the build and install dependencies. Yes, simply not selecting
> --with-libcurl removes the problem ... but most packagers are under
> very heavy pressure to enable all features of a package.
How about we provide the current libpq.so without linking to curl and also a
libpq-oauth.so that has curl support? If we do it right libpq-oauth.so would
itself link to libpq.so, making libpq-oauth.so a fairly small library.
That way packagers can split libpq-oauth.so into a separate package, while
still just building once.
That'd be a bit of work on the buildsystem side, but it seems doable.
> From what's been said here, only a small minority of users are likely
> to have any interest in this feature. So my answer to "is it worth
> the cost" is no, and would be no even if I had a lower estimate of
> the costs.
I think this is likely going to be rather widely used, way more widely than
e.g. kerberos or ldap support in libpq. My understanding is that there's a
fair bit of pressure in lots of companies to centralize authentication towards
centralized systems, even for server applications.
> I don't have any problem with making a solution available to those
> users who want it --- but I really do NOT want this to be part of
> stock libpq nor done as part of the core Postgres build. I do not
> think that the costs of that have been fully accounted for, especially
> not the fact that almost all of those costs fall on people other than
> us.
I am on board with not having it as part of stock libpq, but I don't see what
we gain by not building it as part of postgres (if the dependencies are
available, of course).
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
@ 2025-03-21 12:40 ` Andrew Dunstan <[email protected]>
2025-03-21 18:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Andrew Dunstan @ 2025-03-21 12:40 UTC (permalink / raw)
To: Andres Freund <[email protected]>; Tom Lane <[email protected]>; +Cc: Bruce Momjian <[email protected]>; Jacob Champion <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>
On 2025-03-20 Th 7:26 PM, Andres Freund wrote:
> Hi,
>
> On 2025-03-20 17:08:54 -0400, Tom Lane wrote:
>> Bruce Momjian <[email protected]> writes:
>>> On Thu, Mar 20, 2025 at 01:33:26PM -0700, Jacob Champion wrote:
>>>> So one question for the collective is -- putting Curl itself aside --
>>>> is having a basic-but-usable OAuth flow, out of the box, worth the
>>>> costs of a generic HTTP client?
>>> One observation is that security scanning tools are going to see the
>>> curl dependency and look at any CSVs related to them and ask us, whether
>>> they are using OAUTH or not.
>> Yes. Also, none of this has addressed my complaint about the extent
>> of the build and install dependencies. Yes, simply not selecting
>> --with-libcurl removes the problem ... but most packagers are under
>> very heavy pressure to enable all features of a package.
> How about we provide the current libpq.so without linking to curl and also a
> libpq-oauth.so that has curl support? If we do it right libpq-oauth.so would
> itself link to libpq.so, making libpq-oauth.so a fairly small library.
>
> That way packagers can split libpq-oauth.so into a separate package, while
> still just building once.
>
> That'd be a bit of work on the buildsystem side, but it seems doable.
>
That certainly seems worth exploring.
>> From what's been said here, only a small minority of users are likely
>> to have any interest in this feature. So my answer to "is it worth
>> the cost" is no, and would be no even if I had a lower estimate of
>> the costs.
> I think this is likely going to be rather widely used, way more widely than
> e.g. kerberos or ldap support in libpq. My understanding is that there's a
> fair bit of pressure in lots of companies to centralize authentication towards
> centralized systems, even for server applications.
Indeed. There is still work to do on OAUTH2 but the demand you mention
is just going to keep increasing.
>
>
>> I don't have any problem with making a solution available to those
>> users who want it --- but I really do NOT want this to be part of
>> stock libpq nor done as part of the core Postgres build. I do not
>> think that the costs of that have been fully accounted for, especially
>> not the fact that almost all of those costs fall on people other than
>> us.
> I am on board with not having it as part of stock libpq, but I don't see what
> we gain by not building it as part of postgres (if the dependencies are
> available, of course).
>
+1.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-21 12:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrew Dunstan <[email protected]>
@ 2025-03-21 18:02 ` Daniel Gustafsson <[email protected]>
2025-03-26 19:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Daniel Gustafsson @ 2025-03-21 18:02 UTC (permalink / raw)
To: Andrew Dunstan <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; Jacob Champion <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>
> On 21 Mar 2025, at 13:40, Andrew Dunstan <[email protected]> wrote:
> On 2025-03-20 Th 7:26 PM, Andres Freund wrote:
>> How about we provide the current libpq.so without linking to curl and also a
>> libpq-oauth.so that has curl support? If we do it right libpq-oauth.so would
>> itself link to libpq.so, making libpq-oauth.so a fairly small library.
>>
>> That way packagers can split libpq-oauth.so into a separate package, while
>> still just building once.
>>
>> That'd be a bit of work on the buildsystem side, but it seems doable.
>
> That certainly seems worth exploring.
This is being worked on.
--
Daniel Gustafsson
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-21 12:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrew Dunstan <[email protected]>
2025-03-21 18:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
@ 2025-03-26 19:09 ` Jacob Champion <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2025-03-26 19:09 UTC (permalink / raw)
To: Daniel Gustafsson <[email protected]>; +Cc: Andrew Dunstan <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>
On Fri, Mar 21, 2025 at 11:02 AM Daniel Gustafsson <[email protected]> wrote:
> >> How about we provide the current libpq.so without linking to curl and also a
> >> libpq-oauth.so that has curl support? If we do it right libpq-oauth.so would
> >> itself link to libpq.so, making libpq-oauth.so a fairly small library.
> >>
> >> That way packagers can split libpq-oauth.so into a separate package, while
> >> still just building once.
> >>
> >> That'd be a bit of work on the buildsystem side, but it seems doable.
> >
> > That certainly seems worth exploring.
>
> This is being worked on.
Attached is a proof of concept, with code from Daniel and myself,
which passes the CI as a starting point.
Roughly speaking, some things to debate are
- the module API itself
- how much to duplicate from libpq vs how much to export
- is this even what you had in mind
libpq-oauth.so is dlopen'd when needed. If it's not found or it
doesn't have the right symbols, builtin OAuth will not happen. Right
now we have an SO version of 1; maybe we want to remove the SO version
entirely to better indicate that it shouldn't be linked?
Two symbols are exported for the async authentication callbacks. Since
the module understands PGconn internals, maybe we could simplify this
to a single callback that manipulates the connection directly.
To keep the diff small to start, the current patch probably exports
too much. I think appendPQExpBufferVA makes sense, considering we
export much of the PQExpBuffer API already, but I imagine we won't
want to expose the pg_g_threadlock symbol. (libpq could maybe push
that pointer into the libpq-oauth module at load time, instead of
having the module pull it.) And we could probably go either way with
the PQauthDataHook; I prefer having a getter and setter for future
flexibility, but it would be simpler to just export the hook directly.
The following functions are duplicated from libpq:
- libpq_block_sigpipe
- libpq_reset_sigpipe
- libpq_binddomain
- libpq_[n]gettext
- libpq_append_conn_error
- oauth_unsafe_debugging_enabled
Those don't seem too bad to me, though maybe there's a good way to
deduplicate. But i18n needs further work. It builds right now, but I
don't think it works yet.
WDYT?
Thanks,
--Jacob
Attachments:
[application/octet-stream] 0001-WIP-split-Device-Authorization-flow-into-dlopen-d-mo.patch (20.8K, ../../CAOYmi+=PkcF8DRw49Jp-9AZDobahOHwnH2p0snYPsv94x==3oA@mail.gmail.com/2-0001-WIP-split-Device-Authorization-flow-into-dlopen-d-mo.patch)
download | inline diff:
From 47fc34de68fe61b796f532f755a17331dff111e3 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Wed, 26 Mar 2025 10:55:28 -0700
Subject: [PATCH] WIP: split Device Authorization flow into dlopen'd module
See notes on mailing list.
Co-authored-by: Daniel Gustafsson <[email protected]>
---
meson.build | 12 +-
src/interfaces/Makefile | 9 +
src/interfaces/libpq-oauth/Makefile | 53 +++++
src/interfaces/libpq-oauth/exports.txt | 3 +
.../fe-auth-oauth-curl.c | 187 +++++++++++++++++-
.../libpq-oauth/fe-auth-oauth-curl.h | 23 +++
src/interfaces/libpq-oauth/meson.build | 64 ++++++
src/interfaces/libpq-oauth/po/meson.build | 3 +
src/interfaces/libpq/Makefile | 4 -
src/interfaces/libpq/exports.txt | 3 +
src/interfaces/libpq/fe-auth-oauth.c | 52 ++++-
src/interfaces/libpq/fe-auth-oauth.h | 4 +-
src/interfaces/libpq/fe-auth.h | 3 -
src/interfaces/libpq/libpq-fe.h | 1 +
src/interfaces/libpq/meson.build | 4 -
15 files changed, 389 insertions(+), 36 deletions(-)
create mode 100644 src/interfaces/libpq-oauth/Makefile
create mode 100644 src/interfaces/libpq-oauth/exports.txt
rename src/interfaces/{libpq => libpq-oauth}/fe-auth-oauth-curl.c (94%)
create mode 100644 src/interfaces/libpq-oauth/fe-auth-oauth-curl.h
create mode 100644 src/interfaces/libpq-oauth/meson.build
create mode 100644 src/interfaces/libpq-oauth/po/meson.build
diff --git a/meson.build b/meson.build
index 7cf518a2765..69e91529259 100644
--- a/meson.build
+++ b/meson.build
@@ -107,6 +107,7 @@ os_deps = []
backend_both_deps = []
backend_deps = []
libpq_deps = []
+libpq_oauth_deps = []
pg_sysroot = ''
@@ -3136,17 +3137,18 @@ libpq_deps += [
gssapi,
ldap_r,
- # XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
- # during gss_acquire_cred(). This is possibly related to Curl's Heimdal
- # dependency on that platform?
- libcurl,
libintl,
ssl,
]
+libpq_oauth_deps += [
+ libcurl,
+]
+
subdir('src/interfaces/libpq')
-# fe_utils depends on libpq
+# fe_utils and libpq-oauth depends on libpq
subdir('src/fe_utils')
+subdir('src/interfaces/libpq-oauth')
# for frontend binaries
frontend_code = declare_dependency(
diff --git a/src/interfaces/Makefile b/src/interfaces/Makefile
index 7d56b29d28f..322a498823d 100644
--- a/src/interfaces/Makefile
+++ b/src/interfaces/Makefile
@@ -14,7 +14,16 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = libpq ecpg
+ifeq ($(with_libcurl), yes)
+SUBDIRS += libpq-oauth
+endif
+
$(recurse)
all-ecpg-recurse: all-libpq-recurse
install-ecpg-recurse: install-libpq-recurse
+
+ifeq ($(with_libcurl), yes)
+all-libpq-oauth-recurse: all-libpq-recurse
+install-libpq-oauth-recurse: install-libpq-recurse
+endif
diff --git a/src/interfaces/libpq-oauth/Makefile b/src/interfaces/libpq-oauth/Makefile
new file mode 100644
index 00000000000..d623a4157e6
--- /dev/null
+++ b/src/interfaces/libpq-oauth/Makefile
@@ -0,0 +1,53 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for libpq-oauth
+#
+# Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/interfaces/libpq-oauth/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/interfaces/libpq-oauth
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+PGFILEDESC = "libpq-oauth - device authorization oauth support"
+NAME = pq-oauth
+SO_MAJOR_VERSION = 1
+SO_MINOR_VERSION = $(MAJORVERSION)
+
+override CPPFLAGS := -I$(libpq_srcdir) -I$(top_builddir)/src/port $(CPPFLAGS)
+
+OBJS = \
+ $(WIN32RES) \
+ fe-auth-oauth-curl.o
+
+SHLIB_LINK_INTERNAL = $(libpq_pgport_shlib)
+SHLIB_LINK = -lcurl
+SHLIB_PREREQS = submake-libpq
+
+SHLIB_EXPORTS = exports.txt
+
+PKG_CONFIG_REQUIRES_PRIVATE = libpq
+#
+# Make dependencies on pg_config_paths.h visible in all builds.
+fe-auth-oauth-curl.o: fe-auth-oauth-curl.c $(top_builddir)/src/port/pg_config_paths.h
+
+$(top_builddir)/src/port/pg_config_paths.h:
+ $(MAKE) -C $(top_builddir)/src/port pg_config_paths.h
+
+all: all-lib
+
+# Shared library stuff
+include $(top_srcdir)/src/Makefile.shlib
+
+install: all installdirs install-lib
+
+installdirs: installdirs-lib
+
+uninstall: uninstall-lib
+
+clean distclean: clean-lib
+ rm -f $(OBJS)
diff --git a/src/interfaces/libpq-oauth/exports.txt b/src/interfaces/libpq-oauth/exports.txt
new file mode 100644
index 00000000000..ac9333763c4
--- /dev/null
+++ b/src/interfaces/libpq-oauth/exports.txt
@@ -0,0 +1,3 @@
+# src/interfaces/libpq-oauth/exports.txt
+pg_fe_run_oauth_flow 1
+pg_fe_cleanup_oauth_flow 2
diff --git a/src/interfaces/libpq/fe-auth-oauth-curl.c b/src/interfaces/libpq-oauth/fe-auth-oauth-curl.c
similarity index 94%
rename from src/interfaces/libpq/fe-auth-oauth-curl.c
rename to src/interfaces/libpq-oauth/fe-auth-oauth-curl.c
index 9e0e8a9f2be..556e436ee93 100644
--- a/src/interfaces/libpq/fe-auth-oauth-curl.c
+++ b/src/interfaces/libpq-oauth/fe-auth-oauth-curl.c
@@ -29,8 +29,10 @@
#include "common/jsonapi.h"
#include "fe-auth.h"
#include "fe-auth-oauth.h"
+#include "fe-auth-oauth-curl.h"
#include "libpq-int.h"
#include "mb/pg_wchar.h"
+#include "pg_config_paths.h"
/*
* It's generally prudent to set a maximum response size to buffer in memory,
@@ -230,6 +232,173 @@ struct async_ctx
bool debugging; /* can we give unsafe developer assistance? */
};
+#ifdef ENABLE_NLS
+
+static void
+libpq_binddomain(void)
+{
+ /*
+ * At least on Windows, there are gettext implementations that fail if
+ * multiple threads call bindtextdomain() concurrently. Use a mutex and
+ * flag variable to ensure that we call it just once per process. It is
+ * not known that similar bugs exist on non-Windows platforms, but we
+ * might as well do it the same way everywhere.
+ */
+ static volatile bool already_bound = false;
+ static pthread_mutex_t binddomain_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+ if (!already_bound)
+ {
+ /* bindtextdomain() does not preserve errno */
+#ifdef WIN32
+ int save_errno = GetLastError();
+#else
+ int save_errno = errno;
+#endif
+
+ (void) pthread_mutex_lock(&binddomain_mutex);
+
+ if (!already_bound)
+ {
+ const char *ldir;
+
+ /*
+ * No relocatable lookup here because the calling executable could
+ * be anywhere
+ */
+ ldir = getenv("PGLOCALEDIR");
+ if (!ldir)
+ ldir = LOCALEDIR;
+ bindtextdomain(PG_TEXTDOMAIN("libpq"), ldir);
+ already_bound = true;
+ }
+
+ (void) pthread_mutex_unlock(&binddomain_mutex);
+
+#ifdef WIN32
+ SetLastError(save_errno);
+#else
+ errno = save_errno;
+#endif
+ }
+}
+
+char *
+libpq_gettext(const char *msgid)
+{
+ libpq_binddomain();
+ return dgettext(PG_TEXTDOMAIN("libpq"), msgid);
+}
+
+char *
+libpq_ngettext(const char *msgid, const char *msgid_plural, unsigned long n)
+{
+ libpq_binddomain();
+ return dngettext(PG_TEXTDOMAIN("libpq"), msgid, msgid_plural, n);
+}
+
+#endif /* ENABLE_NLS */
+
+static void __libpq_append_conn_error(PGconn *conn, const char *fmt,...) pg_attribute_printf(2, 3);
+
+/*
+ * Append a formatted string to the error message buffer of the given
+ * connection, after translating it. A newline is automatically appended; the
+ * format should not end with a newline.
+ */
+static void
+__libpq_append_conn_error(PGconn *conn, const char *fmt,...)
+{
+ int save_errno = errno;
+ bool done;
+ va_list args;
+
+ Assert(fmt[strlen(fmt) - 1] != '\n');
+
+ if (PQExpBufferBroken(&conn->errorMessage))
+ return; /* already failed */
+
+ /* Loop in case we have to retry after enlarging the buffer. */
+ do
+ {
+ errno = save_errno;
+ va_start(args, fmt);
+ done = appendPQExpBufferVA(&conn->errorMessage, libpq_gettext(fmt), args);
+ va_end(args);
+ } while (!done);
+
+ appendPQExpBufferChar(&conn->errorMessage, '\n');
+}
+
+/*
+ * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
+ */
+static bool
+__oauth_unsafe_debugging_enabled(void)
+{
+ const char *env = getenv("PGOAUTHDEBUG");
+
+ return (env && strcmp(env, "UNSAFE") == 0);
+}
+
+static int
+__pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
+{
+ sigset_t sigpipe_sigset;
+ sigset_t sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ /* Block SIGPIPE and save previous mask for later reset */
+ SOCK_ERRNO_SET(pthread_sigmask(SIG_BLOCK, &sigpipe_sigset, osigset));
+ if (SOCK_ERRNO)
+ return -1;
+
+ /* We can have a pending SIGPIPE only if it was blocked before */
+ if (sigismember(osigset, SIGPIPE))
+ {
+ /* Is there a pending SIGPIPE? */
+ if (sigpending(&sigset) != 0)
+ return -1;
+
+ if (sigismember(&sigset, SIGPIPE))
+ *sigpipe_pending = true;
+ else
+ *sigpipe_pending = false;
+ }
+ else
+ *sigpipe_pending = false;
+
+ return 0;
+}
+static void
+__pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
+{
+ int save_errno = SOCK_ERRNO;
+ int signo;
+ sigset_t sigset;
+
+ /* Clear SIGPIPE only if none was pending */
+ if (got_epipe && !sigpipe_pending)
+ {
+ if (sigpending(&sigset) == 0 &&
+ sigismember(&sigset, SIGPIPE))
+ {
+ sigset_t sigpipe_sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ sigwait(&sigpipe_sigset, &signo);
+ }
+ }
+
+ /* Restore saved block mask */
+ pthread_sigmask(SIG_SETMASK, osigset, NULL);
+
+ SOCK_ERRNO_SET(save_errno);
+}
/*
* Tears down the Curl handles and frees the async_ctx.
*/
@@ -252,7 +421,7 @@ free_async_ctx(PGconn *conn, struct async_ctx *actx)
CURLMcode err = curl_multi_remove_handle(actx->curlm, actx->curl);
if (err)
- libpq_append_conn_error(conn,
+ __libpq_append_conn_error(conn,
"libcurl easy handle removal failed: %s",
curl_multi_strerror(err));
}
@@ -272,7 +441,7 @@ free_async_ctx(PGconn *conn, struct async_ctx *actx)
CURLMcode err = curl_multi_cleanup(actx->curlm);
if (err)
- libpq_append_conn_error(conn,
+ __libpq_append_conn_error(conn,
"libcurl multi handle cleanup failed: %s",
curl_multi_strerror(err));
}
@@ -2556,7 +2725,7 @@ initialize_curl(PGconn *conn)
goto done;
else if (init_successful == PG_BOOL_NO)
{
- libpq_append_conn_error(conn,
+ __libpq_append_conn_error(conn,
"curl_global_init previously failed during OAuth setup");
goto done;
}
@@ -2575,7 +2744,7 @@ initialize_curl(PGconn *conn)
*/
if (curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_WIN32) != CURLE_OK)
{
- libpq_append_conn_error(conn,
+ __libpq_append_conn_error(conn,
"curl_global_init failed during OAuth setup");
init_successful = PG_BOOL_NO;
goto done;
@@ -2597,7 +2766,7 @@ initialize_curl(PGconn *conn)
* In a downgrade situation, the damage is already done. Curl global
* state may be corrupted. Be noisy.
*/
- libpq_append_conn_error(conn, "libcurl is no longer thread-safe\n"
+ __libpq_append_conn_error(conn, "libcurl is no longer thread-safe\n"
"\tCurl initialization was reported thread-safe when libpq\n"
"\twas compiled, but the currently installed version of\n"
"\tlibcurl reports that it is not. Recompile libpq against\n"
@@ -2649,7 +2818,7 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
actx = calloc(1, sizeof(*actx));
if (!actx)
{
- libpq_append_conn_error(conn, "out of memory");
+ __libpq_append_conn_error(conn, "out of memory");
return PGRES_POLLING_FAILED;
}
@@ -2657,7 +2826,7 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
actx->timerfd = -1;
/* Should we enable unsafe features? */
- actx->debugging = oauth_unsafe_debugging_enabled();
+ actx->debugging = __oauth_unsafe_debugging_enabled();
state->async_ctx = actx;
@@ -2895,7 +3064,7 @@ pg_fe_run_oauth_flow(PGconn *conn)
* difficult corner case to exercise in practice, and unfortunately it's
* not really clear whether it's necessary in all cases.
*/
- masked = (pq_block_sigpipe(&osigset, &sigpipe_pending) == 0);
+ masked = (__pq_block_sigpipe(&osigset, &sigpipe_pending) == 0);
#endif
result = pg_fe_run_oauth_flow_impl(conn);
@@ -2907,7 +3076,7 @@ pg_fe_run_oauth_flow(PGconn *conn)
* Undo the SIGPIPE mask. Assume we may have gotten EPIPE (we have no
* way of knowing at this level).
*/
- pq_reset_sigpipe(&osigset, sigpipe_pending, true /* EPIPE, maybe */ );
+ __pq_reset_sigpipe(&osigset, sigpipe_pending, true /* EPIPE, maybe */ );
}
#endif
diff --git a/src/interfaces/libpq-oauth/fe-auth-oauth-curl.h b/src/interfaces/libpq-oauth/fe-auth-oauth-curl.h
new file mode 100644
index 00000000000..907f360d9d1
--- /dev/null
+++ b/src/interfaces/libpq-oauth/fe-auth-oauth-curl.h
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-oauth-curl.h
+ *
+ * Definitions for OAuth Device Authorization module
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/fe-auth-oauth-curl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FE_AUTH_OAUTH_CURL_H
+#define FE_AUTH_OAUTH_CURL_H
+
+#include "libpq-fe.h"
+
+extern PGDLLEXPORT PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+extern PGDLLEXPORT void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+#endif /* FE_AUTH_OAUTH_CURL_H */
diff --git a/src/interfaces/libpq-oauth/meson.build b/src/interfaces/libpq-oauth/meson.build
new file mode 100644
index 00000000000..bd348a0afc4
--- /dev/null
+++ b/src/interfaces/libpq-oauth/meson.build
@@ -0,0 +1,64 @@
+# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+
+if not libcurl.found() or host_system == 'windows'
+ subdir_done()
+endif
+
+libpq_sources = files(
+ 'fe-auth-oauth-curl.c',
+)
+libpq_so_sources = [] # for shared lib, in addition to the above
+
+export_file = custom_target('libpq-oauth.exports',
+ kwargs: gen_export_kwargs,
+)
+
+# port needs to be in include path due to pthread-win32.h
+libpq_oauth_inc = include_directories('.', '../libpq', '../../port')
+libpq_c_args = ['-DSO_MAJOR_VERSION=1']
+
+# Not using both_libraries() here as
+# 1) resource files should only be in the shared library
+# 2) we want the .pc file to include a dependency to {pgport,common}_static for
+# libpq_st, and {pgport,common}_shlib for libpq_sh
+#
+# We could try to avoid building the source files twice, but it probably adds
+# more complexity than its worth (reusing object files requires also linking
+# to the library on windows or breaks precompiled headers).
+libpq_oauth_st = static_library('libpq-oauth',
+ libpq_sources,
+ include_directories: [libpq_oauth_inc],
+ c_args: libpq_c_args,
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_stlib_code, libpq_deps],
+ kwargs: default_lib_args,
+)
+
+libpq_oauth_so = shared_library('libpq-oauth',
+ libpq_sources + libpq_so_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_args: libpq_c_args,
+ c_pch: pch_postgres_fe_h,
+ version: '1.' + pg_version_major.to_string(),
+ soversion: host_system != 'windows' ? '1' : '',
+ darwin_versions: ['1', '1.' + pg_version_major.to_string()],
+ dependencies: [frontend_shlib_code, libpq, libpq_oauth_deps],
+ link_depends: export_file,
+ link_args: export_fmt.format(export_file.full_path()),
+ kwargs: default_lib_args,
+)
+
+libpq_oauth = declare_dependency(
+ link_with: [libpq_oauth_so],
+ include_directories: [include_directories('.')]
+)
+
+pkgconfig.generate(
+ name: 'libpq-oauth',
+ description: 'PostgreSQL libpq library, device authorization oauth support',
+ url: pg_url,
+ libraries: libpq_oauth,
+ libraries_private: [frontend_stlib_code, libpq_oauth_deps],
+)
+
+subdir('po', if_found: libintl)
diff --git a/src/interfaces/libpq-oauth/po/meson.build b/src/interfaces/libpq-oauth/po/meson.build
new file mode 100644
index 00000000000..1ca1faaf726
--- /dev/null
+++ b/src/interfaces/libpq-oauth/po/meson.build
@@ -0,0 +1,3 @@
+# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+
+nls_targets += [i18n.gettext('libpq-oauth' + '1' + '-' + pg_version_major.to_string())]
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 90b0b65db6f..8cf8d9e54d8 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -64,10 +64,6 @@ OBJS += \
fe-secure-gssapi.o
endif
-ifeq ($(with_libcurl),yes)
-OBJS += fe-auth-oauth-curl.o
-endif
-
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index d5143766858..bc0ed85482a 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -210,3 +210,6 @@ PQsetAuthDataHook 207
PQgetAuthDataHook 208
PQdefaultAuthDataHook 209
PQfullProtocolVersion 210
+appendPQExpBufferVA 211
+pg_g_threadlock 212
+PQauthDataHook 213
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
index cf1a25e2ccc..55f980f3d05 100644
--- a/src/interfaces/libpq/fe-auth-oauth.c
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -15,6 +15,10 @@
#include "postgres_fe.h"
+#ifndef WIN32
+#include <dlfcn.h>
+#endif
+
#include "common/base64.h"
#include "common/hmac.h"
#include "common/jsonapi.h"
@@ -721,6 +725,44 @@ cleanup_user_oauth_flow(PGconn *conn)
state->async_ctx = NULL;
}
+static bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+#ifdef WIN32
+ return false;
+#else
+ PostgresPollingStatusType (*flow) (PGconn *conn);
+ void (*cleanup) (PGconn *conn);
+
+ state->builtin_flow = dlopen(
+#if defined(__darwin__)
+ "libpq-oauth.1.dylib",
+#else
+ "libpq-oauth.so.1",
+#endif
+ RTLD_NOW | RTLD_LOCAL);
+ if (!state->builtin_flow)
+ {
+ fprintf(stderr, "failed dlopen: %s\n", dlerror()); // XXX
+ return false;
+ }
+
+ flow = dlsym(state->builtin_flow, "pg_fe_run_oauth_flow");
+ cleanup = dlsym(state->builtin_flow, "pg_fe_cleanup_oauth_flow");
+
+ if (!(flow && cleanup))
+ {
+ fprintf(stderr, "failed dlsym: %s\n", dlerror()); // XXX
+ return false;
+ }
+
+ conn->async_auth = flow;
+ conn->cleanup_async_auth = cleanup;
+
+ return true;
+#endif /* !WIN32 */
+}
+
/*
* Chooses an OAuth client flow for the connection, which will retrieve a Bearer
* token for presentation to the server.
@@ -792,18 +834,10 @@ setup_token_request(PGconn *conn, fe_oauth_state *state)
libpq_append_conn_error(conn, "user-defined OAuth flow failed");
goto fail;
}
- else
+ else if (!use_builtin_flow(conn, state))
{
-#if USE_LIBCURL
- /* Hand off to our built-in OAuth flow. */
- conn->async_auth = pg_fe_run_oauth_flow;
- conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
-
-#else
libpq_append_conn_error(conn, "no custom OAuth flows are available, and libpq was not built with libcurl support");
goto fail;
-
-#endif
}
return true;
diff --git a/src/interfaces/libpq/fe-auth-oauth.h b/src/interfaces/libpq/fe-auth-oauth.h
index 3f1a7503a01..699ba42acc2 100644
--- a/src/interfaces/libpq/fe-auth-oauth.h
+++ b/src/interfaces/libpq/fe-auth-oauth.h
@@ -33,10 +33,10 @@ typedef struct
PGconn *conn;
void *async_ctx;
+
+ void *builtin_flow;
} fe_oauth_state;
-extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
-extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
extern void pqClearOAuthToken(PGconn *conn);
extern bool oauth_unsafe_debugging_enabled(void);
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index de98e0d20c4..1d4991f8996 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -18,9 +18,6 @@
#include "libpq-int.h"
-extern PQauthDataHook_type PQauthDataHook;
-
-
/* Prototypes for functions in fe-auth.c */
extern int pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn,
bool *async);
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 7d3a9df6fd5..696a6587dd4 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -812,6 +812,7 @@ typedef int (*PQauthDataHook_type) (PGauthData type, PGconn *conn, void *data);
extern void PQsetAuthDataHook(PQauthDataHook_type hook);
extern PQauthDataHook_type PQgetAuthDataHook(void);
extern int PQdefaultAuthDataHook(PGauthData type, PGconn *conn, void *data);
+extern PQauthDataHook_type PQauthDataHook;
/* === in encnames.c === */
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index 19f4a52a97a..02a88408e34 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -38,10 +38,6 @@ if gssapi.found()
)
endif
-if libcurl.found()
- libpq_sources += files('fe-auth-oauth-curl.c')
-endif
-
export_file = custom_target('libpq.exports',
kwargs: gen_export_kwargs,
)
--
2.34.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
@ 2025-03-31 14:06 ` Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Christoph Berg @ 2025-03-31 14:06 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; Jacob Champion <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>
Re: Andres Freund
> > Yes. Also, none of this has addressed my complaint about the extent
> > of the build and install dependencies. Yes, simply not selecting
> > --with-libcurl removes the problem ... but most packagers are under
> > very heavy pressure to enable all features of a package.
And this feature is kind of only useful if it's available anywhere. If
only half of your clients are able to use SSO, you'd probably stick
with passwords anyway. So it needs to be enabled by default.
> How about we provide the current libpq.so without linking to curl and also a
> libpq-oauth.so that has curl support? If we do it right libpq-oauth.so would
> itself link to libpq.so, making libpq-oauth.so a fairly small library.
>
> That way packagers can split libpq-oauth.so into a separate package, while
> still just building once.
That's definitely a good plan. The blast radius of build dependencies
isn't really a problem, the install/run-time is.
Perhaps we could do the same with libldap and libgssapi? (Though
admittedly I have never seen any complaints or nagging questions from
security people about these.)
Christoph
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
@ 2025-04-01 22:40 ` Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-01 22:40 UTC (permalink / raw)
To: Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; Jacob Champion <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>
On Mon, Mar 31, 2025 at 7:06 AM Christoph Berg <[email protected]> wrote:
> Perhaps we could do the same with libldap and libgssapi? (Though
> admittedly I have never seen any complaints or nagging questions from
> security people about these.)
If we end up happy with how the Curl indirection works, that seems
like it'd be kind of nice in theory. I'm not sure how many people
would notice, though.
On Wed, Mar 26, 2025 at 12:09 PM Jacob Champion
<[email protected]> wrote:
> Right
> now we have an SO version of 1; maybe we want to remove the SO version
> entirely to better indicate that it shouldn't be linked?
Maybe a better idea would be to ship an SONAME of
`libpq-oauth.so.0.<major>`, without any symlinks, so that there's
never any ambiguity about which module belongs with which libpq.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-05 00:27 ` Jacob Champion <[email protected]>
2025-04-06 20:48 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 14:21 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
0 siblings, 3 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-05 00:27 UTC (permalink / raw)
To: Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>
On Thu, Apr 3, 2025 at 12:50 PM Daniel Gustafsson <[email protected]> wrote:
> Thanks, both LGTM so pushed.
Thank you!
On Tue, Apr 1, 2025 at 3:40 PM Jacob Champion
<[email protected]> wrote:
> Maybe a better idea would be to ship an SONAME of
> `libpq-oauth.so.0.<major>`, without any symlinks, so that there's
> never any ambiguity about which module belongs with which libpq.
While I was looking into this I found that Debian's going to use the
existence of an SONAME to check other things, which I assume will make
Christoph's life harder. I have switched over to
'libpq-oauth-<major>.so', without any SONAME or symlinks.
v2 simplifies quite a few things and breaks out the new duplicated
code into its own file. I pared down the exports from libpq, by having
it push the pg_g_threadlock pointer directly into the module when
needed. I think a future improvement would be to combine the dlopen
with the libcurl initialization, so that everything is done exactly
once and the module doesn't need to know about threadlocks at all.
i18n is still not working correctly on my machine. I've gotten `make
init-po` to put the files into the right places now, but if I fake a
.po file and install the generated .mo, the translations still don't
seem to be found at runtime. Is anyone able to take a quick look to
see if I'm missing something obvious?
I still need to disable the module entirely on Windows (and other
platforms without support), and potentially rename the --with-libcurl
option.
Thanks,
--Jacob
Attachments:
[application/octet-stream] v2-0001-WIP-split-Device-Authorization-flow-into-dlopen-d.patch (21.0K, ../../CAOYmi+moTsgohh5Tf1gn7dBynARV9EFfWaBVPcJD9O=h6RkSCw@mail.gmail.com/2-v2-0001-WIP-split-Device-Authorization-flow-into-dlopen-d.patch)
download | inline diff:
From 20b4fbe435d31c4e784ce56c887a8d5c365d8ea5 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Wed, 26 Mar 2025 10:55:28 -0700
Subject: [PATCH v2] WIP: split Device Authorization flow into dlopen'd module
See notes on mailing list.
Co-authored-by: Daniel Gustafsson <[email protected]>
---
meson.build | 12 +-
src/interfaces/Makefile | 9 +
src/interfaces/libpq-oauth/Makefile | 56 ++++++
src/interfaces/libpq-oauth/README | 18 ++
src/interfaces/libpq-oauth/exports.txt | 4 +
src/interfaces/libpq-oauth/meson.build | 32 +++
src/interfaces/libpq-oauth/nls.mk | 15 ++
.../oauth-curl.c} | 9 +-
src/interfaces/libpq-oauth/oauth-curl.h | 23 +++
src/interfaces/libpq-oauth/oauth-utils.c | 190 ++++++++++++++++++
src/interfaces/libpq-oauth/oauth-utils.h | 22 ++
src/interfaces/libpq-oauth/po/LINGUAS | 0
src/interfaces/libpq-oauth/po/meson.build | 3 +
src/interfaces/libpq/Makefile | 4 -
src/interfaces/libpq/exports.txt | 1 +
src/interfaces/libpq/fe-auth-oauth.c | 52 ++++-
src/interfaces/libpq/fe-auth-oauth.h | 4 +-
src/interfaces/libpq/meson.build | 4 -
18 files changed, 431 insertions(+), 27 deletions(-)
create mode 100644 src/interfaces/libpq-oauth/Makefile
create mode 100644 src/interfaces/libpq-oauth/README
create mode 100644 src/interfaces/libpq-oauth/exports.txt
create mode 100644 src/interfaces/libpq-oauth/meson.build
create mode 100644 src/interfaces/libpq-oauth/nls.mk
rename src/interfaces/{libpq/fe-auth-oauth-curl.c => libpq-oauth/oauth-curl.c} (99%)
create mode 100644 src/interfaces/libpq-oauth/oauth-curl.h
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.c
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.h
create mode 100644 src/interfaces/libpq-oauth/po/LINGUAS
create mode 100644 src/interfaces/libpq-oauth/po/meson.build
diff --git a/meson.build b/meson.build
index 454ed81f5ea..5620d959056 100644
--- a/meson.build
+++ b/meson.build
@@ -107,6 +107,7 @@ os_deps = []
backend_both_deps = []
backend_deps = []
libpq_deps = []
+libpq_oauth_deps = []
pg_sysroot = ''
@@ -3215,17 +3216,18 @@ libpq_deps += [
gssapi,
ldap_r,
- # XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
- # during gss_acquire_cred(). This is possibly related to Curl's Heimdal
- # dependency on that platform?
- libcurl,
libintl,
ssl,
]
+libpq_oauth_deps += [
+ libcurl,
+]
+
subdir('src/interfaces/libpq')
-# fe_utils depends on libpq
+# fe_utils and libpq-oauth depends on libpq
subdir('src/fe_utils')
+subdir('src/interfaces/libpq-oauth')
# for frontend binaries
frontend_code = declare_dependency(
diff --git a/src/interfaces/Makefile b/src/interfaces/Makefile
index 7d56b29d28f..322a498823d 100644
--- a/src/interfaces/Makefile
+++ b/src/interfaces/Makefile
@@ -14,7 +14,16 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = libpq ecpg
+ifeq ($(with_libcurl), yes)
+SUBDIRS += libpq-oauth
+endif
+
$(recurse)
all-ecpg-recurse: all-libpq-recurse
install-ecpg-recurse: install-libpq-recurse
+
+ifeq ($(with_libcurl), yes)
+all-libpq-oauth-recurse: all-libpq-recurse
+install-libpq-oauth-recurse: install-libpq-recurse
+endif
diff --git a/src/interfaces/libpq-oauth/Makefile b/src/interfaces/libpq-oauth/Makefile
new file mode 100644
index 00000000000..461c44b59c1
--- /dev/null
+++ b/src/interfaces/libpq-oauth/Makefile
@@ -0,0 +1,56 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for libpq-oauth
+#
+# Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/interfaces/libpq-oauth/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/interfaces/libpq-oauth
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+PGFILEDESC = "libpq-oauth - device authorization oauth support"
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION. (We still put the major version into the name, to make it
+# obvious where the library belongs.)
+NAME = libpq-oauth-$(MAJORVERSION)
+
+override CPPFLAGS := -I$(libpq_srcdir) -I$(top_builddir)/src/port $(CPPFLAGS)
+
+OBJS = \
+ $(WIN32RES) \
+ oauth-curl.o \
+ oauth-utils.o
+
+SHLIB_LINK_INTERNAL = $(libpq_pgport_shlib)
+SHLIB_LINK = -lcurl
+SHLIB_PREREQS = submake-libpq
+
+SHLIB_EXPORTS = exports.txt
+
+PKG_CONFIG_REQUIRES_PRIVATE = libpq
+#
+# Make dependencies on pg_config_paths.h visible in all builds.
+oauth-curl.o: oauth-curl.c $(top_builddir)/src/port/pg_config_paths.h
+
+$(top_builddir)/src/port/pg_config_paths.h:
+ $(MAKE) -C $(top_builddir)/src/port pg_config_paths.h
+
+all: all-lib
+
+# Shared library stuff
+include $(top_srcdir)/src/Makefile.shlib
+
+install: all installdirs install-lib
+
+installdirs: installdirs-lib
+
+uninstall: uninstall-lib
+
+clean distclean: clean-lib
+ rm -f $(OBJS)
diff --git a/src/interfaces/libpq-oauth/README b/src/interfaces/libpq-oauth/README
new file mode 100644
index 00000000000..5006f405080
--- /dev/null
+++ b/src/interfaces/libpq-oauth/README
@@ -0,0 +1,18 @@
+libpq-oauth is an optional module implementing the Device Authorization flow for
+OAuth clients (RFC 8628). It was originally developed as part of libpq core and
+later split out as its own shared library in order to isolate its dependency on
+libcurl. (End users who don't want the Curl dependency can simply choose not to
+install this module.)
+
+If a connection string allows the use of OAuth, the server asks for it, and a
+libpq client has not installed its own custom OAuth flow, libpq will attempt to
+delay-load this module using dlopen() and the following ABI. Failure to load
+results in a failed connection.
+
+= Load-Time ABI =
+
+This module ABI is an internal implementation detail, so it's subject to change
+without warning, even during minor releases (however unlikely). The compiled
+version of libpq-oauth should always match the compiled version of libpq.
+
+TODO
diff --git a/src/interfaces/libpq-oauth/exports.txt b/src/interfaces/libpq-oauth/exports.txt
new file mode 100644
index 00000000000..3787b388e04
--- /dev/null
+++ b/src/interfaces/libpq-oauth/exports.txt
@@ -0,0 +1,4 @@
+# src/interfaces/libpq-oauth/exports.txt
+pg_fe_run_oauth_flow 1
+pg_fe_cleanup_oauth_flow 2
+pg_g_threadlock 3
diff --git a/src/interfaces/libpq-oauth/meson.build b/src/interfaces/libpq-oauth/meson.build
new file mode 100644
index 00000000000..1834afbf7a5
--- /dev/null
+++ b/src/interfaces/libpq-oauth/meson.build
@@ -0,0 +1,32 @@
+# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+
+if not libcurl.found() or host_system == 'windows'
+ subdir_done()
+endif
+
+libpq_oauth_sources = files(
+ 'oauth-curl.c',
+ 'oauth-utils.c',
+)
+
+export_file = custom_target('libpq-oauth.exports',
+ kwargs: gen_export_kwargs,
+)
+
+# port needs to be in include path due to pthread-win32.h
+libpq_oauth_inc = include_directories('.', '../libpq', '../../port')
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION. (We still put the major version into the name, to make it
+# obvious where the library belongs.)
+libpq_oauth_so = shared_module('libpq-oauth-' + pg_version_major.to_string(),
+ libpq_oauth_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_shlib_code, libpq, libpq_oauth_deps],
+ link_depends: export_file,
+ link_args: export_fmt.format(export_file.full_path()),
+ kwargs: default_lib_args,
+)
+
+subdir('po', if_found: libintl)
diff --git a/src/interfaces/libpq-oauth/nls.mk b/src/interfaces/libpq-oauth/nls.mk
new file mode 100644
index 00000000000..eab3347ef60
--- /dev/null
+++ b/src/interfaces/libpq-oauth/nls.mk
@@ -0,0 +1,15 @@
+# src/interfaces/libpq-oauth/nls.mk
+CATALOG_NAME = libpq-oauth
+GETTEXT_FILES = oauth-curl.c \
+ oauth-utils.c
+GETTEXT_TRIGGERS = actx_error:2 \
+ libpq_append_conn_error:2 \
+ libpq_append_error:2 \
+ libpq_gettext \
+ libpq_ngettext:1,2
+GETTEXT_FLAGS = actx_error:2:c-format \
+ libpq_append_conn_error:2:c-format \
+ libpq_append_error:2:c-format \
+ libpq_gettext:1:pass-c-format \
+ libpq_ngettext:1:pass-c-format \
+ libpq_ngettext:2:pass-c-format
diff --git a/src/interfaces/libpq/fe-auth-oauth-curl.c b/src/interfaces/libpq-oauth/oauth-curl.c
similarity index 99%
rename from src/interfaces/libpq/fe-auth-oauth-curl.c
rename to src/interfaces/libpq-oauth/oauth-curl.c
index cd9c0323bb6..11d17ec1597 100644
--- a/src/interfaces/libpq/fe-auth-oauth-curl.c
+++ b/src/interfaces/libpq-oauth/oauth-curl.c
@@ -1,6 +1,6 @@
/*-------------------------------------------------------------------------
*
- * fe-auth-oauth-curl.c
+ * oauth-curl.c
* The libcurl implementation of OAuth/OIDC authentication, using the
* OAuth Device Authorization Grant (RFC 8628).
*
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * src/interfaces/libpq/fe-auth-oauth-curl.c
+ * src/interfaces/libpq-oauth/oauth-curl.c
*
*-------------------------------------------------------------------------
*/
@@ -31,6 +31,8 @@
#include "fe-auth-oauth.h"
#include "libpq-int.h"
#include "mb/pg_wchar.h"
+#include "oauth-curl.h"
+#include "oauth-utils.h"
/*
* It's generally prudent to set a maximum response size to buffer in memory,
@@ -2487,8 +2489,9 @@ prompt_user(struct async_ctx *actx, PGconn *conn)
.verification_uri_complete = actx->authz.verification_uri_complete,
.expires_in = actx->authz.expires_in,
};
+ PQauthDataHook_type hook = PQgetAuthDataHook();
- res = PQauthDataHook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
+ res = hook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
if (!res)
{
diff --git a/src/interfaces/libpq-oauth/oauth-curl.h b/src/interfaces/libpq-oauth/oauth-curl.h
new file mode 100644
index 00000000000..bcc1e737dcd
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-curl.h
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-curl.h
+ *
+ * Definitions for OAuth Device Authorization module
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-curl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_CURL_H
+#define OAUTH_CURL_H
+
+#include "libpq-fe.h"
+
+extern PGDLLEXPORT PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+extern PGDLLEXPORT void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+#endif /* OAUTH_CURL_H */
diff --git a/src/interfaces/libpq-oauth/oauth-utils.c b/src/interfaces/libpq-oauth/oauth-utils.c
new file mode 100644
index 00000000000..81f9c6dc247
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.c
@@ -0,0 +1,190 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.c
+ *
+ * "Glue" helpers providing a copy of some internal APIs from libpq. At
+ * some point in the future, we might be able to deduplicate.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq-oauth/oauth-utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-int.h"
+#include "oauth-utils.h"
+#include "pg_config_paths.h"
+
+pgthreadlock_t pg_g_threadlock;
+
+/*
+ * Append a formatted string to the error message buffer of the given
+ * connection, after translating it. A newline is automatically appended; the
+ * format should not end with a newline.
+ */
+void
+libpq_append_conn_error(PGconn *conn, const char *fmt,...)
+{
+ int save_errno = errno;
+ bool done;
+ va_list args;
+
+ Assert(fmt[strlen(fmt) - 1] != '\n');
+
+ if (PQExpBufferBroken(&conn->errorMessage))
+ return; /* already failed */
+
+ /* Loop in case we have to retry after enlarging the buffer. */
+ do
+ {
+ errno = save_errno;
+ va_start(args, fmt);
+ done = appendPQExpBufferVA(&conn->errorMessage, libpq_gettext(fmt), args);
+ va_end(args);
+ } while (!done);
+
+ appendPQExpBufferChar(&conn->errorMessage, '\n');
+}
+
+/*
+ * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
+ */
+bool
+oauth_unsafe_debugging_enabled(void)
+{
+ const char *env = getenv("PGOAUTHDEBUG");
+
+ return (env && strcmp(env, "UNSAFE") == 0);
+}
+
+int
+pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
+{
+ sigset_t sigpipe_sigset;
+ sigset_t sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ /* Block SIGPIPE and save previous mask for later reset */
+ SOCK_ERRNO_SET(pthread_sigmask(SIG_BLOCK, &sigpipe_sigset, osigset));
+ if (SOCK_ERRNO)
+ return -1;
+
+ /* We can have a pending SIGPIPE only if it was blocked before */
+ if (sigismember(osigset, SIGPIPE))
+ {
+ /* Is there a pending SIGPIPE? */
+ if (sigpending(&sigset) != 0)
+ return -1;
+
+ if (sigismember(&sigset, SIGPIPE))
+ *sigpipe_pending = true;
+ else
+ *sigpipe_pending = false;
+ }
+ else
+ *sigpipe_pending = false;
+
+ return 0;
+}
+
+void
+pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
+{
+ int save_errno = SOCK_ERRNO;
+ int signo;
+ sigset_t sigset;
+
+ /* Clear SIGPIPE only if none was pending */
+ if (got_epipe && !sigpipe_pending)
+ {
+ if (sigpending(&sigset) == 0 &&
+ sigismember(&sigset, SIGPIPE))
+ {
+ sigset_t sigpipe_sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ sigwait(&sigpipe_sigset, &signo);
+ }
+ }
+
+ /* Restore saved block mask */
+ pthread_sigmask(SIG_SETMASK, osigset, NULL);
+
+ SOCK_ERRNO_SET(save_errno);
+}
+
+#ifdef ENABLE_NLS
+
+static void
+libpq_binddomain(void)
+{
+ /*
+ * At least on Windows, there are gettext implementations that fail if
+ * multiple threads call bindtextdomain() concurrently. Use a mutex and
+ * flag variable to ensure that we call it just once per process. It is
+ * not known that similar bugs exist on non-Windows platforms, but we
+ * might as well do it the same way everywhere.
+ */
+ static volatile bool already_bound = false;
+ static pthread_mutex_t binddomain_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+ if (!already_bound)
+ {
+ /* bindtextdomain() does not preserve errno */
+#ifdef WIN32
+ int save_errno = GetLastError();
+#else
+ int save_errno = errno;
+#endif
+
+ (void) pthread_mutex_lock(&binddomain_mutex);
+
+ if (!already_bound)
+ {
+ const char *ldir;
+
+ /*
+ * No relocatable lookup here because the calling executable could
+ * be anywhere
+ */
+ ldir = getenv("PGLOCALEDIR");
+ if (!ldir)
+ ldir = LOCALEDIR;
+ bindtextdomain(PG_TEXTDOMAIN("libpq-oauth"), ldir);
+ already_bound = true;
+ }
+
+ (void) pthread_mutex_unlock(&binddomain_mutex);
+
+#ifdef WIN32
+ SetLastError(save_errno);
+#else
+ errno = save_errno;
+#endif
+ }
+}
+
+char *
+libpq_gettext(const char *msgid)
+{
+ libpq_binddomain();
+ return dgettext(PG_TEXTDOMAIN("libpq-oauth"), msgid);
+}
+
+char *
+libpq_ngettext(const char *msgid, const char *msgid_plural, unsigned long n)
+{
+ libpq_binddomain();
+ return dngettext(PG_TEXTDOMAIN("libpq-oauth"), msgid, msgid_plural, n);
+}
+
+#endif /* ENABLE_NLS */
diff --git a/src/interfaces/libpq-oauth/oauth-utils.h b/src/interfaces/libpq-oauth/oauth-utils.h
new file mode 100644
index 00000000000..e5bd6b28b11
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.h
+ *
+ * Definitions providing missing libpq internal APIs
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "libpq-int.h"
+
+extern PGDLLEXPORT pgthreadlock_t pg_g_threadlock;
+
+void libpq_append_conn_error(PGconn *conn, const char *fmt,...) pg_attribute_printf(2, 3);
+bool oauth_unsafe_debugging_enabled(void);
+int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
+void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe);
diff --git a/src/interfaces/libpq-oauth/po/LINGUAS b/src/interfaces/libpq-oauth/po/LINGUAS
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/src/interfaces/libpq-oauth/po/meson.build b/src/interfaces/libpq-oauth/po/meson.build
new file mode 100644
index 00000000000..61b3807ac68
--- /dev/null
+++ b/src/interfaces/libpq-oauth/po/meson.build
@@ -0,0 +1,3 @@
+# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+
+nls_targets += [i18n.gettext('libpq-oauth-' + pg_version_major.to_string())]
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 90b0b65db6f..8cf8d9e54d8 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -64,10 +64,6 @@ OBJS += \
fe-secure-gssapi.o
endif
-ifeq ($(with_libcurl),yes)
-OBJS += fe-auth-oauth-curl.o
-endif
-
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index d5143766858..0625cf39e9a 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -210,3 +210,4 @@ PQsetAuthDataHook 207
PQgetAuthDataHook 208
PQdefaultAuthDataHook 209
PQfullProtocolVersion 210
+appendPQExpBufferVA 211
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
index cf1a25e2ccc..ce15a5e8de1 100644
--- a/src/interfaces/libpq/fe-auth-oauth.c
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -15,6 +15,10 @@
#include "postgres_fe.h"
+#ifndef WIN32
+#include <dlfcn.h>
+#endif
+
#include "common/base64.h"
#include "common/hmac.h"
#include "common/jsonapi.h"
@@ -721,6 +725,44 @@ cleanup_user_oauth_flow(PGconn *conn)
state->async_ctx = NULL;
}
+static bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+#ifdef WIN32
+ return false;
+#else
+ PostgresPollingStatusType (*flow) (PGconn *conn);
+ void (*cleanup) (PGconn *conn);
+ pgthreadlock_t *threadlock_copy;
+
+ /* libpq-oauth is versioned in lockstep; we don't export a stable ABI. */
+ state->builtin_flow = dlopen("libpq-oauth-" PG_MAJORVERSION DLSUFFIX,
+ RTLD_NOW | RTLD_LOCAL);
+ if (!state->builtin_flow)
+ {
+ fprintf(stderr, "failed dlopen: %s\n", dlerror()); // XXX
+ return false;
+ }
+
+ flow = dlsym(state->builtin_flow, "pg_fe_run_oauth_flow");
+ cleanup = dlsym(state->builtin_flow, "pg_fe_cleanup_oauth_flow");
+ threadlock_copy = dlsym(state->builtin_flow, "pg_g_threadlock");
+
+ if (!(flow && cleanup && threadlock_copy))
+ {
+ fprintf(stderr, "failed dlsym: %s\n", dlerror()); // XXX
+ dlclose(state->builtin_flow);
+ return false;
+ }
+
+ conn->async_auth = flow;
+ conn->cleanup_async_auth = cleanup;
+ *threadlock_copy = pg_g_threadlock;
+
+ return true;
+#endif /* !WIN32 */
+}
+
/*
* Chooses an OAuth client flow for the connection, which will retrieve a Bearer
* token for presentation to the server.
@@ -792,18 +834,10 @@ setup_token_request(PGconn *conn, fe_oauth_state *state)
libpq_append_conn_error(conn, "user-defined OAuth flow failed");
goto fail;
}
- else
+ else if (!use_builtin_flow(conn, state))
{
-#if USE_LIBCURL
- /* Hand off to our built-in OAuth flow. */
- conn->async_auth = pg_fe_run_oauth_flow;
- conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
-
-#else
libpq_append_conn_error(conn, "no custom OAuth flows are available, and libpq was not built with libcurl support");
goto fail;
-
-#endif
}
return true;
diff --git a/src/interfaces/libpq/fe-auth-oauth.h b/src/interfaces/libpq/fe-auth-oauth.h
index 3f1a7503a01..699ba42acc2 100644
--- a/src/interfaces/libpq/fe-auth-oauth.h
+++ b/src/interfaces/libpq/fe-auth-oauth.h
@@ -33,10 +33,10 @@ typedef struct
PGconn *conn;
void *async_ctx;
+
+ void *builtin_flow;
} fe_oauth_state;
-extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
-extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
extern void pqClearOAuthToken(PGconn *conn);
extern bool oauth_unsafe_debugging_enabled(void);
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index 292fecf3320..47d38e9378f 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -38,10 +38,6 @@ if gssapi.found()
)
endif
-if libcurl.found()
- libpq_sources += files('fe-auth-oauth-curl.c')
-endif
-
export_file = custom_target('libpq.exports',
kwargs: gen_export_kwargs,
)
--
2.34.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-06 20:48 ` Christoph Berg <[email protected]>
2 siblings, 0 replies; 194+ messages in thread
From: Christoph Berg @ 2025-04-06 20:48 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>
> On Thu, Apr 3, 2025 at 12:50 PM Daniel Gustafsson <[email protected]> wrote:
> > Thanks, both LGTM so pushed.
Ack, the build there worked now. (Albeit without running any tests,
but let's not care too much about this snowflake architecture.)
> On Tue, Apr 1, 2025 at 3:40 PM Jacob Champion
> While I was looking into this I found that Debian's going to use the
> existence of an SONAME to check other things, which I assume will make
> Christoph's life harder. I have switched over to
> 'libpq-oauth-<major>.so', without any SONAME or symlinks.
Since this is a plugin for libpq and nothing external is linking
directly to it, using a formal SONAME wouldn't gain anything, right.
Thanks,
Christoph
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-07 14:21 ` Andres Freund <[email protected]>
2 siblings, 0 replies; 194+ messages in thread
From: Andres Freund @ 2025-04-07 14:21 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Christoph Berg <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>
Hi,
On 2025-04-04 17:27:46 -0700, Jacob Champion wrote:
> += Load-Time ABI =
> +
> +This module ABI is an internal implementation detail, so it's subject to change
> +without warning, even during minor releases (however unlikely). The compiled
> +version of libpq-oauth should always match the compiled version of libpq.
Shouldn't we then include the *minor* version in the soname? I think otherwise
we run into the danger of the wrong library version being loaded in some
cases. Imagine a program being told with libpq to use via rpath. But then we
load the oauth module via a dlopen without a specified path - it'll just
search the global library locations.
Which actually makes me wonder if we ought to instead load the library from a
specific location...
> +TODO
> diff --git a/src/interfaces/libpq-oauth/exports.txt b/src/interfaces/libpq-oauth/exports.txt
> new file mode 100644
> index 00000000000..3787b388e04
> --- /dev/null
> +++ b/src/interfaces/libpq-oauth/exports.txt
> @@ -0,0 +1,4 @@
> +# src/interfaces/libpq-oauth/exports.txt
> +pg_fe_run_oauth_flow 1
> +pg_fe_cleanup_oauth_flow 2
> +pg_g_threadlock 3
The pg_g_threadlock thing seems pretty ugly. Can't we just pass that to the
relevant functions? But more fundamentally, are we actually using
pg_g_threadlock anywhere? I removed all the releant code and the tests still
seem to pass?
> diff --git a/src/interfaces/libpq-oauth/meson.build b/src/interfaces/libpq-oauth/meson.build
> new file mode 100644
> index 00000000000..1834afbf7a5
> --- /dev/null
> +++ b/src/interfaces/libpq-oauth/meson.build
> @@ -0,0 +1,32 @@
> +# Copyright (c) 2022-2025, PostgreSQL Global Development Group
> +
> +if not libcurl.found() or host_system == 'windows'
> + subdir_done()
> +endif
Why does this not work on windows? I don't see similar code in the removed
lines?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-07 14:43 ` Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2 siblings, 1 reply; 194+ messages in thread
From: Andres Freund @ 2025-04-07 14:43 UTC (permalink / raw)
To: Peter Eisentraut <[email protected]>; +Cc: Jacob Champion <[email protected]>; Christoph Berg <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
Hi,
On 2025-04-07 15:59:19 +0200, Peter Eisentraut wrote:
> On 05.04.25 02:27, Jacob Champion wrote:
> > On Tue, Apr 1, 2025 at 3:40 PM Jacob Champion
> > <[email protected]> wrote:
> > > Maybe a better idea would be to ship an SONAME of
> > > `libpq-oauth.so.0.<major>`, without any symlinks, so that there's
> > > never any ambiguity about which module belongs with which libpq.
> >
> > While I was looking into this I found that Debian's going to use the
> > existence of an SONAME to check other things, which I assume will make
> > Christoph's life harder. I have switched over to
> > 'libpq-oauth-<major>.so', without any SONAME or symlinks.
>
> Yes, this is correct. We want a shared module, not a shared library, in
> meson parlance.
It's not entirely obvious to me that we do want that.
There recently was a breakage of building with PG on macos with meson, due to
the meson folks implementing a feature request to move away from using
bundles, as
1) bundles apparently aren't supported on iOS
2) there apparently aren't any restrictions left that require using bundles,
and there haven't been for a while.
They've now reverted these changes, due to the postgres build failures that
caused as well as recognizing they probably moved too fast, but the iOS
portion seems like it could be relevant for us?
Afaict this library doesn't have unresolved symbols, due to just linking to
libpq. So I don't think we really need this to be a shared module?
> But the installation directory of a shared module should not be directly
> libdir.
Agreed. However, it seems like relocatability would be much more important for
something like libpq than server modules... Particularly on windows it'll
often just be shipped alongside the executable - which won't work if we load
from pklibdir or such.
> I don't know whether we need an exports file for libpq-oauth. The other
> shared modules don't provide an export file, and I'm not sure whether this
> is even supported for shared modules. I guess it doesn't hurt?
It does seem just using PGDLLEXPORT would suffice here.
> The PKG_CONFIG_REQUIRES_PRIVATE setting in libpq-oauth/Makefile is
> meaningless and can be removed.
>
> In fe-auth-oauth.c, I note that dlerror() is not necessarily thread-safe.
I sometimes really really hate posix.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
@ 2025-04-07 16:41 ` Jacob Champion <[email protected]>
2025-04-07 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 3 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-07 16:41 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Christoph Berg <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
Hi all,
Thanks for all the feedback! I'll combine them all into one email:
On Mon, Apr 7, 2025 at 6:59 AM Peter Eisentraut <[email protected]> wrote:
> Looks mostly ok. I need the following patch to get it to build on macOS:
> [...]
> (The second change is not strictly required, but it disables the use of
> -bundle_loader postgres, since this is not a backend-loadable module.)
Hm, okay. I'll take a closer look at this, thanks.
> The PKG_CONFIG_REQUIRES_PRIVATE setting in libpq-oauth/Makefile is
> meaningless and can be removed.
Ah, right. Will do.
> In fe-auth-oauth.c, I note that dlerror() is not necessarily
> thread-safe. Since there isn't really an alternative, I guess it's ok
> to leave it like that, but I figured it should be mentioned.
Yeah. The XXX comments there are a reminder to me to lock the stderr
printing behind debug mode, since I hope most non-packaging people are
not going to be troubleshooting load-time errors. But see the
threadlock discussions below.
> Not sure, the code looks correct at first glance. However, you could
> also just keep the libpq-oauth strings in the libpq catalog. There
> isn't really a need to make a separate one, since the versions you end
> up installing are locked to each other. So you could for example in
> libpq's nls.mk just add
>
> ../libpq-oauth/oauth-curl.c
>
> etc. to the files.
Oh, that's an interesting idea. Thanks, I'll give it a try.
> Maybe it would also make sense to make libpq-oauth a subdirectory of the
> libpq directory instead of a peer.
Works for me.
On Mon, Apr 7, 2025 at 7:21 AM Andres Freund <[email protected]> wrote:
> On 2025-04-04 17:27:46 -0700, Jacob Champion wrote:
> > +This module ABI is an internal implementation detail, so it's subject to change
> > +without warning, even during minor releases (however unlikely). The compiled
> > +version of libpq-oauth should always match the compiled version of libpq.
>
> Shouldn't we then include the *minor* version in the soname?
No objection here.
> I think otherwise
> we run into the danger of the wrong library version being loaded in some
> cases. Imagine a program being told with libpq to use via rpath. But then we
> load the oauth module via a dlopen without a specified path - it'll just
> search the global library locations.
Ah, you mean if the RPATH'd build doesn't have a flow, but the
globally installed one (with a different ABI) does? Yeah, that would
be a problem.
> Which actually makes me wonder if we ought to instead load the library from a
> specific location...
We could hardcode the disk location for version 1, I guess. I kind of
liked giving packagers the flexibility they're used to having from the
ld.so architecture, though. See below.
> > +# src/interfaces/libpq-oauth/exports.txt
> > +pg_fe_run_oauth_flow 1
> > +pg_fe_cleanup_oauth_flow 2
> > +pg_g_threadlock 3
>
> The pg_g_threadlock thing seems pretty ugly. Can't we just pass that to the
> relevant functions?
We can do it however we want, honestly, especially since the ABI isn't
public/stable. I chose this way just to ease review.
> But more fundamentally, are we actually using
> pg_g_threadlock anywhere? I removed all the releant code and the tests still
> seem to pass?
If you have an older Curl installation, it is used. Newer libcurls
don't need it.
A future simplification could be to pull the use of the threadlock
back into libpq, and have it perform a one-time
dlopen-plus-Curl-initialization under the lock... That would also get
rid of the dlerror() thread safety problems. But that's an awful lot
of moving parts under a mutex, which makes me a little nervous.
> > diff --git a/src/interfaces/libpq-oauth/meson.build b/src/interfaces/libpq-oauth/meson.build
> > +if not libcurl.found() or host_system == 'windows'
> > + subdir_done()
> > +endif
>
> Why does this not work on windows? I don't see similar code in the removed
> lines?
The Device Authorization flow is not currently implemented on Windows.
On Mon, Apr 7, 2025 at 7:43 AM Andres Freund <[email protected]> wrote:
> > Yes, this is correct. We want a shared module, not a shared library, in
> > meson parlance.
>
> It's not entirely obvious to me that we do want that.
>
> There recently was a breakage of building with PG on macos with meson, due to
> the meson folks implementing a feature request to move away from using
> bundles, as
> 1) bundles apparently aren't supported on iOS
> 2) there apparently aren't any restrictions left that require using bundles,
> and there haven't been for a while.
Could you explain how this is related to .app bundles? I thought I was
just building a standard dylib.
> Afaict this library doesn't have unresolved symbols, due to just linking to
> libpq. So I don't think we really need this to be a shared module?
Correct, no unresolved symbols. My naive understanding was that
distros were going to impose restrictions on an SONAME'd library that
we may not want to deal with.
> > But the installation directory of a shared module should not be directly
> > libdir.
>
> Agreed. However, it seems like relocatability would be much more important for
> something like libpq than server modules... Particularly on windows it'll
> often just be shipped alongside the executable - which won't work if we load
> from pklibdir or such.
Yeah, I really like the simplicity of "use the standard runtime
loader, just on-demand." Seems more friendly to the ecosystem.
Are there technical downsides of putting it into $libdir? I understand
there are "people" downsides, since we don't really want to signal
that this is a publicly linkable thing... but surely if you go through
the process of linking our library (which has no SONAME, includes the
major/minor version in its -l option, and has no pkgconfig) and
shoving a private pointer to a threadlock into it, you can keep all
the pieces when they break?
> > I don't know whether we need an exports file for libpq-oauth. The other
> > shared modules don't provide an export file, and I'm not sure whether this
> > is even supported for shared modules. I guess it doesn't hurt?
>
> It does seem just using PGDLLEXPORT would suffice here.
My motivation was to strictly identify the ABI that we intend libpq to
use, to try to future-proof things for everybody. Especially since
we're duplicating functions from libpq that we'd rather not be. (The
use of RTLD_LOCAL maybe makes that more of a belt-and-suspenders
thing.)
Are there any downsides to the exports file?
Thanks,
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-07 17:05 ` Andres Freund <[email protected]>
2025-04-07 17:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2 siblings, 1 reply; 194+ messages in thread
From: Andres Freund @ 2025-04-07 17:05 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Christoph Berg <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
Hi,
On 2025-04-07 09:41:25 -0700, Jacob Champion wrote:
> On Mon, Apr 7, 2025 at 7:21 AM Andres Freund <[email protected]> wrote:
> > On 2025-04-04 17:27:46 -0700, Jacob Champion wrote:
> > I think otherwise
> > we run into the danger of the wrong library version being loaded in some
> > cases. Imagine a program being told with libpq to use via rpath. But then we
> > load the oauth module via a dlopen without a specified path - it'll just
> > search the global library locations.
>
> Ah, you mean if the RPATH'd build doesn't have a flow, but the
> globally installed one (with a different ABI) does? Yeah, that would
> be a problem.
That and more: Even if the RPATH libpq does have oauth support, the
libpq-oauth won't necessarily be at the front of the global library search
path. So afaict you'll often get a different libpq-oauth.
Except perhaps on macos, where all this stuff works differently again. But I
managed to unload the required knowledge out of my brain and don't want to
further think about that :)
> > > +# src/interfaces/libpq-oauth/exports.txt
> > > +pg_fe_run_oauth_flow 1
> > > +pg_fe_cleanup_oauth_flow 2
> > > +pg_g_threadlock 3
> >
> > The pg_g_threadlock thing seems pretty ugly. Can't we just pass that to the
> > relevant functions?
>
> We can do it however we want, honestly, especially since the ABI isn't
> public/stable. I chose this way just to ease review.
I found it rather confusing that libpq looks up a symbol and then sets
libpq-oauth's symbol to a pointer in libpq's namespace.
> > But more fundamentally, are we actually using
> > pg_g_threadlock anywhere? I removed all the releant code and the tests still
> > seem to pass?
>
> If you have an older Curl installation, it is used. Newer libcurls
> don't need it.
Oh, wow. We hide the references to pg_g_threadlock behind a friggin macro?
That's just ...
Not your fault, I know...
> A future simplification could be to pull the use of the threadlock
> back into libpq, and have it perform a one-time
> dlopen-plus-Curl-initialization under the lock... That would also get
> rid of the dlerror() thread safety problems. But that's an awful lot
> of moving parts under a mutex, which makes me a little nervous.
I still think we should simply reject at configure time if curl init isn't
threadsafe ;)
> On Mon, Apr 7, 2025 at 7:43 AM Andres Freund <[email protected]> wrote:
> > > Yes, this is correct. We want a shared module, not a shared library, in
> > > meson parlance.
> >
> > It's not entirely obvious to me that we do want that.
> >
> > There recently was a breakage of building with PG on macos with meson, due to
> > the meson folks implementing a feature request to move away from using
> > bundles, as
> > 1) bundles apparently aren't supported on iOS
> > 2) there apparently aren't any restrictions left that require using bundles,
> > and there haven't been for a while.
>
> Could you explain how this is related to .app bundles? I thought I was
> just building a standard dylib.
The other kind of bundles (what on earth apple was thinking with the naming
here I don't know). Stuff liked with ld -bundle.
> > > I don't know whether we need an exports file for libpq-oauth. The other
> > > shared modules don't provide an export file, and I'm not sure whether this
> > > is even supported for shared modules. I guess it doesn't hurt?
> >
> > It does seem just using PGDLLEXPORT would suffice here.
>
> My motivation was to strictly identify the ABI that we intend libpq to
> use, to try to future-proof things for everybody. Especially since
> we're duplicating functions from libpq that we'd rather not be. (The
> use of RTLD_LOCAL maybe makes that more of a belt-and-suspenders
> thing.)
PGDLLEXPORT serves that purpose too, fwiw. These days we use compiler flags
that restrict function visibility of everything not annotated with
PGDLLEXPORT.
However - that's all in c.h, port/win32.h,port/cygwin.h, , which libpq headers
might not want to include.
> Are there any downsides to the exports file?
I think it's fine either way.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
@ 2025-04-07 17:32 ` Jacob Champion <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-07 17:32 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Christoph Berg <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Mon, Apr 7, 2025 at 10:05 AM Andres Freund <[email protected]> wrote:
> On 2025-04-07 09:41:25 -0700, Jacob Champion wrote:
> > Ah, you mean if the RPATH'd build doesn't have a flow, but the
> > globally installed one (with a different ABI) does? Yeah, that would
> > be a problem.
>
> That and more: Even if the RPATH libpq does have oauth support, the
> libpq-oauth won't necessarily be at the front of the global library search
> path. So afaict you'll often get a different libpq-oauth.
ldopen() should respect RPATH, though? Either way, I agree with
pushing the minor version into the name (or else deciding that we will
keep the ABI completely stable across minor version bumps; not sure I
want to guarantee that just yet).
> > We can do it however we want, honestly, especially since the ABI isn't
> > public/stable. I chose this way just to ease review.
>
> I found it rather confusing that libpq looks up a symbol and then sets
> libpq-oauth's symbol to a pointer in libpq's namespace.
Yeah, I think a one-time init call would make this nicer.
> > A future simplification could be to pull the use of the threadlock
> > back into libpq, and have it perform a one-time
> > dlopen-plus-Curl-initialization under the lock... That would also get
> > rid of the dlerror() thread safety problems. But that's an awful lot
> > of moving parts under a mutex, which makes me a little nervous.
>
> I still think we should simply reject at configure time if curl init isn't
> threadsafe ;)
Practically speaking, I don't think that's a choice we can make. For
example, RHEL won't have threadsafe Curl until 10.
> > Could you explain how this is related to .app bundles? I thought I was
> > just building a standard dylib.
>
> The other kind of bundles (what on earth apple was thinking with the naming
> here I don't know). Stuff liked with ld -bundle.
Ah, some new corner-case magic to learn...
> These days we use compiler flags
> that restrict function visibility of everything not annotated with
> PGDLLEXPORT.
Hm, I missed/forgot that. That is nice. Personally I like having a
single file document the exports, so I'll keep it that way for now
unless there are objections, but it's good to know it's not necessary.
Thanks,
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-07 18:47 ` Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2 siblings, 1 reply; 194+ messages in thread
From: Christoph Berg @ 2025-04-07 18:47 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
Re: Jacob Champion
> > > +This module ABI is an internal implementation detail, so it's subject to change
> > > +without warning, even during minor releases (however unlikely). The compiled
> > > +version of libpq-oauth should always match the compiled version of libpq.
> >
> > Shouldn't we then include the *minor* version in the soname?
>
> No objection here.
Mmmmh. Since we are currently only talking about 3 symbols, it doesn't
sound very likely that we'd have to bump this in a major branch.
Putting the minor version into the filename would make looking at
package diffs harder when upgrading. Do we really need this as opposed
to some hardcoded number like libpq.so.5.18 ?
Perhaps reusing the number from libpq.so.5.18 also for this lib would
be the way to go?
> > Which actually makes me wonder if we ought to instead load the library from a
> > specific location...
>
> We could hardcode the disk location for version 1, I guess. I kind of
> liked giving packagers the flexibility they're used to having from the
> ld.so architecture, though. See below.
pkglibdir or a subdirectory of it might make sense.
Though for Debian, I'd actually prefer
/usr/lib/$DEB_HOST_MULTIARCH/libpq/libpq-oauth...
since the libpq packaging is independent from the major version
packaging.
> Are there technical downsides of putting it into $libdir? I understand
> there are "people" downsides, since we don't really want to signal
> that this is a publicly linkable thing... but surely if you go through
> the process of linking our library (which has no SONAME, includes the
> major/minor version in its -l option, and has no pkgconfig) and
> shoving a private pointer to a threadlock into it, you can keep all
> the pieces when they break?
The Debian Policy expectation is that everything in libdir is a proper
library that could be linked to, and that random private stuff should
be elsewhere. But if being able to use the default lib search path is
an important argument, we could put it there.
Christoph
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
@ 2025-04-07 21:49 ` Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-07 21:49 UTC (permalink / raw)
To: Christoph Berg <[email protected]>; Jacob Champion <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Mon, Apr 7, 2025 at 11:47 AM Christoph Berg <[email protected]> wrote:
> Mmmmh. Since we are currently only talking about 3 symbols, it doesn't
> sound very likely that we'd have to bump this in a major branch.
The ABI extends to the pointers we're using, though. This module uses
PGconn* internals and libpq-int.h. [1]
> Putting the minor version into the filename would make looking at
> package diffs harder when upgrading. Do we really need this as opposed
> to some hardcoded number like libpq.so.5.18 ?
>
> Perhaps reusing the number from libpq.so.5.18 also for this lib would
> be the way to go?
That doesn't address Andres' concern, though; if multiple
installations all use libpq.so.5.18, they still can't necessarily mix
and match.
In fact you can't mix and match across different settings of
ENABLE_SSL/GSS/SSPI, either. So I guess that nudges me towards
pkglibdir/<some subdirectory>, to avoid major pain for some unlucky
end user.
> Though for Debian, I'd actually prefer
> /usr/lib/$DEB_HOST_MULTIARCH/libpq/libpq-oauth...
> since the libpq packaging is independent from the major version
> packaging.
Not sure I understand this. Do you mean you'd patch our lookup for
Debian, to find it there instead of pkglibdir? I don't think we can
adopt that ourselves, for the same reasons as above; the two sides
have to be in lockstep.
> The Debian Policy expectation is that everything in libdir is a proper
> library that could be linked to, and that random private stuff should
> be elsewhere. But if being able to use the default lib search path is
> an important argument, we could put it there.
I was hoping the default lib search would make your life (and ours)
easier. If it doesn't, I can lock it down.
Thanks,
--Jacob
[1] Future work ideas in this area include allowing other people to
compile their own loadable flow plugin, so that the utilities can use
it. (Only Device Authorization can be used by psql et al, for 18.) At
that point, developers will need a limited API to twiddle the
connection handle, and our builtin flow(s?) could use the same API.
But that's not work we can tackle for 18.
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-07 21:58 ` Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Christoph Berg @ 2025-04-07 21:58 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
Re: Jacob Champion
> > Putting the minor version into the filename would make looking at
> > package diffs harder when upgrading. Do we really need this as opposed
> > to some hardcoded number like libpq.so.5.18 ?
> >
> > Perhaps reusing the number from libpq.so.5.18 also for this lib would
> > be the way to go?
>
> That doesn't address Andres' concern, though; if multiple
> installations all use libpq.so.5.18, they still can't necessarily mix
> and match.
Well the whole world is linking against libpq5.so and not breaking
either. Why is this module worse? (I guess the answer is internal data
structures... but does it have to be worse?)
> > Though for Debian, I'd actually prefer
> > /usr/lib/$DEB_HOST_MULTIARCH/libpq/libpq-oauth...
> > since the libpq packaging is independent from the major version
> > packaging.
>
> Not sure I understand this. Do you mean you'd patch our lookup for
> Debian, to find it there instead of pkglibdir? I don't think we can
> adopt that ourselves, for the same reasons as above; the two sides
> have to be in lockstep.
Because pkglibdir would be something like /usr/lib/postgresql/17/lib,
even when there is only one libpq5 package for all major server
versions on Debian. So if you have postgresql-16 installed, you'd end
up with
/usr/lib/postgresql/16/{bin,lib} everything from PG 16
/usr/lib/x86_64-linux-gnu/libpq* libpq5
/usr/lib/postgresql/17/lib/libpq-oauth.so
... which is weird.
> [1] Future work ideas in this area include allowing other people to
> compile their own loadable flow plugin, so that the utilities can use
> it. (Only Device Authorization can be used by psql et al, for 18.) At
> that point, developers will need a limited API to twiddle the
> connection handle, and our builtin flow(s?) could use the same API.
> But that's not work we can tackle for 18.
Perhaps keep things simple for PG18 and choose a simple filename and
location. If future extensions need something more elaborate, we can
still switch later.
Christoph
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
@ 2025-04-07 22:26 ` Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-07 22:26 UTC (permalink / raw)
To: Christoph Berg <[email protected]>; Jacob Champion <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Mon, Apr 7, 2025 at 2:58 PM Christoph Berg <[email protected]> wrote:
> Why is this module worse? (I guess the answer is internal data
> structures... but does it have to be worse?)
It doesn't have to be, in general, and the coupling surface is small
enough (libpq_append_conn_error) that we have a relatively easy path
toward decoupling it in the future. But for 18, I suspect no one will
be happy with me if I try to turn that inside out right this instant.
The goal was just to turn an internal implementation detail into a
delay-loaded internal implementation detail.
> Because pkglibdir would be something like /usr/lib/postgresql/17/lib,
> even when there is only one libpq5 package for all major server
> versions on Debian. So if you have postgresql-16 installed, you'd end
> up with
>
> /usr/lib/postgresql/16/{bin,lib} everything from PG 16
> /usr/lib/x86_64-linux-gnu/libpq* libpq5
> /usr/lib/postgresql/17/lib/libpq-oauth.so
>
> ... which is weird.
Weird, sure -- but it's correct, right? Because you have PG17's OAuth
flow installed.
If someone comes to the list with a flow bug in three years, and I ask
them what version they have installed, and they tell me "PG16, and
it's loading /usr/lib/aarch64-linux-gnu/libpq/libpq-oauth.so." That
won't be incredibly helpful IMHO.
> > [1] Future work ideas in this area include allowing other people to
> > compile their own loadable flow plugin, so that the utilities can use
> > it. (Only Device Authorization can be used by psql et al, for 18.) At
> > that point, developers will need a limited API to twiddle the
> > connection handle, and our builtin flow(s?) could use the same API.
> > But that's not work we can tackle for 18.
>
> Perhaps keep things simple for PG18 and choose a simple filename and
> location. If future extensions need something more elaborate, we can
> still switch later.
Sounds good. Any opinions from the gallery on what a "libpq plugin
subdirectory" in pkglibdir should be called? ("client", "modules",
"plugins"...?) Is there still a good reason to put any explicit
versioning into the filename if we do that?
Thanks,
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-08 02:10 ` Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 17:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 2 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-08 02:10 UTC (permalink / raw)
To: Christoph Berg <[email protected]>; Jacob Champion <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Mon, Apr 7, 2025 at 3:26 PM Jacob Champion
<[email protected]> wrote:
> Sounds good. Any opinions from the gallery on what a "libpq plugin
> subdirectory" in pkglibdir should be called? ("client", "modules",
> "plugins"...?)
Hm, one immediate consequence of hardcoding pkglibdir is that we can
no longer rely on LD_LIBRARY_PATH for pre-installation testing.
(Contrast with the server, which is able to relocate extension paths
based on its executable location.)
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-08 14:32 ` Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Bruce Momjian @ 2025-04-08 14:32 UTC (permalink / raw)
To: Daniel Gustafsson <[email protected]>; +Cc: Jacob Champion <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 12:34:02PM +0200, Daniel Gustafsson wrote:
> > On 8 Apr 2025, at 04:10, Jacob Champion <[email protected]> wrote:
> >
> > On Mon, Apr 7, 2025 at 3:26 PM Jacob Champion
> > <[email protected]> wrote:
> >> Sounds good. Any opinions from the gallery on what a "libpq plugin
> >> subdirectory" in pkglibdir should be called? ("client", "modules",
> >> "plugins"...?)
> >
> > Hm, one immediate consequence of hardcoding pkglibdir is that we can
> > no longer rely on LD_LIBRARY_PATH for pre-installation testing.
> > (Contrast with the server, which is able to relocate extension paths
> > based on its executable location.)
>
> That strikes me as a signifant drawback.
Uh, where are we on the inclusion of curl in our build? Maybe it was
explained but I have not seen it. I still see
src/interfaces/libpq/fe-auth-oauth-curl.c.
--
Bruce Momjian <[email protected]> https://momjian.us
EDB https://enterprisedb.com
Do not let urgent matters crowd out time for investment in the future.
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
@ 2025-04-08 15:04 ` Jacob Champion <[email protected]>
2025-04-08 15:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 3 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-08 15:04 UTC (permalink / raw)
To: Bruce Momjian <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 7:32 AM Bruce Momjian <[email protected]> wrote:
> Uh, where are we on the inclusion of curl in our build? Maybe it was
> explained but I have not seen it.
The above is discussing a patch to split this into its own loadable
module. Andres and Christoph's feedback has been shaping where we put
that module, exactly.
Thanks,
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-08 15:13 ` Bruce Momjian <[email protected]>
2025-04-08 15:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2 siblings, 1 reply; 194+ messages in thread
From: Bruce Momjian @ 2025-04-08 15:13 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 08:04:22AM -0700, Jacob Champion wrote:
> On Tue, Apr 8, 2025 at 7:32 AM Bruce Momjian <[email protected]> wrote:
> > Uh, where are we on the inclusion of curl in our build? Maybe it was
> > explained but I have not seen it.
>
> The above is discussing a patch to split this into its own loadable
> module. Andres and Christoph's feedback has been shaping where we put
> that module, exactly.
Uh, I was afraid that was the case, which is why I asked. We have just
hit feature freeze, so it is not good that we are still "shaping" the
patch. Should we consider reverting it? It is true we still "adjust"
patches after feature freeze.
--
Bruce Momjian <[email protected]> https://momjian.us
EDB https://enterprisedb.com
Do not let urgent matters crowd out time for investment in the future.
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 15:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
@ 2025-04-08 15:22 ` Bruce Momjian <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Bruce Momjian @ 2025-04-08 15:22 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Jacob Champion <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 11:20:11AM -0400, Andres Freund wrote:
> Hi,
>
> On 2025-04-08 11:13:51 -0400, Bruce Momjian wrote:
> > On Tue, Apr 8, 2025 at 08:04:22AM -0700, Jacob Champion wrote:
> > > On Tue, Apr 8, 2025 at 7:32 AM Bruce Momjian <[email protected]> wrote:
> > > > Uh, where are we on the inclusion of curl in our build? Maybe it was
> > > > explained but I have not seen it.
> > >
> > > The above is discussing a patch to split this into its own loadable
> > > module. Andres and Christoph's feedback has been shaping where we put
> > > that module, exactly.
> >
> > Uh, I was afraid that was the case, which is why I asked. We have just
> > hit feature freeze, so it is not good that we are still "shaping" the
> > patch. Should we consider reverting it? It is true we still "adjust"
> > patches after feature freeze.
>
> You brought the dependency concern up well after the feature was merged, after
> it had been in development for a *long* time. It wasn't a secret that it had a
> dependency on curl. I don't think it's fair to penalize a feature's authors
> to not finish implementing a complicated and completely new requirement within
> 17 days.
Fair point --- I was just asking.
--
Bruce Momjian <[email protected]> https://momjian.us
EDB https://enterprisedb.com
Do not let urgent matters crowd out time for investment in the future.
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-08 16:14 ` Bruce Momjian <[email protected]>
2025-04-08 16:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2 siblings, 1 reply; 194+ messages in thread
From: Bruce Momjian @ 2025-04-08 16:14 UTC (permalink / raw)
To: Wolfgang Walther <[email protected]>; +Cc: Jacob Champion <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 06:01:42PM +0200, Wolfgang Walther wrote:
> Jacob Champion:
> > The above is discussing a patch to split this into its own loadable
> > module.
>
> Wasn't sure where to put this exactly, the thread is long and I couldn't
> find any discussion around it:
>
> How does the proposal with a loadable module affect a static libpq.a?
>
> I have not tried, yet, but is my assumption correct, that I could build a
> libpq.a with oauth/curl support on current HEAD?
>
> If yes, would that still be an option after the split?
How does this patch help us avoid having to handle curl CVEs and its
curl's additional dependencies? As I understand the patch, it makes
libpq _not_ have additional dependencies but moves the dependencies to a
special loadable library that libpq can use.
--
Bruce Momjian <[email protected]> https://momjian.us
EDB https://enterprisedb.com
Do not let urgent matters crowd out time for investment in the future.
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
@ 2025-04-08 16:17 ` Jacob Champion <[email protected]>
2025-04-08 16:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-08 16:17 UTC (permalink / raw)
To: Bruce Momjian <[email protected]>; +Cc: Wolfgang Walther <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 9:14 AM Bruce Momjian <[email protected]> wrote:
> How does this patch help us avoid having to handle curl CVEs and its
> curl's additional dependencies? As I understand the patch, it makes
> libpq _not_ have additional dependencies but moves the dependencies to a
> special loadable library that libpq can use.
It allows packagers to ship the OAuth library separately, so end users
that don't want the additional exposure don't have to install it at
all.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 16:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-08 16:33 ` Bruce Momjian <[email protected]>
2025-04-08 16:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:48 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
0 siblings, 2 replies; 194+ messages in thread
From: Bruce Momjian @ 2025-04-08 16:33 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Wolfgang Walther <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 09:17:03AM -0700, Jacob Champion wrote:
> On Tue, Apr 8, 2025 at 9:14 AM Bruce Momjian <[email protected]> wrote:
> > How does this patch help us avoid having to handle curl CVEs and its
> > curl's additional dependencies? As I understand the patch, it makes
> > libpq _not_ have additional dependencies but moves the dependencies to a
> > special loadable library that libpq can use.
>
> It allows packagers to ship the OAuth library separately, so end users
> that don't want the additional exposure don't have to install it at
> all.
Okay, so how would they do that? I understand how that would happen if
it was an external extension, but how if it is under /src or /contrib.
FYI, I see a good number of curl CVEs:
https://curl.se/docs/security.html
Would we have to put out minor releases for curl CVEs? I don't think we
have to for OpenSSL so would curl be the same?
I am asking these questions now so we can save time in getting this
closed.
--
Bruce Momjian <[email protected]> https://momjian.us
EDB https://enterprisedb.com
Do not let urgent matters crowd out time for investment in the future.
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 16:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
@ 2025-04-08 16:43 ` Jacob Champion <[email protected]>
2025-04-08 16:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-08 16:43 UTC (permalink / raw)
To: Bruce Momjian <[email protected]>; +Cc: Wolfgang Walther <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 9:33 AM Bruce Momjian <[email protected]> wrote:
> On Tue, Apr 8, 2025 at 09:17:03AM -0700, Jacob Champion wrote:
> > It allows packagers to ship the OAuth library separately, so end users
> > that don't want the additional exposure don't have to install it at
> > all.
>
> Okay, so how would they do that? I understand how that would happen if
> it was an external extension, but how if it is under /src or /contrib.
By adding the new .so to a different package. For example, RPM specs
would just let you say "hey, this .so I just built doesn't go into the
main client package, it goes into an add-on that depends on the client
package." It's the same way separate client and server packages get
generated from the same single build of Postgres.
> Would we have to put out minor releases for curl CVEs?
In general, no.
Thanks,
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 16:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 16:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-08 16:49 ` Bruce Momjian <[email protected]>
2025-04-08 17:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Bruce Momjian @ 2025-04-08 16:49 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Wolfgang Walther <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 09:43:01AM -0700, Jacob Champion wrote:
> On Tue, Apr 8, 2025 at 9:33 AM Bruce Momjian <[email protected]> wrote:
> > On Tue, Apr 8, 2025 at 09:17:03AM -0700, Jacob Champion wrote:
> > > It allows packagers to ship the OAuth library separately, so end users
> > > that don't want the additional exposure don't have to install it at
> > > all.
> >
> > Okay, so how would they do that? I understand how that would happen if
> > it was an external extension, but how if it is under /src or /contrib.
>
> By adding the new .so to a different package. For example, RPM specs
> would just let you say "hey, this .so I just built doesn't go into the
> main client package, it goes into an add-on that depends on the client
> package." It's the same way separate client and server packages get
> generated from the same single build of Postgres.
Do we have any idea how many packagers are interested in doing this?
> > Would we have to put out minor releases for curl CVEs?
>
> In general, no.
Good.
FYI, I saw bug bounty dollar amounts next to each curl CVE:
https://curl.se/docs/security.html
No wonder some people ask for bounties.
--
Bruce Momjian <[email protected]> https://momjian.us
EDB https://enterprisedb.com
Do not let urgent matters crowd out time for investment in the future.
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 16:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 16:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
@ 2025-04-08 17:00 ` Jacob Champion <[email protected]>
2025-04-08 17:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-08 17:00 UTC (permalink / raw)
To: Bruce Momjian <[email protected]>; +Cc: Wolfgang Walther <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 9:49 AM Bruce Momjian <[email protected]> wrote:
> On Tue, Apr 8, 2025 at 09:43:01AM -0700, Jacob Champion wrote:
> > By adding the new .so to a different package. For example, RPM specs
> > would just let you say "hey, this .so I just built doesn't go into the
> > main client package, it goes into an add-on that depends on the client
> > package." It's the same way separate client and server packages get
> > generated from the same single build of Postgres.
>
> Do we have any idea how many packagers are interested in doing this?
I'm not sure how to answer this. The primary drivers from the dev side
are you and Tom, I think. Christoph seems to be on board with a split
as long as we don't make his life harder. Wolfgang appears to be a
packager who would not make use of a split (and in fact cannot).
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 16:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 16:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 17:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-08 17:03 ` Bruce Momjian <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Bruce Momjian @ 2025-04-08 17:03 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Wolfgang Walther <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 10:00:56AM -0700, Jacob Champion wrote:
> On Tue, Apr 8, 2025 at 9:49 AM Bruce Momjian <[email protected]> wrote:
> > On Tue, Apr 8, 2025 at 09:43:01AM -0700, Jacob Champion wrote:
> > > By adding the new .so to a different package. For example, RPM specs
> > > would just let you say "hey, this .so I just built doesn't go into the
> > > main client package, it goes into an add-on that depends on the client
> > > package." It's the same way separate client and server packages get
> > > generated from the same single build of Postgres.
> >
> > Do we have any idea how many packagers are interested in doing this?
>
> I'm not sure how to answer this. The primary drivers from the dev side
> are you and Tom, I think. Christoph seems to be on board with a split
> as long as we don't make his life harder. Wolfgang appears to be a
> packager who would not make use of a split (and in fact cannot).
Okay, I have just posted a more detailed email about my security
concern, so let's look at that. I am ready to admit I am wrong.
--
Bruce Momjian <[email protected]> https://momjian.us
EDB https://enterprisedb.com
Do not let urgent matters crowd out time for investment in the future.
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 16:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
@ 2025-04-08 16:48 ` Bruce Momjian <[email protected]>
1 sibling, 0 replies; 194+ messages in thread
From: Bruce Momjian @ 2025-04-08 16:48 UTC (permalink / raw)
To: Daniel Gustafsson <[email protected]>; +Cc: Jacob Champion <[email protected]>; Wolfgang Walther <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 06:42:19PM +0200, Daniel Gustafsson wrote:
> > On 8 Apr 2025, at 18:33, Bruce Momjian <[email protected]> wrote:
>
> > Would we have to put out minor releases for curl CVEs? I don't think we
> > have to for OpenSSL so would curl be the same?
>
> Why do you envision this being different from all other dependencies we have?
> For OpenSSL we also happily build against a version (and until recently,
> several versions) which is EOL and don't even receive security fixes.
I don't know. I know people scan our downloads and report when the
scanners detect something, but I am unclear what those scanners are
doing. Would they see some new risks with curl?
--
Bruce Momjian <[email protected]> https://momjian.us
EDB https://enterprisedb.com
Do not let urgent matters crowd out time for investment in the future.
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-08 16:14 ` Jacob Champion <[email protected]>
2025-04-08 16:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2 siblings, 2 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-08 16:14 UTC (permalink / raw)
To: Wolfgang Walther <[email protected]>; +Cc: Bruce Momjian <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 9:02 AM Wolfgang Walther <[email protected]> wrote:
> How does the proposal with a loadable module affect a static libpq.a?
The currently proposed patch would have you package and install a
separate .so module implementing OAuth, which the staticlib would load
once when needed. Similarly to how you still have to somehow
dynamically link your static app against Curl.
As a staticlib user, how do you feel about that?
> I have not tried, yet, but is my assumption correct, that I could build
> a libpq.a with oauth/curl support on current HEAD?
Yes.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-08 16:50 ` Jacob Champion <[email protected]>
2025-04-08 17:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 17:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 2 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-08 16:50 UTC (permalink / raw)
To: Wolfgang Walther <[email protected]>; +Cc: Bruce Momjian <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 9:32 AM Wolfgang Walther <[email protected]> wrote:
> And that should also not be a problem for distributions - they could offer a libpq and a libpq_oauth package, where only one of them can be installed at the same time, I guess? *
My outsider understanding is that maintaining this sort of thing
becomes a major headache, because of combinatorics. You don't really
want to ship a libpq and libpq-with-gss and libpq-with-oauth and
libpq-with-oauth-and-gss and ...
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-08 17:02 ` Bruce Momjian <[email protected]>
2025-04-08 18:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Bruce Momjian @ 2025-04-08 17:02 UTC (permalink / raw)
To: Wolfgang Walther <[email protected]>; +Cc: Jacob Champion <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 06:57:18PM +0200, Wolfgang Walther wrote:
> Jacob Champion:
> > On Tue, Apr 8, 2025 at 9:32 AM Wolfgang Walther <[email protected]> wrote:
> > > And that should also not be a problem for distributions - they could offer a libpq and a libpq_oauth package, where only one of them can be installed at the same time, I guess? *
> > My outsider understanding is that maintaining this sort of thing
> > becomes a major headache, because of combinatorics. You don't really
> > want to ship a libpq and libpq-with-gss and libpq-with-oauth and
> > libpq-with-oauth-and-gss and ...
>
> That would only be the case, if you were to consider those other
> dependencies as "dangerous" as cURL. But we already depend on them. So if
> it's really the case that cURL is that much worse, that we consider loading
> it as a module... then the combinatorics should not be a problem either.
>
> However, if the other deps are considered problematic as well, then the ship
> has already sailed, and there is not point for a special case here anymore.
Yes, I think this is what I am asking too. For me it was curl's
security reputation and whether that would taint the security reputation
of libpq. For Tom, I think it was the dependency additions.
--
Bruce Momjian <[email protected]> https://momjian.us
EDB https://enterprisedb.com
Do not let urgent matters crowd out time for investment in the future.
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 17:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
@ 2025-04-08 18:11 ` Andres Freund <[email protected]>
2025-04-08 18:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Andres Freund @ 2025-04-08 18:11 UTC (permalink / raw)
To: Bruce Momjian <[email protected]>; +Cc: Wolfgang Walther <[email protected]>; Jacob Champion <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
Hi,
On 2025-04-08 13:02:11 -0400, Bruce Momjian wrote:
> On Tue, Apr 8, 2025 at 06:57:18PM +0200, Wolfgang Walther wrote:
> > Jacob Champion:
> > > On Tue, Apr 8, 2025 at 9:32 AM Wolfgang Walther <[email protected]> wrote:
> > > > And that should also not be a problem for distributions - they could offer a libpq and a libpq_oauth package, where only one of them can be installed at the same time, I guess? *
> > > My outsider understanding is that maintaining this sort of thing
> > > becomes a major headache, because of combinatorics. You don't really
> > > want to ship a libpq and libpq-with-gss and libpq-with-oauth and
> > > libpq-with-oauth-and-gss and ...
> >
> > That would only be the case, if you were to consider those other
> > dependencies as "dangerous" as cURL. But we already depend on them. So if
> > it's really the case that cURL is that much worse, that we consider loading
> > it as a module... then the combinatorics should not be a problem either.
> >
> > However, if the other deps are considered problematic as well, then the ship
> > has already sailed, and there is not point for a special case here anymore.
>
> Yes, I think this is what I am asking too. For me it was curl's
> security reputation and whether that would taint the security reputation
> of libpq. For Tom, I think it was the dependency additions.
I'd say that curl's security reputation is higher than most of our other
dependencies. We have dependencies for libraries with regular security issues,
with those issues at times not getting addressed for prolonged amounts of
time.
I do agree that there's an issue increasing libpq's indirect footprint
substantially, but I don't think that's due to curl's reputation or
anything. It's just needing a significantly higher number of shared libraries,
which comes with runtime and distribution overhead.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 17:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 18:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
@ 2025-04-08 18:25 ` Bruce Momjian <[email protected]>
2025-04-08 19:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Bruce Momjian @ 2025-04-08 18:25 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Wolfgang Walther <[email protected]>; Jacob Champion <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 02:11:19PM -0400, Andres Freund wrote:
> Hi,
>
> On 2025-04-08 13:02:11 -0400, Bruce Momjian wrote:
> > On Tue, Apr 8, 2025 at 06:57:18PM +0200, Wolfgang Walther wrote:
> > > Jacob Champion:
> > > > On Tue, Apr 8, 2025 at 9:32 AM Wolfgang Walther <[email protected]> wrote:
> > > > > And that should also not be a problem for distributions - they could offer a libpq and a libpq_oauth package, where only one of them can be installed at the same time, I guess? *
> > > > My outsider understanding is that maintaining this sort of thing
> > > > becomes a major headache, because of combinatorics. You don't really
> > > > want to ship a libpq and libpq-with-gss and libpq-with-oauth and
> > > > libpq-with-oauth-and-gss and ...
> > >
> > > That would only be the case, if you were to consider those other
> > > dependencies as "dangerous" as cURL. But we already depend on them. So if
> > > it's really the case that cURL is that much worse, that we consider loading
> > > it as a module... then the combinatorics should not be a problem either.
> > >
> > > However, if the other deps are considered problematic as well, then the ship
> > > has already sailed, and there is not point for a special case here anymore.
> >
> > Yes, I think this is what I am asking too. For me it was curl's
> > security reputation and whether that would taint the security reputation
> > of libpq. For Tom, I think it was the dependency additions.
>
> I'd say that curl's security reputation is higher than most of our other
> dependencies. We have dependencies for libraries with regular security issues,
> with those issues at times not getting addressed for prolonged amounts of
> time.
I see curl CVEs regularly as part of Debian minor updates, which is why
I had concerns, but if it is similar to OpenSSL, and better than other
libraries that don't even get CVEs, I guess it okay. However, is this
true for libpq libraries or database server libraries. Does it matter?
--
Bruce Momjian <[email protected]> https://momjian.us
EDB https://enterprisedb.com
Do not let urgent matters crowd out time for investment in the future.
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 17:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 18:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-08 18:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
@ 2025-04-08 19:22 ` Jacob Champion <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-08 19:22 UTC (permalink / raw)
To: Bruce Momjian <[email protected]>; +Cc: Andres Freund <[email protected]>; Wolfgang Walther <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 11:25 AM Bruce Momjian <[email protected]> wrote:
> However, is this
> true for libpq libraries or database server libraries. Does it matter?
The dependency on Curl is through libpq. We have some server-side
features that pull in libpq and would transitively depend on Curl. But
for Curl to be initialized server-side, the two peers still have to
agree on the use of OAuth.
It seems unlikely that users would opt into that for, say,
postgres_fdw in PG18, because the Device Authorization flow is the
only one we currently ship, and it's intended for end users. A flow
that prints a code to stderr is not very helpful for your proxy.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-08 17:04 ` Jacob Champion <[email protected]>
2025-04-08 17:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-08 17:04 UTC (permalink / raw)
To: Wolfgang Walther <[email protected]>; +Cc: Bruce Momjian <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 9:57 AM Wolfgang Walther <[email protected]> wrote:
> if it's really the case that cURL is that much worse
(it is not, but I am sympathetic to the argument that if you don't use
it, you don't need it in the process space)
> However, if the other deps are considered problematic as well, then the
> ship has already sailed, and there is not point for a special case here
> anymore.
I think this line of argument is unlikely to find traction. Upthread
there were people asking if we could maybe split out other
possibly-unused dependencies in the future, like Kerberos.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 17:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-08 17:13 ` Jacob Champion <[email protected]>
2025-04-08 17:15 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-08 17:13 UTC (permalink / raw)
To: Wolfgang Walther <[email protected]>; +Cc: Bruce Momjian <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 10:10 AM Wolfgang Walther
<[email protected]> wrote:
> And if that means making libpq modular at run-time, then this should be planned and built with all deps, and other use-cases (like static linking) in mind - and not like it is right now.
I think that'd be neat in concept, but specifically this thread is
discussing a PG18 open item. For future releases, if we're happy with
how Curl gets split out, maybe that would be fuel for other
delay-loaded client dependencies. I'm not sure.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 17:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 17:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-08 17:15 ` Bruce Momjian <[email protected]>
2025-04-08 17:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Bruce Momjian @ 2025-04-08 17:15 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Wolfgang Walther <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 10:13:46AM -0700, Jacob Champion wrote:
> On Tue, Apr 8, 2025 at 10:10 AM Wolfgang Walther
> <[email protected]> wrote:
> > And if that means making libpq modular at run-time, then this should be planned and built with all deps, and other use-cases (like static linking) in mind - and not like it is right now.
>
> I think that'd be neat in concept, but specifically this thread is
> discussing a PG18 open item. For future releases, if we're happy with
> how Curl gets split out, maybe that would be fuel for other
> delay-loaded client dependencies. I'm not sure.
Well, if we think we are going to do that, it seems we would need a
different architecture than the one being proposed for PG 18, which
could lead to a lot of user/developer API churn.
--
Bruce Momjian <[email protected]> https://momjian.us
EDB https://enterprisedb.com
Do not let urgent matters crowd out time for investment in the future.
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 17:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 17:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 17:15 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
@ 2025-04-08 17:19 ` Jacob Champion <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-08 17:19 UTC (permalink / raw)
To: Bruce Momjian <[email protected]>; +Cc: Wolfgang Walther <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 10:15 AM Bruce Momjian <[email protected]> wrote:
> Well, if we think we are going to do that, it seems we would need a
> different architecture than the one being proposed for PG 18, which
> could lead to a lot of user/developer API churn.
A major goal of the current patch proposal is to explicitly hide this
from the end-user and public APIs. So it can be changed without public
breakage. It can't be hidden from packagers, of course, but that's the
point of the feature request.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-08 17:44 ` Jacob Champion <[email protected]>
1 sibling, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-08 17:44 UTC (permalink / raw)
To: Wolfgang Walther <[email protected]>; +Cc: Bruce Momjian <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 9:32 AM Wolfgang Walther <[email protected]> wrote:
> This means that shipping another .so file will not happen with this approach. Assuming OAuth will be picked up by some of the bigger providers, that would... make me feel quite bad about it, actually.
It occurs to me that I didn't respond to this point explicitly. I
would like to avoid making your life harder.
Would anybody following along be opposed to a situation where
- dynamiclib builds go through the dlopen() shim
- staticlib builds always rely on statically linked symbols
Or do we need to be able to mix and match?
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-08 17:36 ` Jacob Champion <[email protected]>
2025-04-08 19:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-08 17:36 UTC (permalink / raw)
To: Daniel Gustafsson <[email protected]>; +Cc: Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 3:34 AM Daniel Gustafsson <[email protected]> wrote:
> > On 8 Apr 2025, at 04:10, Jacob Champion <[email protected]> wrote:
> > Hm, one immediate consequence of hardcoding pkglibdir is that we can
> > no longer rely on LD_LIBRARY_PATH for pre-installation testing.
> > (Contrast with the server, which is able to relocate extension paths
> > based on its executable location.)
>
> That strikes me as a signifant drawback.
Yeah, but it's one of those things that feels like it must have been
solved by the others in the space. Once it's installed, the concern
goes away (unless you demand absolute relocatability without
recompilation). I'll take a look at how libkrb/libmagick do their
testing.
If it somehow turns out to be impossible, one option might be to shove
a more detailed ABI identifier into the name. In other words, builds
without ENABLE_SSL/GSS/SSPI or whatever get different names on disk.
That doesn't scale at all, but it's a short-term option that would put
more pressure on a medium-term stable ABI.
Thanks,
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 17:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-08 19:07 ` Jacob Champion <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-08 19:07 UTC (permalink / raw)
To: Daniel Gustafsson <[email protected]>; +Cc: Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Tue, Apr 8, 2025 at 10:36 AM Jacob Champion
<[email protected]> wrote:
> Yeah, but it's one of those things that feels like it must have been
> solved by the others in the space. Once it's installed, the concern
> goes away (unless you demand absolute relocatability without
> recompilation). I'll take a look at how libkrb/libmagick do their
> testing.
Perhaps unsurprisingly, they inject different lookup paths via
envvars. We could do the same (I have FUD about the security
characteristics)...
> If it somehow turns out to be impossible, one option might be to shove
> a more detailed ABI identifier into the name.
...but I wonder if I can invert the dependency on
libpq_append_conn_error entirely, and remove that part of the ABI
surface, then revisit the discussion on `-<major>.so` vs
`-<major>-<minor>.so`.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-08 21:32 ` Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-08 21:32 UTC (permalink / raw)
To: Peter Eisentraut <[email protected]>; +Cc: Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Mon, Apr 7, 2025 at 9:41 AM Jacob Champion
<[email protected]> wrote:
> > Not sure, the code looks correct at first glance. However, you could
> > also just keep the libpq-oauth strings in the libpq catalog. There
> > isn't really a need to make a separate one, since the versions you end
> > up installing are locked to each other. So you could for example in
> > libpq's nls.mk just add
> >
> > ../libpq-oauth/oauth-curl.c
> >
> > etc. to the files.
>
> Oh, that's an interesting idea. Thanks, I'll give it a try.
A consequence of this is that our copy of libpq_binddomain isn't using
the same mutex as libpq's copy to protect the "libpq-18" message
domain. We could discuss whether or not it matters, since we don't
support Windows, but it doesn't feel architecturally sound to me. If
we want to reuse the same domain, I think the module should be using
libpq's libpq_gettext(). (Which we could do, again through the magic
of dependency injection.)
> > Maybe it would also make sense to make libpq-oauth a subdirectory of the
> > libpq directory instead of a peer.
>
> Works for me.
It does not, however, work for our $(recurse) setup in the makefiles
-- a shared library depending on a parent directory's shared library
leads to infinite recursion, with the current tools -- so I'll keep it
at the current directory level for now.
Thanks,
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-09 02:57 ` Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-09 02:57 UTC (permalink / raw)
To: Peter Eisentraut <[email protected]>; +Cc: Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>
On Tue, Apr 8, 2025 at 2:32 PM Jacob Champion
<[email protected]> wrote:
> I think the module should be using
> libpq's libpq_gettext(). (Which we could do, again through the magic
> of dependency injection.)
To illustrate what I mean, v3 introduces an initialization function
that names the three internal dependencies (libpq_gettext,
pg_g_threadlock, and conn->errorMessage) explicitly. I decided not to
attempt injecting the variadic libpq_append_conn_error function, and
instead focus a level below it, since we must depend directly on
libpq_gettext anyway.
This is maybe overkill, if it's decided that the two halves must
always come from the same build, but I think it should decouple the
two sides enough that this is now a question of user experience rather
than ABI correctness.
Is it acceptable/desirable for a build, which has not been configured
--with-libcurl, to still pick up a compatible OAuth implementation
installed by the distro? If so, we can go with a "bare" dlopen(). If
that's not okay, I think we will probably need to use pkglibdir or
some derivative, and introduce a way for tests (and users?) to
override that directory selection. Unless someone has a good idea on
how we can split the difference.
--Jacob
Attachments:
[application/octet-stream] v3-0001-WIP-split-Device-Authorization-flow-into-dlopen-d.patch (23.6K, ../../CAOYmi+=mGJfJUdz13Ty86-epEX4g9zRNVq0+i+KmHsodAJ2FdQ@mail.gmail.com/2-v3-0001-WIP-split-Device-Authorization-flow-into-dlopen-d.patch)
download | inline diff:
From 19e6887b43a3d907465b9e36816ef6ef235207a6 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Wed, 26 Mar 2025 10:55:28 -0700
Subject: [PATCH v3] WIP: split Device Authorization flow into dlopen'd module
See notes on mailing list.
Co-authored-by: Daniel Gustafsson <[email protected]>
---
meson.build | 12 +-
src/interfaces/Makefile | 12 ++
src/interfaces/libpq-oauth/Makefile | 55 +++++
src/interfaces/libpq-oauth/README | 30 +++
src/interfaces/libpq-oauth/exports.txt | 4 +
src/interfaces/libpq-oauth/meson.build | 31 +++
.../oauth-curl.c} | 10 +-
src/interfaces/libpq-oauth/oauth-curl.h | 24 +++
src/interfaces/libpq-oauth/oauth-utils.c | 201 ++++++++++++++++++
src/interfaces/libpq-oauth/oauth-utils.h | 35 +++
src/interfaces/libpq/Makefile | 4 -
src/interfaces/libpq/exports.txt | 1 +
src/interfaces/libpq/fe-auth-oauth.c | 94 +++++++-
src/interfaces/libpq/fe-auth-oauth.h | 4 +-
src/interfaces/libpq/meson.build | 4 -
src/interfaces/libpq/nls.mk | 12 +-
16 files changed, 502 insertions(+), 31 deletions(-)
create mode 100644 src/interfaces/libpq-oauth/Makefile
create mode 100644 src/interfaces/libpq-oauth/README
create mode 100644 src/interfaces/libpq-oauth/exports.txt
create mode 100644 src/interfaces/libpq-oauth/meson.build
rename src/interfaces/{libpq/fe-auth-oauth-curl.c => libpq-oauth/oauth-curl.c} (99%)
create mode 100644 src/interfaces/libpq-oauth/oauth-curl.h
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.c
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.h
diff --git a/meson.build b/meson.build
index 27717ad8976..6f1a8ea55ef 100644
--- a/meson.build
+++ b/meson.build
@@ -107,6 +107,7 @@ os_deps = []
backend_both_deps = []
backend_deps = []
libpq_deps = []
+libpq_oauth_deps = []
pg_sysroot = ''
@@ -3251,17 +3252,18 @@ libpq_deps += [
gssapi,
ldap_r,
- # XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
- # during gss_acquire_cred(). This is possibly related to Curl's Heimdal
- # dependency on that platform?
- libcurl,
libintl,
ssl,
]
+libpq_oauth_deps += [
+ libcurl,
+]
+
subdir('src/interfaces/libpq')
-# fe_utils depends on libpq
+# fe_utils and libpq-oauth depends on libpq
subdir('src/fe_utils')
+subdir('src/interfaces/libpq-oauth')
# for frontend binaries
frontend_code = declare_dependency(
diff --git a/src/interfaces/Makefile b/src/interfaces/Makefile
index 7d56b29d28f..e6822caa206 100644
--- a/src/interfaces/Makefile
+++ b/src/interfaces/Makefile
@@ -14,7 +14,19 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = libpq ecpg
+ifeq ($(with_libcurl), yes)
+SUBDIRS += libpq-oauth
+else
+ALWAYS_SUBDIRS += libpq-oauth
+endif
+
$(recurse)
+$(recurse_always)
all-ecpg-recurse: all-libpq-recurse
install-ecpg-recurse: install-libpq-recurse
+
+ifeq ($(with_libcurl), yes)
+all-libpq-oauth-recurse: all-libpq-recurse
+install-libpq-oauth-recurse: install-libpq-recurse
+endif
diff --git a/src/interfaces/libpq-oauth/Makefile b/src/interfaces/libpq-oauth/Makefile
new file mode 100644
index 00000000000..f44766dd549
--- /dev/null
+++ b/src/interfaces/libpq-oauth/Makefile
@@ -0,0 +1,55 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for libpq-oauth
+#
+# Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/interfaces/libpq-oauth/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/interfaces/libpq-oauth
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+PGFILEDESC = "libpq-oauth - device authorization OAuth support"
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
+NAME = libpq-oauth-$(MAJORVERSION)
+
+override CPPFLAGS := -I$(libpq_srcdir) -I$(top_builddir)/src/port $(CPPFLAGS)
+
+OBJS = \
+ $(WIN32RES) \
+ oauth-curl.o \
+ oauth-utils.o
+
+SHLIB_LINK_INTERNAL = $(libpq_pgport_shlib)
+SHLIB_LINK = -lcurl $(filter -lintl, $(LIBS))
+SHLIB_PREREQS = submake-libpq
+SHLIB_EXPORTS = exports.txt
+
+# Disable -bundle_loader on macOS.
+BE_DLLLIBS =
+
+# Make dependencies on pg_config_paths.h visible in all builds.
+oauth-curl.o: oauth-curl.c $(top_builddir)/src/port/pg_config_paths.h
+
+$(top_builddir)/src/port/pg_config_paths.h:
+ $(MAKE) -C $(top_builddir)/src/port pg_config_paths.h
+
+all: all-lib
+
+# Shared library stuff
+include $(top_srcdir)/src/Makefile.shlib
+
+install: all installdirs install-lib
+
+installdirs: installdirs-lib
+
+uninstall: uninstall-lib
+
+clean distclean: clean-lib
+ rm -f $(OBJS)
diff --git a/src/interfaces/libpq-oauth/README b/src/interfaces/libpq-oauth/README
new file mode 100644
index 00000000000..ef746617c71
--- /dev/null
+++ b/src/interfaces/libpq-oauth/README
@@ -0,0 +1,30 @@
+libpq-oauth is an optional module implementing the Device Authorization flow for
+OAuth clients (RFC 8628). It was originally developed as part of libpq core and
+later split out as its own shared library in order to isolate its dependency on
+libcurl. (End users who don't want the Curl dependency can simply choose not to
+install this module.)
+
+If a connection string allows the use of OAuth, the server asks for it, and a
+libpq client has not installed its own custom OAuth flow, libpq will attempt to
+delay-load this module using dlopen() and the following ABI. Failure to load
+results in a failed connection.
+
+= Load-Time ABI =
+
+This module ABI is an internal implementation detail, so it's subject to change
+without warning, even during minor releases (however unlikely). The compiled
+version of libpq-oauth should always match the compiled version of libpq.
+
+- PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+- void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+pg_fe_run_oauth_flow and pg_fe_cleanup_oauth_flow are implementations of
+conn->async_auth and conn->cleanup_async_auth, respectively.
+
+- void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+
+At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
+libpq_gettext(), which must be injected by libpq before the flow is run. It also
+relies on libpq to expose conn->errorMessage, via an errmsg_impl.
diff --git a/src/interfaces/libpq-oauth/exports.txt b/src/interfaces/libpq-oauth/exports.txt
new file mode 100644
index 00000000000..6891a83dbf9
--- /dev/null
+++ b/src/interfaces/libpq-oauth/exports.txt
@@ -0,0 +1,4 @@
+# src/interfaces/libpq-oauth/exports.txt
+libpq_oauth_init 1
+pg_fe_run_oauth_flow 2
+pg_fe_cleanup_oauth_flow 3
diff --git a/src/interfaces/libpq-oauth/meson.build b/src/interfaces/libpq-oauth/meson.build
new file mode 100644
index 00000000000..79916e7aa62
--- /dev/null
+++ b/src/interfaces/libpq-oauth/meson.build
@@ -0,0 +1,31 @@
+# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+
+if not libcurl.found() or host_system == 'windows'
+ subdir_done()
+endif
+
+libpq_oauth_sources = files(
+ 'oauth-curl.c',
+ 'oauth-utils.c',
+)
+
+export_file = custom_target('libpq-oauth.exports',
+ kwargs: gen_export_kwargs,
+)
+
+# port needs to be in include path due to pthread-win32.h
+libpq_oauth_inc = include_directories('.', '../libpq', '../../port')
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
+libpq_oauth_name = 'libpq-oauth-@0@'.format(pg_version_major)
+
+libpq_oauth_so = shared_module(libpq_oauth_name,
+ libpq_oauth_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_shlib_code, libpq, libpq_oauth_deps],
+ link_depends: export_file,
+ link_args: export_fmt.format(export_file.full_path()),
+ kwargs: default_lib_args,
+)
diff --git a/src/interfaces/libpq/fe-auth-oauth-curl.c b/src/interfaces/libpq-oauth/oauth-curl.c
similarity index 99%
rename from src/interfaces/libpq/fe-auth-oauth-curl.c
rename to src/interfaces/libpq-oauth/oauth-curl.c
index cd9c0323bb6..759cd494aae 100644
--- a/src/interfaces/libpq/fe-auth-oauth-curl.c
+++ b/src/interfaces/libpq-oauth/oauth-curl.c
@@ -1,6 +1,6 @@
/*-------------------------------------------------------------------------
*
- * fe-auth-oauth-curl.c
+ * oauth-curl.c
* The libcurl implementation of OAuth/OIDC authentication, using the
* OAuth Device Authorization Grant (RFC 8628).
*
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * src/interfaces/libpq/fe-auth-oauth-curl.c
+ * src/interfaces/libpq-oauth/oauth-curl.c
*
*-------------------------------------------------------------------------
*/
@@ -29,8 +29,9 @@
#include "common/jsonapi.h"
#include "fe-auth.h"
#include "fe-auth-oauth.h"
-#include "libpq-int.h"
#include "mb/pg_wchar.h"
+#include "oauth-curl.h"
+#include "oauth-utils.h"
/*
* It's generally prudent to set a maximum response size to buffer in memory,
@@ -2487,8 +2488,9 @@ prompt_user(struct async_ctx *actx, PGconn *conn)
.verification_uri_complete = actx->authz.verification_uri_complete,
.expires_in = actx->authz.expires_in,
};
+ PQauthDataHook_type hook = PQgetAuthDataHook();
- res = PQauthDataHook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
+ res = hook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
if (!res)
{
diff --git a/src/interfaces/libpq-oauth/oauth-curl.h b/src/interfaces/libpq-oauth/oauth-curl.h
new file mode 100644
index 00000000000..248d0424ad0
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-curl.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-curl.h
+ *
+ * Definitions for OAuth Device Authorization module
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-curl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_CURL_H
+#define OAUTH_CURL_H
+
+#include "libpq-fe.h"
+
+/* Exported async-auth callbacks. */
+extern PGDLLEXPORT PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+extern PGDLLEXPORT void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+#endif /* OAUTH_CURL_H */
diff --git a/src/interfaces/libpq-oauth/oauth-utils.c b/src/interfaces/libpq-oauth/oauth-utils.c
new file mode 100644
index 00000000000..7a0949c071b
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.c
@@ -0,0 +1,201 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.c
+ *
+ * "Glue" helpers providing a copy of some internal APIs from libpq. At
+ * some point in the future, we might be able to deduplicate.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq-oauth/oauth-utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+
+#include "libpq-int.h"
+#include "oauth-utils.h"
+
+pgthreadlock_t pg_g_threadlock;
+libpq_gettext_func libpq_gettext_impl;
+conn_errorMessage_func conn_errorMessage;
+
+/*-
+ * Initializes libpq-oauth by setting necessary callbacks.
+ *
+ * The current implementation relies on the following private implementation
+ * details of libpq:
+ *
+ * - pg_g_threadlock: protects libcurl initialization if the underlying Curl
+ * installation is not threadsafe
+ *
+ * - libpq_gettext: translates error messages using libpq's message domain
+ *
+ * - conn->errorMessage: holds translated errors for the connection. This is
+ * handled through a translation shim, which avoids either depending on the
+ * offset of the errorMessage in PGconn, or needing to export the variadic
+ * libpq_append_conn_error().
+ */
+void
+libpq_oauth_init(pgthreadlock_t threadlock_impl,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl)
+{
+ pg_g_threadlock = threadlock_impl;
+ libpq_gettext_impl = gettext_impl;
+ conn_errorMessage = errmsg_impl;
+}
+
+/*
+ * Append a formatted string to the error message buffer of the given
+ * connection, after translating it. This is a copy of libpq's internal API.
+ */
+void
+libpq_append_conn_error(PGconn *conn, const char *fmt,...)
+{
+ int save_errno = errno;
+ bool done;
+ va_list args;
+ PQExpBuffer errorMessage = conn_errorMessage(conn);
+
+ Assert(fmt[strlen(fmt) - 1] != '\n');
+
+ if (PQExpBufferBroken(errorMessage))
+ return; /* already failed */
+
+ /* Loop in case we have to retry after enlarging the buffer. */
+ do
+ {
+ errno = save_errno;
+ va_start(args, fmt);
+ done = appendPQExpBufferVA(errorMessage, libpq_gettext(fmt), args);
+ va_end(args);
+ } while (!done);
+
+ appendPQExpBufferChar(errorMessage, '\n');
+}
+
+#ifdef ENABLE_NLS
+
+/*
+ * A shim that defers to the actual libpq_gettext().
+ */
+char *
+libpq_gettext(const char *msgid)
+{
+ if (!libpq_gettext_impl)
+ {
+ /*
+ * Possible if the libpq build doesn't enable NLS. That's a concerning
+ * mismatch, but in this particular case we can handle it. Try to warn
+ * a developer with an assertion, though.
+ */
+ Assert(false);
+
+ /*
+ * Note that callers of libpq_gettext() have to treat the return value
+ * as if it were const, because builds without NLS simply pass through
+ * their argument.
+ */
+ return unconstify(char *, msgid);
+ }
+
+ return libpq_gettext_impl(msgid);
+}
+
+#endif /* ENABLE_NLS */
+
+/*
+ * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
+ */
+bool
+oauth_unsafe_debugging_enabled(void)
+{
+ const char *env = getenv("PGOAUTHDEBUG");
+
+ return (env && strcmp(env, "UNSAFE") == 0);
+}
+
+/*
+ * Duplicate SOCK_ERRNO* definitions from libpq-int.h, for use by
+ * pq_block/reset_sigpipe().
+ */
+#ifdef WIN32
+#define SOCK_ERRNO (WSAGetLastError())
+#define SOCK_ERRNO_SET(e) WSASetLastError(e)
+#else
+#define SOCK_ERRNO errno
+#define SOCK_ERRNO_SET(e) (errno = (e))
+#endif
+
+/*
+ * Block SIGPIPE for this thread. This is a copy of libpq's internal API.
+ */
+int
+pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
+{
+ sigset_t sigpipe_sigset;
+ sigset_t sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ /* Block SIGPIPE and save previous mask for later reset */
+ SOCK_ERRNO_SET(pthread_sigmask(SIG_BLOCK, &sigpipe_sigset, osigset));
+ if (SOCK_ERRNO)
+ return -1;
+
+ /* We can have a pending SIGPIPE only if it was blocked before */
+ if (sigismember(osigset, SIGPIPE))
+ {
+ /* Is there a pending SIGPIPE? */
+ if (sigpending(&sigset) != 0)
+ return -1;
+
+ if (sigismember(&sigset, SIGPIPE))
+ *sigpipe_pending = true;
+ else
+ *sigpipe_pending = false;
+ }
+ else
+ *sigpipe_pending = false;
+
+ return 0;
+}
+
+/*
+ * Discard any pending SIGPIPE and reset the signal mask. This is a copy of
+ * libpq's internal API.
+ */
+void
+pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
+{
+ int save_errno = SOCK_ERRNO;
+ int signo;
+ sigset_t sigset;
+
+ /* Clear SIGPIPE only if none was pending */
+ if (got_epipe && !sigpipe_pending)
+ {
+ if (sigpending(&sigset) == 0 &&
+ sigismember(&sigset, SIGPIPE))
+ {
+ sigset_t sigpipe_sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ sigwait(&sigpipe_sigset, &signo);
+ }
+ }
+
+ /* Restore saved block mask */
+ pthread_sigmask(SIG_SETMASK, osigset, NULL);
+
+ SOCK_ERRNO_SET(save_errno);
+}
diff --git a/src/interfaces/libpq-oauth/oauth-utils.h b/src/interfaces/libpq-oauth/oauth-utils.h
new file mode 100644
index 00000000000..279fc113248
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.h
+ *
+ * Definitions providing missing libpq internal APIs
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_UTILS_H
+#define OAUTH_UTILS_H
+
+#include "libpq-fe.h"
+#include "pqexpbuffer.h"
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/* Initializes libpq-oauth. */
+extern PGDLLEXPORT void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+
+/* Duplicated APIs, copied from libpq. */
+extern void libpq_append_conn_error(PGconn *conn, const char *fmt,...) pg_attribute_printf(2, 3);
+extern bool oauth_unsafe_debugging_enabled(void);
+extern int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
+extern void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe);
+
+#endif /* OAUTH_UTILS_H */
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 90b0b65db6f..8cf8d9e54d8 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -64,10 +64,6 @@ OBJS += \
fe-secure-gssapi.o
endif
-ifeq ($(with_libcurl),yes)
-OBJS += fe-auth-oauth-curl.o
-endif
-
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index d5143766858..0625cf39e9a 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -210,3 +210,4 @@ PQsetAuthDataHook 207
PQgetAuthDataHook 208
PQdefaultAuthDataHook 209
PQfullProtocolVersion 210
+appendPQExpBufferVA 211
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
index cf1a25e2ccc..416964ac335 100644
--- a/src/interfaces/libpq/fe-auth-oauth.c
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -15,6 +15,10 @@
#include "postgres_fe.h"
+#ifndef WIN32
+#include <dlfcn.h>
+#endif
+
#include "common/base64.h"
#include "common/hmac.h"
#include "common/jsonapi.h"
@@ -22,6 +26,7 @@
#include "fe-auth.h"
#include "fe-auth-oauth.h"
#include "mb/pg_wchar.h"
+#include "pg_config_paths.h"
/* The exported OAuth callback mechanism. */
static void *oauth_init(PGconn *conn, const char *password,
@@ -721,6 +726,85 @@ cleanup_user_oauth_flow(PGconn *conn)
state->async_ctx = NULL;
}
+typedef char *(*libpq_gettext_func) (const char *msgid);
+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/*
+ * This shim is injected into libpq-oauth so that it doesn't depend on the
+ * offset of conn->errorMessage.
+ *
+ * TODO: look into exporting libpq_append_conn_error or a comparable API from
+ * libpq, instead.
+ */
+static PQExpBuffer
+conn_errorMessage(PGconn *conn)
+{
+ return &conn->errorMessage;
+}
+
+static bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+#ifdef WIN32
+ return false;
+#else
+ void (*init) (pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+ PostgresPollingStatusType (*flow) (PGconn *conn);
+ void (*cleanup) (PGconn *conn);
+
+ state->builtin_flow = dlopen("libpq-oauth-" PG_MAJORVERSION DLSUFFIX,
+ RTLD_NOW | RTLD_LOCAL);
+ if (!state->builtin_flow)
+ {
+ /*
+ * For end users, this probably isn't an error condition, it just
+ * means the flow isn't installed. Developers and package maintainers
+ * may want to debug this via the PGOAUTHDEBUG envvar, though.
+ *
+ * Note that POSIX dlerror() isn't guaranteed to be threadsafe.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
+
+ return false;
+ }
+
+ if ((init = dlsym(state->builtin_flow, "libpq_oauth_init")) == NULL
+ || (flow = dlsym(state->builtin_flow, "pg_fe_run_oauth_flow")) == NULL
+ || (cleanup = dlsym(state->builtin_flow, "pg_fe_cleanup_oauth_flow")) == NULL)
+ {
+ /*
+ * This is more of an error condition than the one above, but due to
+ * the dlerror() threadsafety issue, lock it behind PGOAUTHDEBUG too.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
+
+ dlclose(state->builtin_flow);
+ return false;
+ }
+
+ /*
+ * Inject necessary function pointers into the module.
+ */
+ init(pg_g_threadlock,
+#ifdef ENABLE_NLS
+ libpq_gettext,
+#else
+ NULL,
+#endif
+ conn_errorMessage);
+
+ /* Set our asynchronous callbacks. */
+ conn->async_auth = flow;
+ conn->cleanup_async_auth = cleanup;
+
+ return true;
+#endif /* !WIN32 */
+}
+
/*
* Chooses an OAuth client flow for the connection, which will retrieve a Bearer
* token for presentation to the server.
@@ -792,18 +876,10 @@ setup_token_request(PGconn *conn, fe_oauth_state *state)
libpq_append_conn_error(conn, "user-defined OAuth flow failed");
goto fail;
}
- else
+ else if (!use_builtin_flow(conn, state))
{
-#if USE_LIBCURL
- /* Hand off to our built-in OAuth flow. */
- conn->async_auth = pg_fe_run_oauth_flow;
- conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
-
-#else
libpq_append_conn_error(conn, "no custom OAuth flows are available, and libpq was not built with libcurl support");
goto fail;
-
-#endif
}
return true;
diff --git a/src/interfaces/libpq/fe-auth-oauth.h b/src/interfaces/libpq/fe-auth-oauth.h
index 3f1a7503a01..699ba42acc2 100644
--- a/src/interfaces/libpq/fe-auth-oauth.h
+++ b/src/interfaces/libpq/fe-auth-oauth.h
@@ -33,10 +33,10 @@ typedef struct
PGconn *conn;
void *async_ctx;
+
+ void *builtin_flow;
} fe_oauth_state;
-extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
-extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
extern void pqClearOAuthToken(PGconn *conn);
extern bool oauth_unsafe_debugging_enabled(void);
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index 292fecf3320..47d38e9378f 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -38,10 +38,6 @@ if gssapi.found()
)
endif
-if libcurl.found()
- libpq_sources += files('fe-auth-oauth-curl.c')
-endif
-
export_file = custom_target('libpq.exports',
kwargs: gen_export_kwargs,
)
diff --git a/src/interfaces/libpq/nls.mk b/src/interfaces/libpq/nls.mk
index ae761265852..b87df277d93 100644
--- a/src/interfaces/libpq/nls.mk
+++ b/src/interfaces/libpq/nls.mk
@@ -13,15 +13,21 @@ GETTEXT_FILES = fe-auth.c \
fe-secure-common.c \
fe-secure-gssapi.c \
fe-secure-openssl.c \
- win32.c
-GETTEXT_TRIGGERS = libpq_append_conn_error:2 \
+ win32.c \
+ ../libpq-oauth/oauth-curl.c \
+ ../libpq-oauth/oauth-utils.c
+GETTEXT_TRIGGERS = actx_error:2 \
+ libpq_append_conn_error:2 \
libpq_append_error:2 \
libpq_gettext \
libpq_ngettext:1,2 \
+ oauth_parse_set_error:2 \
pqInternalNotice:2
-GETTEXT_FLAGS = libpq_append_conn_error:2:c-format \
+GETTEXT_FLAGS = actx_error:2:c-format \
+ libpq_append_conn_error:2:c-format \
libpq_append_error:2:c-format \
libpq_gettext:1:pass-c-format \
libpq_ngettext:1:pass-c-format \
libpq_ngettext:2:pass-c-format \
+ oauth_parse_set_error:2:c-format \
pqInternalNotice:2:c-format
--
2.34.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-09 08:14 ` Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Christoph Berg @ 2025-04-09 08:14 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>
Re: Jacob Champion
> Is it acceptable/desirable for a build, which has not been configured
> --with-libcurl, to still pick up a compatible OAuth implementation
> installed by the distro? If so, we can go with a "bare" dlopen(). If
> that's not okay, I think we will probably need to use pkglibdir or
> some derivative, and introduce a way for tests (and users?) to
> override that directory selection. Unless someone has a good idea on
> how we can split the difference.
One design goal could be reproducible builds-alike, that is, have
libpq configured with or without libcurl be completely identical, and
the feature being present is simply the libpq-oauth.so file existing
or not. That might make using plain dlopen() more attractive.
Christoph
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
@ 2025-04-09 12:50 ` Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-09 12:50 UTC (permalink / raw)
To: Christoph Berg <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Jelte Fennema-Nio <[email protected]>
On Wed, Apr 9, 2025 at 1:14 AM Christoph Berg <[email protected]> wrote:
> One design goal could be reproducible builds-alike, that is, have
> libpq configured with or without libcurl be completely identical, and
> the feature being present is simply the libpq-oauth.so file existing
> or not. That might make using plain dlopen() more attractive.
I think that's more or less what the current v3 does, but Jelte's
point (which is upthread for me but downthread for others :D) is that
making them identical might not actually be a desirable thing in this
situation, because if you don't compile --with-libcurl, when you test
that the feature is disabled you might potentially find that it is
not.
On Wed, Apr 9, 2025 at 1:39 AM Jelte Fennema-Nio <[email protected]> wrote:
> How about ifdef-ing away the dlopen call if --with-libcurl is not specified.
This sounds like a pretty decent, simple way to go. Christoph, does
that ring any alarm bells from your perspective?
Thanks,
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-09 17:44 ` Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Christoph Berg @ 2025-04-09 17:44 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Jelte Fennema-Nio <[email protected]>
Re: Jacob Champion
> > How about ifdef-ing away the dlopen call if --with-libcurl is not specified.
>
> This sounds like a pretty decent, simple way to go. Christoph, does
> that ring any alarm bells from your perspective?
Ok for me. The opposite that I said in the other mail was just a
suggestion.
Christoph
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
@ 2025-04-09 23:08 ` Jacob Champion <[email protected]>
2025-04-11 00:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
0 siblings, 2 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-09 23:08 UTC (permalink / raw)
To: Christoph Berg <[email protected]>; Jacob Champion <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Jelte Fennema-Nio <[email protected]>
On Wed, Apr 9, 2025 at 10:44 AM Christoph Berg <[email protected]> wrote:
>
> Re: Jacob Champion
> > > How about ifdef-ing away the dlopen call if --with-libcurl is not specified.
> >
> > This sounds like a pretty decent, simple way to go. Christoph, does
> > that ring any alarm bells from your perspective?
>
> Ok for me. The opposite that I said in the other mail was just a
> suggestion.
Cool, thanks! v4 does it that way. It also errors out at configure
time if you demand libpq-oauth on a platform that does not support it.
The Autoconf side was still polluting LIBS and CPPFLAGS with Curl
stuff. I have isolated them in v4, with some additional m4
boilerplate. IMO this makes the subtle difference between USE_LIBCURL
(which means the libpq-oauth module is enabled to build) and
HAVE_LIBCURL (which means you have libcurl installed locally) even
more confusing.
Christoph noted that this was also confusing from the packaging side,
earlier, and Daniel proposed -Doauth-client/--with-oauth-client as the
feature switch name instead. Any objections? Unfortunately it would
mean a buildfarm email is in order, so we should get it locked in.
Next up: staticlibs.
Thanks,
--Jacob
Attachments:
[application/octet-stream] v4-0001-WIP-split-Device-Authorization-flow-into-dlopen-d.patch (37.5K, ../../CAOYmi+n9DHS_xUatuuspdC8tjtaMzY8P11Y9y5Fz+2pjikkL9g@mail.gmail.com/2-v4-0001-WIP-split-Device-Authorization-flow-into-dlopen-d.patch)
download | inline diff:
From c500ad9f5653f02440449404ba1a975dab6ddfe6 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Wed, 26 Mar 2025 10:55:28 -0700
Subject: [PATCH v4] WIP: split Device Authorization flow into dlopen'd module
See notes on mailing list.
Co-authored-by: Daniel Gustafsson <[email protected]>
---
config/programs.m4 | 17 +-
configure | 50 ++++-
configure.ac | 26 ++-
meson.build | 22 +-
src/Makefile.global.in | 3 +
src/interfaces/Makefile | 12 ++
src/interfaces/libpq-oauth/Makefile | 55 +++++
src/interfaces/libpq-oauth/README | 30 +++
src/interfaces/libpq-oauth/exports.txt | 4 +
src/interfaces/libpq-oauth/meson.build | 42 ++++
.../oauth-curl.c} | 60 +++---
src/interfaces/libpq-oauth/oauth-curl.h | 24 +++
src/interfaces/libpq-oauth/oauth-utils.c | 202 ++++++++++++++++++
src/interfaces/libpq-oauth/oauth-utils.h | 35 +++
src/interfaces/libpq/Makefile | 10 +-
src/interfaces/libpq/exports.txt | 1 +
src/interfaces/libpq/fe-auth-oauth.c | 102 ++++++++-
src/interfaces/libpq/fe-auth-oauth.h | 4 +-
src/interfaces/libpq/meson.build | 4 -
src/interfaces/libpq/nls.mk | 12 +-
src/makefiles/meson.build | 2 +
21 files changed, 635 insertions(+), 82 deletions(-)
create mode 100644 src/interfaces/libpq-oauth/Makefile
create mode 100644 src/interfaces/libpq-oauth/README
create mode 100644 src/interfaces/libpq-oauth/exports.txt
create mode 100644 src/interfaces/libpq-oauth/meson.build
rename src/interfaces/{libpq/fe-auth-oauth-curl.c => libpq-oauth/oauth-curl.c} (98%)
create mode 100644 src/interfaces/libpq-oauth/oauth-curl.h
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.c
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.h
diff --git a/config/programs.m4 b/config/programs.m4
index 0a07feb37cc..0ad1e58b48d 100644
--- a/config/programs.m4
+++ b/config/programs.m4
@@ -286,9 +286,20 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
[
AC_CHECK_HEADER(curl/curl.h, [],
[AC_MSG_ERROR([header file <curl/curl.h> is required for --with-libcurl])])
- AC_CHECK_LIB(curl, curl_multi_init, [],
+ AC_CHECK_LIB(curl, curl_multi_init, [
+ AC_DEFINE([HAVE_LIBCURL], [1], [Define to 1 if you have the `curl' library (-lcurl).])
+ AC_SUBST(LIBCURL_LDLIBS, -lcurl)
+ ],
[AC_MSG_ERROR([library 'curl' does not provide curl_multi_init])])
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+ LIBS="$LIBCURL_LDLIBS $LIBS"
+
# Check to see whether the current platform supports threadsafe Curl
# initialization.
AC_CACHE_CHECK([for curl_global_init thread safety], [pgac_cv__libcurl_threadsafe_init],
@@ -338,4 +349,8 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
*** lookups. Rebuild libcurl with the AsynchDNS feature enabled in order
*** to use it with libpq.])
fi
+
+ CPPFLAGS=$pgac_save_CPPFLAGS
+ LDFLAGS=$pgac_save_LDFLAGS
+ LIBS=$pgac_save_LIBS
])# PGAC_CHECK_LIBCURL
diff --git a/configure b/configure
index 8f4a5ab28ec..df1da549c4c 100755
--- a/configure
+++ b/configure
@@ -655,6 +655,7 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+LIBCURL_LDLIBS
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -708,6 +709,8 @@ XML2_LIBS
XML2_CFLAGS
XML2_CONFIG
with_libxml
+LIBCURL_LDFLAGS
+LIBCURL_CPPFLAGS
LIBCURL_LIBS
LIBCURL_CFLAGS
with_libcurl
@@ -9042,19 +9045,27 @@ $as_echo "yes" >&6; }
fi
- # We only care about -I, -D, and -L switches;
- # note that -lcurl will be added by PGAC_CHECK_LIBCURL below.
+ # Curl's flags are kept separate from the standard CPPFLAGS/LDFLAGS. We use
+ # them only for libpq-oauth.
+ LIBCURL_CPPFLAGS=
+ LIBCURL_LDFLAGS=
+
+ # We only care about -I, -D, and -L switches. Note that -lcurl will be added
+ # to LIBCURL_LDLIBS by PGAC_CHECK_LIBCURL, below.
for pgac_option in $LIBCURL_CFLAGS; do
case $pgac_option in
- -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+ -I*|-D*) LIBCURL_CPPFLAGS="$LIBCURL_CPPFLAGS $pgac_option";;
esac
done
for pgac_option in $LIBCURL_LIBS; do
case $pgac_option in
- -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+ -L*) LIBCURL_LDFLAGS="$LIBCURL_LDFLAGS $pgac_option";;
esac
done
+
+
+
# OAuth requires python for testing
if test "$with_python" != yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: *** OAuth support tests require --with-python to run" >&5
@@ -12517,9 +12528,6 @@ fi
fi
-# XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
-# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
-# dependency on that platform?
if test "$with_libcurl" = yes ; then
ac_fn_c_check_header_mongrel "$LINENO" "curl/curl.h" "ac_cv_header_curl_curl_h" "$ac_includes_default"
@@ -12567,17 +12575,26 @@ fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_curl_curl_multi_init" >&5
$as_echo "$ac_cv_lib_curl_curl_multi_init" >&6; }
if test "x$ac_cv_lib_curl_curl_multi_init" = xyes; then :
- cat >>confdefs.h <<_ACEOF
-#define HAVE_LIBCURL 1
-_ACEOF
- LIBS="-lcurl $LIBS"
+
+$as_echo "#define HAVE_LIBCURL 1" >>confdefs.h
+
+ LIBCURL_LDLIBS=-lcurl
+
else
as_fn_error $? "library 'curl' does not provide curl_multi_init" "$LINENO" 5
fi
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+ LIBS="$LIBCURL_LDLIBS $LIBS"
+
# Check to see whether the current platform supports threadsafe Curl
# initialization.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_global_init thread safety" >&5
@@ -12681,6 +12698,10 @@ $as_echo "$pgac_cv__libcurl_async_dns" >&6; }
*** to use it with libpq." "$LINENO" 5
fi
+ CPPFLAGS=$pgac_save_CPPFLAGS
+ LDFLAGS=$pgac_save_LDFLAGS
+ LIBS=$pgac_save_LIBS
+
fi
if test "$with_gssapi" = yes ; then
@@ -14329,6 +14350,13 @@ done
fi
+if test "$with_libcurl" = yes ; then
+ # Error out early if this platform can't support libpq-oauth.
+ if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
+ as_fn_error $? "client OAuth is not supported on this platform" "$LINENO" 5
+ fi
+fi
+
##
## Types, structures, compiler characteristics
##
diff --git a/configure.ac b/configure.ac
index fc5f7475d07..218aeea1b3b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1033,19 +1033,27 @@ if test "$with_libcurl" = yes ; then
# to explicitly set TLS 1.3 ciphersuites).
PKG_CHECK_MODULES(LIBCURL, [libcurl >= 7.61.0])
- # We only care about -I, -D, and -L switches;
- # note that -lcurl will be added by PGAC_CHECK_LIBCURL below.
+ # Curl's flags are kept separate from the standard CPPFLAGS/LDFLAGS. We use
+ # them only for libpq-oauth.
+ LIBCURL_CPPFLAGS=
+ LIBCURL_LDFLAGS=
+
+ # We only care about -I, -D, and -L switches. Note that -lcurl will be added
+ # to LIBCURL_LDLIBS by PGAC_CHECK_LIBCURL, below.
for pgac_option in $LIBCURL_CFLAGS; do
case $pgac_option in
- -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+ -I*|-D*) LIBCURL_CPPFLAGS="$LIBCURL_CPPFLAGS $pgac_option";;
esac
done
for pgac_option in $LIBCURL_LIBS; do
case $pgac_option in
- -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+ -L*) LIBCURL_LDFLAGS="$LIBCURL_LDFLAGS $pgac_option";;
esac
done
+ AC_SUBST(LIBCURL_CPPFLAGS)
+ AC_SUBST(LIBCURL_LDFLAGS)
+
# OAuth requires python for testing
if test "$with_python" != yes; then
AC_MSG_WARN([*** OAuth support tests require --with-python to run])
@@ -1340,9 +1348,6 @@ failure. It is possible the compiler isn't looking in the proper directory.
Use --without-zlib to disable zlib support.])])
fi
-# XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
-# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
-# dependency on that platform?
if test "$with_libcurl" = yes ; then
PGAC_CHECK_LIBCURL
fi
@@ -1640,6 +1645,13 @@ if test "$PORTNAME" = "win32" ; then
AC_CHECK_HEADERS(crtdefs.h)
fi
+if test "$with_libcurl" = yes ; then
+ # Error out early if this platform can't support libpq-oauth.
+ if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
+ AC_MSG_ERROR([client OAuth is not supported on this platform])
+ fi
+fi
+
##
## Types, structures, compiler characteristics
##
diff --git a/meson.build b/meson.build
index 27717ad8976..0f0df9b1af4 100644
--- a/meson.build
+++ b/meson.build
@@ -107,6 +107,7 @@ os_deps = []
backend_both_deps = []
backend_deps = []
libpq_deps = []
+libpq_oauth_deps = []
pg_sysroot = ''
@@ -2587,6 +2588,7 @@ header_checks = [
'xlocale.h',
]
+header_macros = {}
foreach header : header_checks
varname = 'HAVE_' + header.underscorify().to_upper()
@@ -2595,6 +2597,15 @@ foreach header : header_checks
include_directories: postgres_inc, args: test_c_args)
cdata.set(varname, found ? 1 : false,
description: 'Define to 1 if you have the <@0@> header file.'.format(header))
+
+ # Mixing 1/false in cdata means we can't perform equality checks using
+ # cdata.get(), though, so store our defined header macros for later lookup.
+ #
+ # https://github.com/mesonbuild/meson/issues/11581
+ #
+ if found
+ header_macros += {varname: true}
+ endif
endforeach
@@ -3251,17 +3262,18 @@ libpq_deps += [
gssapi,
ldap_r,
- # XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
- # during gss_acquire_cred(). This is possibly related to Curl's Heimdal
- # dependency on that platform?
- libcurl,
libintl,
ssl,
]
+libpq_oauth_deps += [
+ libcurl,
+]
+
subdir('src/interfaces/libpq')
-# fe_utils depends on libpq
+# fe_utils and libpq-oauth depends on libpq
subdir('src/fe_utils')
+subdir('src/interfaces/libpq-oauth')
# for frontend binaries
frontend_code = declare_dependency(
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 737b2dd1869..eb9b5de75b4 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -343,6 +343,9 @@ perl_embed_ldflags = @perl_embed_ldflags@
AWK = @AWK@
LN_S = @LN_S@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBCURL_LDFLAGS = @LIBCURL_LDFLAGS@
+LIBCURL_LDLIBS = @LIBCURL_LDLIBS@
MSGFMT = @MSGFMT@
MSGFMT_FLAGS = @MSGFMT_FLAGS@
MSGMERGE = @MSGMERGE@
diff --git a/src/interfaces/Makefile b/src/interfaces/Makefile
index 7d56b29d28f..e6822caa206 100644
--- a/src/interfaces/Makefile
+++ b/src/interfaces/Makefile
@@ -14,7 +14,19 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = libpq ecpg
+ifeq ($(with_libcurl), yes)
+SUBDIRS += libpq-oauth
+else
+ALWAYS_SUBDIRS += libpq-oauth
+endif
+
$(recurse)
+$(recurse_always)
all-ecpg-recurse: all-libpq-recurse
install-ecpg-recurse: install-libpq-recurse
+
+ifeq ($(with_libcurl), yes)
+all-libpq-oauth-recurse: all-libpq-recurse
+install-libpq-oauth-recurse: install-libpq-recurse
+endif
diff --git a/src/interfaces/libpq-oauth/Makefile b/src/interfaces/libpq-oauth/Makefile
new file mode 100644
index 00000000000..8ed5a6a39c3
--- /dev/null
+++ b/src/interfaces/libpq-oauth/Makefile
@@ -0,0 +1,55 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for libpq-oauth
+#
+# Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/interfaces/libpq-oauth/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/interfaces/libpq-oauth
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+PGFILEDESC = "libpq-oauth - device authorization OAuth support"
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
+NAME = libpq-oauth-$(MAJORVERSION)
+
+override CPPFLAGS := -I$(libpq_srcdir) -I$(top_builddir)/src/port $(LIBCURL_CPPFLAGS) $(CPPFLAGS)
+
+OBJS = \
+ $(WIN32RES) \
+ oauth-curl.o \
+ oauth-utils.o
+
+SHLIB_LINK_INTERNAL = $(libpq_pgport_shlib)
+SHLIB_LINK = $(LIBCURL_LDFLAGS) $(LIBCURL_LDLIBS)
+SHLIB_PREREQS = submake-libpq
+SHLIB_EXPORTS = exports.txt
+
+# Disable -bundle_loader on macOS.
+BE_DLLLIBS =
+
+# Make dependencies on pg_config_paths.h visible in all builds.
+oauth-curl.o: oauth-curl.c $(top_builddir)/src/port/pg_config_paths.h
+
+$(top_builddir)/src/port/pg_config_paths.h:
+ $(MAKE) -C $(top_builddir)/src/port pg_config_paths.h
+
+all: all-lib
+
+# Shared library stuff
+include $(top_srcdir)/src/Makefile.shlib
+
+install: all installdirs install-lib
+
+installdirs: installdirs-lib
+
+uninstall: uninstall-lib
+
+clean distclean: clean-lib
+ rm -f $(OBJS)
diff --git a/src/interfaces/libpq-oauth/README b/src/interfaces/libpq-oauth/README
new file mode 100644
index 00000000000..ef746617c71
--- /dev/null
+++ b/src/interfaces/libpq-oauth/README
@@ -0,0 +1,30 @@
+libpq-oauth is an optional module implementing the Device Authorization flow for
+OAuth clients (RFC 8628). It was originally developed as part of libpq core and
+later split out as its own shared library in order to isolate its dependency on
+libcurl. (End users who don't want the Curl dependency can simply choose not to
+install this module.)
+
+If a connection string allows the use of OAuth, the server asks for it, and a
+libpq client has not installed its own custom OAuth flow, libpq will attempt to
+delay-load this module using dlopen() and the following ABI. Failure to load
+results in a failed connection.
+
+= Load-Time ABI =
+
+This module ABI is an internal implementation detail, so it's subject to change
+without warning, even during minor releases (however unlikely). The compiled
+version of libpq-oauth should always match the compiled version of libpq.
+
+- PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+- void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+pg_fe_run_oauth_flow and pg_fe_cleanup_oauth_flow are implementations of
+conn->async_auth and conn->cleanup_async_auth, respectively.
+
+- void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+
+At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
+libpq_gettext(), which must be injected by libpq before the flow is run. It also
+relies on libpq to expose conn->errorMessage, via an errmsg_impl.
diff --git a/src/interfaces/libpq-oauth/exports.txt b/src/interfaces/libpq-oauth/exports.txt
new file mode 100644
index 00000000000..6891a83dbf9
--- /dev/null
+++ b/src/interfaces/libpq-oauth/exports.txt
@@ -0,0 +1,4 @@
+# src/interfaces/libpq-oauth/exports.txt
+libpq_oauth_init 1
+pg_fe_run_oauth_flow 2
+pg_fe_cleanup_oauth_flow 3
diff --git a/src/interfaces/libpq-oauth/meson.build b/src/interfaces/libpq-oauth/meson.build
new file mode 100644
index 00000000000..bf181fd5b96
--- /dev/null
+++ b/src/interfaces/libpq-oauth/meson.build
@@ -0,0 +1,42 @@
+# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+
+oauth_flow_supported = (
+ libcurl.found()
+ and (header_macros.has_key('HAVE_SYS_EVENT_H')
+ or header_macros.has_key('HAVE_SYS_EPOLL_H'))
+)
+
+if libcurlopt.disabled()
+ subdir_done()
+elif not oauth_flow_supported
+ if libcurlopt.enabled()
+ error('client OAuth is not supported on this platform')
+ endif
+ subdir_done()
+endif
+
+libpq_oauth_sources = files(
+ 'oauth-curl.c',
+ 'oauth-utils.c',
+)
+
+export_file = custom_target('libpq-oauth.exports',
+ kwargs: gen_export_kwargs,
+)
+
+# port needs to be in include path due to pthread-win32.h
+libpq_oauth_inc = include_directories('.', '../libpq', '../../port')
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
+libpq_oauth_name = 'libpq-oauth-@0@'.format(pg_version_major)
+
+libpq_oauth_so = shared_module(libpq_oauth_name,
+ libpq_oauth_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_shlib_code, libpq, libpq_oauth_deps],
+ link_depends: export_file,
+ link_args: export_fmt.format(export_file.full_path()),
+ kwargs: default_lib_args,
+)
diff --git a/src/interfaces/libpq/fe-auth-oauth-curl.c b/src/interfaces/libpq-oauth/oauth-curl.c
similarity index 98%
rename from src/interfaces/libpq/fe-auth-oauth-curl.c
rename to src/interfaces/libpq-oauth/oauth-curl.c
index cd9c0323bb6..d52125415bc 100644
--- a/src/interfaces/libpq/fe-auth-oauth-curl.c
+++ b/src/interfaces/libpq-oauth/oauth-curl.c
@@ -1,6 +1,6 @@
/*-------------------------------------------------------------------------
*
- * fe-auth-oauth-curl.c
+ * oauth-curl.c
* The libcurl implementation of OAuth/OIDC authentication, using the
* OAuth Device Authorization Grant (RFC 8628).
*
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * src/interfaces/libpq/fe-auth-oauth-curl.c
+ * src/interfaces/libpq-oauth/oauth-curl.c
*
*-------------------------------------------------------------------------
*/
@@ -17,20 +17,23 @@
#include <curl/curl.h>
#include <math.h>
-#ifdef HAVE_SYS_EPOLL_H
+#include <unistd.h>
+
+#if defined(HAVE_SYS_EPOLL_H)
#include <sys/epoll.h>
#include <sys/timerfd.h>
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
#include <sys/event.h>
+#else
+#error libpq-oauth is not supported on this platform
#endif
-#include <unistd.h>
#include "common/jsonapi.h"
#include "fe-auth.h"
#include "fe-auth-oauth.h"
-#include "libpq-int.h"
#include "mb/pg_wchar.h"
+#include "oauth-curl.h"
+#include "oauth-utils.h"
/*
* It's generally prudent to set a maximum response size to buffer in memory,
@@ -1110,7 +1113,7 @@ parse_access_token(struct async_ctx *actx, struct token *tok)
static bool
setup_multiplexer(struct async_ctx *actx)
{
-#ifdef HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct epoll_event ev = {.events = EPOLLIN};
actx->mux = epoll_create1(EPOLL_CLOEXEC);
@@ -1134,8 +1137,7 @@ setup_multiplexer(struct async_ctx *actx)
}
return true;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
actx->mux = kqueue();
if (actx->mux < 0)
{
@@ -1158,10 +1160,9 @@ setup_multiplexer(struct async_ctx *actx)
}
return true;
+#else
+#error setup_multiplexer is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support the Device Authorization flow on this platform");
- return false;
}
/*
@@ -1174,7 +1175,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
{
struct async_ctx *actx = ctx;
-#ifdef HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct epoll_event ev = {0};
int res;
int op = EPOLL_CTL_ADD;
@@ -1230,8 +1231,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
}
return 0;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
struct kevent ev[2] = {0};
struct kevent ev_out[2];
struct timespec timeout = {0};
@@ -1312,10 +1312,9 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
}
return 0;
+#else
+#error register_socket is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support multiplexer sockets on this platform");
- return -1;
}
/*
@@ -1334,7 +1333,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
static bool
set_timer(struct async_ctx *actx, long timeout)
{
-#if HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct itimerspec spec = {0};
if (timeout < 0)
@@ -1363,8 +1362,7 @@ set_timer(struct async_ctx *actx, long timeout)
}
return true;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
struct kevent ev;
#ifdef __NetBSD__
@@ -1419,10 +1417,9 @@ set_timer(struct async_ctx *actx, long timeout)
}
return true;
+#else
+#error set_timer is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support timers on this platform");
- return false;
}
/*
@@ -1433,7 +1430,7 @@ set_timer(struct async_ctx *actx, long timeout)
static int
timer_expired(struct async_ctx *actx)
{
-#if HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct itimerspec spec = {0};
if (timerfd_gettime(actx->timerfd, &spec) < 0)
@@ -1453,8 +1450,7 @@ timer_expired(struct async_ctx *actx)
/* If the remaining time to expiration is zero, we're done. */
return (spec.it_value.tv_sec == 0
&& spec.it_value.tv_nsec == 0);
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
int res;
/* Is the timer queue ready? */
@@ -1466,10 +1462,9 @@ timer_expired(struct async_ctx *actx)
}
return (res > 0);
+#else
+#error timer_expired is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support timers on this platform");
- return -1;
}
/*
@@ -2487,8 +2482,9 @@ prompt_user(struct async_ctx *actx, PGconn *conn)
.verification_uri_complete = actx->authz.verification_uri_complete,
.expires_in = actx->authz.expires_in,
};
+ PQauthDataHook_type hook = PQgetAuthDataHook();
- res = PQauthDataHook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
+ res = hook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
if (!res)
{
diff --git a/src/interfaces/libpq-oauth/oauth-curl.h b/src/interfaces/libpq-oauth/oauth-curl.h
new file mode 100644
index 00000000000..248d0424ad0
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-curl.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-curl.h
+ *
+ * Definitions for OAuth Device Authorization module
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-curl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_CURL_H
+#define OAUTH_CURL_H
+
+#include "libpq-fe.h"
+
+/* Exported async-auth callbacks. */
+extern PGDLLEXPORT PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+extern PGDLLEXPORT void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+#endif /* OAUTH_CURL_H */
diff --git a/src/interfaces/libpq-oauth/oauth-utils.c b/src/interfaces/libpq-oauth/oauth-utils.c
new file mode 100644
index 00000000000..2bdbf904743
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.c
@@ -0,0 +1,202 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.c
+ *
+ * "Glue" helpers providing a copy of some internal APIs from libpq. At
+ * some point in the future, we might be able to deduplicate.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq-oauth/oauth-utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+
+#include "libpq-int.h"
+#include "oauth-utils.h"
+
+static libpq_gettext_func libpq_gettext_impl;
+static conn_errorMessage_func conn_errorMessage;
+
+pgthreadlock_t pg_g_threadlock;
+
+/*-
+ * Initializes libpq-oauth by setting necessary callbacks.
+ *
+ * The current implementation relies on the following private implementation
+ * details of libpq:
+ *
+ * - pg_g_threadlock: protects libcurl initialization if the underlying Curl
+ * installation is not threadsafe
+ *
+ * - libpq_gettext: translates error messages using libpq's message domain
+ *
+ * - conn->errorMessage: holds translated errors for the connection. This is
+ * handled through a translation shim, which avoids either depending on the
+ * offset of the errorMessage in PGconn, or needing to export the variadic
+ * libpq_append_conn_error().
+ */
+void
+libpq_oauth_init(pgthreadlock_t threadlock_impl,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl)
+{
+ pg_g_threadlock = threadlock_impl;
+ libpq_gettext_impl = gettext_impl;
+ conn_errorMessage = errmsg_impl;
+}
+
+/*
+ * Append a formatted string to the error message buffer of the given
+ * connection, after translating it. This is a copy of libpq's internal API.
+ */
+void
+libpq_append_conn_error(PGconn *conn, const char *fmt,...)
+{
+ int save_errno = errno;
+ bool done;
+ va_list args;
+ PQExpBuffer errorMessage = conn_errorMessage(conn);
+
+ Assert(fmt[strlen(fmt) - 1] != '\n');
+
+ if (PQExpBufferBroken(errorMessage))
+ return; /* already failed */
+
+ /* Loop in case we have to retry after enlarging the buffer. */
+ do
+ {
+ errno = save_errno;
+ va_start(args, fmt);
+ done = appendPQExpBufferVA(errorMessage, libpq_gettext(fmt), args);
+ va_end(args);
+ } while (!done);
+
+ appendPQExpBufferChar(errorMessage, '\n');
+}
+
+#ifdef ENABLE_NLS
+
+/*
+ * A shim that defers to the actual libpq_gettext().
+ */
+char *
+libpq_gettext(const char *msgid)
+{
+ if (!libpq_gettext_impl)
+ {
+ /*
+ * Possible if the libpq build doesn't enable NLS. That's a concerning
+ * mismatch, but in this particular case we can handle it. Try to warn
+ * a developer with an assertion, though.
+ */
+ Assert(false);
+
+ /*
+ * Note that callers of libpq_gettext() have to treat the return value
+ * as if it were const, because builds without NLS simply pass through
+ * their argument.
+ */
+ return unconstify(char *, msgid);
+ }
+
+ return libpq_gettext_impl(msgid);
+}
+
+#endif /* ENABLE_NLS */
+
+/*
+ * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
+ */
+bool
+oauth_unsafe_debugging_enabled(void)
+{
+ const char *env = getenv("PGOAUTHDEBUG");
+
+ return (env && strcmp(env, "UNSAFE") == 0);
+}
+
+/*
+ * Duplicate SOCK_ERRNO* definitions from libpq-int.h, for use by
+ * pq_block/reset_sigpipe().
+ */
+#ifdef WIN32
+#define SOCK_ERRNO (WSAGetLastError())
+#define SOCK_ERRNO_SET(e) WSASetLastError(e)
+#else
+#define SOCK_ERRNO errno
+#define SOCK_ERRNO_SET(e) (errno = (e))
+#endif
+
+/*
+ * Block SIGPIPE for this thread. This is a copy of libpq's internal API.
+ */
+int
+pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
+{
+ sigset_t sigpipe_sigset;
+ sigset_t sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ /* Block SIGPIPE and save previous mask for later reset */
+ SOCK_ERRNO_SET(pthread_sigmask(SIG_BLOCK, &sigpipe_sigset, osigset));
+ if (SOCK_ERRNO)
+ return -1;
+
+ /* We can have a pending SIGPIPE only if it was blocked before */
+ if (sigismember(osigset, SIGPIPE))
+ {
+ /* Is there a pending SIGPIPE? */
+ if (sigpending(&sigset) != 0)
+ return -1;
+
+ if (sigismember(&sigset, SIGPIPE))
+ *sigpipe_pending = true;
+ else
+ *sigpipe_pending = false;
+ }
+ else
+ *sigpipe_pending = false;
+
+ return 0;
+}
+
+/*
+ * Discard any pending SIGPIPE and reset the signal mask. This is a copy of
+ * libpq's internal API.
+ */
+void
+pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
+{
+ int save_errno = SOCK_ERRNO;
+ int signo;
+ sigset_t sigset;
+
+ /* Clear SIGPIPE only if none was pending */
+ if (got_epipe && !sigpipe_pending)
+ {
+ if (sigpending(&sigset) == 0 &&
+ sigismember(&sigset, SIGPIPE))
+ {
+ sigset_t sigpipe_sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ sigwait(&sigpipe_sigset, &signo);
+ }
+ }
+
+ /* Restore saved block mask */
+ pthread_sigmask(SIG_SETMASK, osigset, NULL);
+
+ SOCK_ERRNO_SET(save_errno);
+}
diff --git a/src/interfaces/libpq-oauth/oauth-utils.h b/src/interfaces/libpq-oauth/oauth-utils.h
new file mode 100644
index 00000000000..279fc113248
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.h
+ *
+ * Definitions providing missing libpq internal APIs
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_UTILS_H
+#define OAUTH_UTILS_H
+
+#include "libpq-fe.h"
+#include "pqexpbuffer.h"
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/* Initializes libpq-oauth. */
+extern PGDLLEXPORT void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+
+/* Duplicated APIs, copied from libpq. */
+extern void libpq_append_conn_error(PGconn *conn, const char *fmt,...) pg_attribute_printf(2, 3);
+extern bool oauth_unsafe_debugging_enabled(void);
+extern int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
+extern void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe);
+
+#endif /* OAUTH_UTILS_H */
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 90b0b65db6f..b5346181b9a 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -64,10 +64,6 @@ OBJS += \
fe-secure-gssapi.o
endif
-ifeq ($(with_libcurl),yes)
-OBJS += fe-auth-oauth-curl.o
-endif
-
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
@@ -86,7 +82,7 @@ endif
# that are built correctly for use in a shlib.
SHLIB_LINK_INTERNAL = -lpgcommon_shlib -lpgport_shlib
ifneq ($(PORTNAME), win32)
-SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lcurl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
+SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
else
SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi32 -lssl -lsocket -lnsl -lresolv -lintl -lm $(PTHREAD_LIBS), $(LIBS)) $(LDAP_LIBS_FE)
endif
@@ -115,8 +111,6 @@ backend_src = $(top_srcdir)/src/backend
# which seems to insert references to that even in pure C code. Excluding
# __tsan_func_exit is necessary when using ThreadSanitizer data race detector
# which use this function for instrumentation of function exit.
-# libcurl registers an exit handler in the memory debugging code when running
-# with LeakSanitizer.
# Skip the test when profiling, as gcc may insert exit() calls for that.
# Also skip the test on platforms where libpq infrastructure may be provided
# by statically-linked libraries, as we can't expect them to honor this
@@ -124,7 +118,7 @@ backend_src = $(top_srcdir)/src/backend
libpq-refs-stamp: $(shlib)
ifneq ($(enable_coverage), yes)
ifeq (,$(filter solaris,$(PORTNAME)))
- @if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit -e _atexit | grep exit; then \
+ @if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit | grep exit; then \
echo 'libpq must not be calling any function which invokes exit'; exit 1; \
fi
endif
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index d5143766858..0625cf39e9a 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -210,3 +210,4 @@ PQsetAuthDataHook 207
PQgetAuthDataHook 208
PQdefaultAuthDataHook 209
PQfullProtocolVersion 210
+appendPQExpBufferVA 211
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
index cf1a25e2ccc..5bea1e059a2 100644
--- a/src/interfaces/libpq/fe-auth-oauth.c
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -15,6 +15,10 @@
#include "postgres_fe.h"
+#ifndef WIN32
+#include <dlfcn.h>
+#endif
+
#include "common/base64.h"
#include "common/hmac.h"
#include "common/jsonapi.h"
@@ -22,6 +26,7 @@
#include "fe-auth.h"
#include "fe-auth-oauth.h"
#include "mb/pg_wchar.h"
+#include "pg_config_paths.h"
/* The exported OAuth callback mechanism. */
static void *oauth_init(PGconn *conn, const char *password,
@@ -721,6 +726,93 @@ cleanup_user_oauth_flow(PGconn *conn)
state->async_ctx = NULL;
}
+#ifdef USE_LIBCURL
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/*
+ * This shim is injected into libpq-oauth so that it doesn't depend on the
+ * offset of conn->errorMessage.
+ *
+ * TODO: look into exporting libpq_append_conn_error or a comparable API from
+ * libpq, instead.
+ */
+static PQExpBuffer
+conn_errorMessage(PGconn *conn)
+{
+ return &conn->errorMessage;
+}
+
+static bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ void (*init) (pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+ PostgresPollingStatusType (*flow) (PGconn *conn);
+ void (*cleanup) (PGconn *conn);
+
+ state->builtin_flow = dlopen("libpq-oauth-" PG_MAJORVERSION DLSUFFIX,
+ RTLD_NOW | RTLD_LOCAL);
+ if (!state->builtin_flow)
+ {
+ /*
+ * For end users, this probably isn't an error condition, it just
+ * means the flow isn't installed. Developers and package maintainers
+ * may want to debug this via the PGOAUTHDEBUG envvar, though.
+ *
+ * Note that POSIX dlerror() isn't guaranteed to be threadsafe.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
+
+ return false;
+ }
+
+ if ((init = dlsym(state->builtin_flow, "libpq_oauth_init")) == NULL
+ || (flow = dlsym(state->builtin_flow, "pg_fe_run_oauth_flow")) == NULL
+ || (cleanup = dlsym(state->builtin_flow, "pg_fe_cleanup_oauth_flow")) == NULL)
+ {
+ /*
+ * This is more of an error condition than the one above, but due to
+ * the dlerror() threadsafety issue, lock it behind PGOAUTHDEBUG too.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
+
+ dlclose(state->builtin_flow);
+ return false;
+ }
+
+ /*
+ * Inject necessary function pointers into the module.
+ */
+ init(pg_g_threadlock,
+#ifdef ENABLE_NLS
+ libpq_gettext,
+#else
+ NULL,
+#endif
+ conn_errorMessage);
+
+ /* Set our asynchronous callbacks. */
+ conn->async_auth = flow;
+ conn->cleanup_async_auth = cleanup;
+
+ return true;
+}
+
+#else /* !USE_LIBCURL */
+
+static bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ return false;
+}
+
+#endif /* USE_LIBCURL */
+
/*
* Chooses an OAuth client flow for the connection, which will retrieve a Bearer
* token for presentation to the server.
@@ -792,18 +884,10 @@ setup_token_request(PGconn *conn, fe_oauth_state *state)
libpq_append_conn_error(conn, "user-defined OAuth flow failed");
goto fail;
}
- else
+ else if (!use_builtin_flow(conn, state))
{
-#if USE_LIBCURL
- /* Hand off to our built-in OAuth flow. */
- conn->async_auth = pg_fe_run_oauth_flow;
- conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
-
-#else
libpq_append_conn_error(conn, "no custom OAuth flows are available, and libpq was not built with libcurl support");
goto fail;
-
-#endif
}
return true;
diff --git a/src/interfaces/libpq/fe-auth-oauth.h b/src/interfaces/libpq/fe-auth-oauth.h
index 3f1a7503a01..699ba42acc2 100644
--- a/src/interfaces/libpq/fe-auth-oauth.h
+++ b/src/interfaces/libpq/fe-auth-oauth.h
@@ -33,10 +33,10 @@ typedef struct
PGconn *conn;
void *async_ctx;
+
+ void *builtin_flow;
} fe_oauth_state;
-extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
-extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
extern void pqClearOAuthToken(PGconn *conn);
extern bool oauth_unsafe_debugging_enabled(void);
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index 292fecf3320..47d38e9378f 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -38,10 +38,6 @@ if gssapi.found()
)
endif
-if libcurl.found()
- libpq_sources += files('fe-auth-oauth-curl.c')
-endif
-
export_file = custom_target('libpq.exports',
kwargs: gen_export_kwargs,
)
diff --git a/src/interfaces/libpq/nls.mk b/src/interfaces/libpq/nls.mk
index ae761265852..b87df277d93 100644
--- a/src/interfaces/libpq/nls.mk
+++ b/src/interfaces/libpq/nls.mk
@@ -13,15 +13,21 @@ GETTEXT_FILES = fe-auth.c \
fe-secure-common.c \
fe-secure-gssapi.c \
fe-secure-openssl.c \
- win32.c
-GETTEXT_TRIGGERS = libpq_append_conn_error:2 \
+ win32.c \
+ ../libpq-oauth/oauth-curl.c \
+ ../libpq-oauth/oauth-utils.c
+GETTEXT_TRIGGERS = actx_error:2 \
+ libpq_append_conn_error:2 \
libpq_append_error:2 \
libpq_gettext \
libpq_ngettext:1,2 \
+ oauth_parse_set_error:2 \
pqInternalNotice:2
-GETTEXT_FLAGS = libpq_append_conn_error:2:c-format \
+GETTEXT_FLAGS = actx_error:2:c-format \
+ libpq_append_conn_error:2:c-format \
libpq_append_error:2:c-format \
libpq_gettext:1:pass-c-format \
libpq_ngettext:1:pass-c-format \
libpq_ngettext:2:pass-c-format \
+ oauth_parse_set_error:2:c-format \
pqInternalNotice:2:c-format
diff --git a/src/makefiles/meson.build b/src/makefiles/meson.build
index 46d8da070e8..f2ba5b38124 100644
--- a/src/makefiles/meson.build
+++ b/src/makefiles/meson.build
@@ -201,6 +201,8 @@ pgxs_empty = [
'ICU_LIBS',
'LIBURING_CFLAGS', 'LIBURING_LIBS',
+
+ 'LIBCURL_CPPFLAGS', 'LIBCURL_LDFLAGS', 'LIBCURL_LDLIBS',
]
if host_system == 'windows' and cc.get_argument_syntax() != 'msvc'
--
2.34.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-11 00:12 ` Jacob Champion <[email protected]>
2025-04-14 16:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-11 00:12 UTC (permalink / raw)
To: Jelte Fennema-Nio <[email protected]>; +Cc: Christoph Berg <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>
On Wed, Apr 9, 2025 at 4:42 PM Jelte Fennema-Nio <[email protected]> wrote:
> I think your suggestion of not using any .so files would best there (from w user perspective). I'd be quite surprised if a static build still resulted in me having to manage shared library files anyway.
Done this way in v5. I had planned to separate the implementations by
a #define, but I ran into issues with Makefile.shlib, so I split the
shared and dynamic versions into separate files. I just now realized
that we do something about this exact problem in src/common, so I'll
see if I can copy its technique for the next go round.
In the next version, I'll try to add --with-oauth-client while keeping
--with-libcurl as an alias, to let the buildfarm migrate off of it
before it's removed.
Thanks!
--Jacob
Attachments:
[application/octet-stream] v5-0001-WIP-split-Device-Authorization-flow-into-dlopen-d.patch (43.1K, ../../CAOYmi+nQRtnvFCdjRx+rA=MS1XTB8JTzj0q2o+zitzw=DSCYWg@mail.gmail.com/2-v5-0001-WIP-split-Device-Authorization-flow-into-dlopen-d.patch)
download | inline diff:
From 1e500c077aef3dfd17c5423d9f63639a75c2fd80 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Wed, 26 Mar 2025 10:55:28 -0700
Subject: [PATCH v5] WIP: split Device Authorization flow into dlopen'd module
See notes on mailing list.
Co-authored-by: Daniel Gustafsson <[email protected]>
---
config/programs.m4 | 17 +-
configure | 50 ++++-
configure.ac | 26 ++-
meson.build | 22 +-
src/Makefile.global.in | 3 +
src/interfaces/Makefile | 12 ++
src/interfaces/libpq-oauth/Makefile | 65 ++++++
src/interfaces/libpq-oauth/README | 30 +++
src/interfaces/libpq-oauth/exports.txt | 4 +
src/interfaces/libpq-oauth/meson.build | 54 +++++
.../oauth-curl.c} | 60 +++---
src/interfaces/libpq-oauth/oauth-curl.h | 24 +++
src/interfaces/libpq-oauth/oauth-utils.c | 202 ++++++++++++++++++
src/interfaces/libpq-oauth/oauth-utils.h | 35 +++
src/interfaces/libpq/Makefile | 20 +-
src/interfaces/libpq/exports.txt | 1 +
src/interfaces/libpq/fe-auth-oauth-dynamic.c | 109 ++++++++++
src/interfaces/libpq/fe-auth-oauth-static.c | 40 ++++
src/interfaces/libpq/fe-auth-oauth.c | 26 ++-
src/interfaces/libpq/fe-auth-oauth.h | 5 +-
src/interfaces/libpq/meson.build | 8 +-
src/interfaces/libpq/nls.mk | 12 +-
src/makefiles/meson.build | 2 +
23 files changed, 747 insertions(+), 80 deletions(-)
create mode 100644 src/interfaces/libpq-oauth/Makefile
create mode 100644 src/interfaces/libpq-oauth/README
create mode 100644 src/interfaces/libpq-oauth/exports.txt
create mode 100644 src/interfaces/libpq-oauth/meson.build
rename src/interfaces/{libpq/fe-auth-oauth-curl.c => libpq-oauth/oauth-curl.c} (98%)
create mode 100644 src/interfaces/libpq-oauth/oauth-curl.h
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.c
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.h
create mode 100644 src/interfaces/libpq/fe-auth-oauth-dynamic.c
create mode 100644 src/interfaces/libpq/fe-auth-oauth-static.c
diff --git a/config/programs.m4 b/config/programs.m4
index 0a07feb37cc..0ad1e58b48d 100644
--- a/config/programs.m4
+++ b/config/programs.m4
@@ -286,9 +286,20 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
[
AC_CHECK_HEADER(curl/curl.h, [],
[AC_MSG_ERROR([header file <curl/curl.h> is required for --with-libcurl])])
- AC_CHECK_LIB(curl, curl_multi_init, [],
+ AC_CHECK_LIB(curl, curl_multi_init, [
+ AC_DEFINE([HAVE_LIBCURL], [1], [Define to 1 if you have the `curl' library (-lcurl).])
+ AC_SUBST(LIBCURL_LDLIBS, -lcurl)
+ ],
[AC_MSG_ERROR([library 'curl' does not provide curl_multi_init])])
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+ LIBS="$LIBCURL_LDLIBS $LIBS"
+
# Check to see whether the current platform supports threadsafe Curl
# initialization.
AC_CACHE_CHECK([for curl_global_init thread safety], [pgac_cv__libcurl_threadsafe_init],
@@ -338,4 +349,8 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
*** lookups. Rebuild libcurl with the AsynchDNS feature enabled in order
*** to use it with libpq.])
fi
+
+ CPPFLAGS=$pgac_save_CPPFLAGS
+ LDFLAGS=$pgac_save_LDFLAGS
+ LIBS=$pgac_save_LIBS
])# PGAC_CHECK_LIBCURL
diff --git a/configure b/configure
index 8f4a5ab28ec..df1da549c4c 100755
--- a/configure
+++ b/configure
@@ -655,6 +655,7 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+LIBCURL_LDLIBS
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -708,6 +709,8 @@ XML2_LIBS
XML2_CFLAGS
XML2_CONFIG
with_libxml
+LIBCURL_LDFLAGS
+LIBCURL_CPPFLAGS
LIBCURL_LIBS
LIBCURL_CFLAGS
with_libcurl
@@ -9042,19 +9045,27 @@ $as_echo "yes" >&6; }
fi
- # We only care about -I, -D, and -L switches;
- # note that -lcurl will be added by PGAC_CHECK_LIBCURL below.
+ # Curl's flags are kept separate from the standard CPPFLAGS/LDFLAGS. We use
+ # them only for libpq-oauth.
+ LIBCURL_CPPFLAGS=
+ LIBCURL_LDFLAGS=
+
+ # We only care about -I, -D, and -L switches. Note that -lcurl will be added
+ # to LIBCURL_LDLIBS by PGAC_CHECK_LIBCURL, below.
for pgac_option in $LIBCURL_CFLAGS; do
case $pgac_option in
- -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+ -I*|-D*) LIBCURL_CPPFLAGS="$LIBCURL_CPPFLAGS $pgac_option";;
esac
done
for pgac_option in $LIBCURL_LIBS; do
case $pgac_option in
- -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+ -L*) LIBCURL_LDFLAGS="$LIBCURL_LDFLAGS $pgac_option";;
esac
done
+
+
+
# OAuth requires python for testing
if test "$with_python" != yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: *** OAuth support tests require --with-python to run" >&5
@@ -12517,9 +12528,6 @@ fi
fi
-# XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
-# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
-# dependency on that platform?
if test "$with_libcurl" = yes ; then
ac_fn_c_check_header_mongrel "$LINENO" "curl/curl.h" "ac_cv_header_curl_curl_h" "$ac_includes_default"
@@ -12567,17 +12575,26 @@ fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_curl_curl_multi_init" >&5
$as_echo "$ac_cv_lib_curl_curl_multi_init" >&6; }
if test "x$ac_cv_lib_curl_curl_multi_init" = xyes; then :
- cat >>confdefs.h <<_ACEOF
-#define HAVE_LIBCURL 1
-_ACEOF
- LIBS="-lcurl $LIBS"
+
+$as_echo "#define HAVE_LIBCURL 1" >>confdefs.h
+
+ LIBCURL_LDLIBS=-lcurl
+
else
as_fn_error $? "library 'curl' does not provide curl_multi_init" "$LINENO" 5
fi
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+ LIBS="$LIBCURL_LDLIBS $LIBS"
+
# Check to see whether the current platform supports threadsafe Curl
# initialization.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_global_init thread safety" >&5
@@ -12681,6 +12698,10 @@ $as_echo "$pgac_cv__libcurl_async_dns" >&6; }
*** to use it with libpq." "$LINENO" 5
fi
+ CPPFLAGS=$pgac_save_CPPFLAGS
+ LDFLAGS=$pgac_save_LDFLAGS
+ LIBS=$pgac_save_LIBS
+
fi
if test "$with_gssapi" = yes ; then
@@ -14329,6 +14350,13 @@ done
fi
+if test "$with_libcurl" = yes ; then
+ # Error out early if this platform can't support libpq-oauth.
+ if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
+ as_fn_error $? "client OAuth is not supported on this platform" "$LINENO" 5
+ fi
+fi
+
##
## Types, structures, compiler characteristics
##
diff --git a/configure.ac b/configure.ac
index fc5f7475d07..218aeea1b3b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1033,19 +1033,27 @@ if test "$with_libcurl" = yes ; then
# to explicitly set TLS 1.3 ciphersuites).
PKG_CHECK_MODULES(LIBCURL, [libcurl >= 7.61.0])
- # We only care about -I, -D, and -L switches;
- # note that -lcurl will be added by PGAC_CHECK_LIBCURL below.
+ # Curl's flags are kept separate from the standard CPPFLAGS/LDFLAGS. We use
+ # them only for libpq-oauth.
+ LIBCURL_CPPFLAGS=
+ LIBCURL_LDFLAGS=
+
+ # We only care about -I, -D, and -L switches. Note that -lcurl will be added
+ # to LIBCURL_LDLIBS by PGAC_CHECK_LIBCURL, below.
for pgac_option in $LIBCURL_CFLAGS; do
case $pgac_option in
- -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+ -I*|-D*) LIBCURL_CPPFLAGS="$LIBCURL_CPPFLAGS $pgac_option";;
esac
done
for pgac_option in $LIBCURL_LIBS; do
case $pgac_option in
- -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+ -L*) LIBCURL_LDFLAGS="$LIBCURL_LDFLAGS $pgac_option";;
esac
done
+ AC_SUBST(LIBCURL_CPPFLAGS)
+ AC_SUBST(LIBCURL_LDFLAGS)
+
# OAuth requires python for testing
if test "$with_python" != yes; then
AC_MSG_WARN([*** OAuth support tests require --with-python to run])
@@ -1340,9 +1348,6 @@ failure. It is possible the compiler isn't looking in the proper directory.
Use --without-zlib to disable zlib support.])])
fi
-# XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
-# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
-# dependency on that platform?
if test "$with_libcurl" = yes ; then
PGAC_CHECK_LIBCURL
fi
@@ -1640,6 +1645,13 @@ if test "$PORTNAME" = "win32" ; then
AC_CHECK_HEADERS(crtdefs.h)
fi
+if test "$with_libcurl" = yes ; then
+ # Error out early if this platform can't support libpq-oauth.
+ if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
+ AC_MSG_ERROR([client OAuth is not supported on this platform])
+ fi
+fi
+
##
## Types, structures, compiler characteristics
##
diff --git a/meson.build b/meson.build
index 27717ad8976..0f0df9b1af4 100644
--- a/meson.build
+++ b/meson.build
@@ -107,6 +107,7 @@ os_deps = []
backend_both_deps = []
backend_deps = []
libpq_deps = []
+libpq_oauth_deps = []
pg_sysroot = ''
@@ -2587,6 +2588,7 @@ header_checks = [
'xlocale.h',
]
+header_macros = {}
foreach header : header_checks
varname = 'HAVE_' + header.underscorify().to_upper()
@@ -2595,6 +2597,15 @@ foreach header : header_checks
include_directories: postgres_inc, args: test_c_args)
cdata.set(varname, found ? 1 : false,
description: 'Define to 1 if you have the <@0@> header file.'.format(header))
+
+ # Mixing 1/false in cdata means we can't perform equality checks using
+ # cdata.get(), though, so store our defined header macros for later lookup.
+ #
+ # https://github.com/mesonbuild/meson/issues/11581
+ #
+ if found
+ header_macros += {varname: true}
+ endif
endforeach
@@ -3251,17 +3262,18 @@ libpq_deps += [
gssapi,
ldap_r,
- # XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
- # during gss_acquire_cred(). This is possibly related to Curl's Heimdal
- # dependency on that platform?
- libcurl,
libintl,
ssl,
]
+libpq_oauth_deps += [
+ libcurl,
+]
+
subdir('src/interfaces/libpq')
-# fe_utils depends on libpq
+# fe_utils and libpq-oauth depends on libpq
subdir('src/fe_utils')
+subdir('src/interfaces/libpq-oauth')
# for frontend binaries
frontend_code = declare_dependency(
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 737b2dd1869..eb9b5de75b4 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -343,6 +343,9 @@ perl_embed_ldflags = @perl_embed_ldflags@
AWK = @AWK@
LN_S = @LN_S@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBCURL_LDFLAGS = @LIBCURL_LDFLAGS@
+LIBCURL_LDLIBS = @LIBCURL_LDLIBS@
MSGFMT = @MSGFMT@
MSGFMT_FLAGS = @MSGFMT_FLAGS@
MSGMERGE = @MSGMERGE@
diff --git a/src/interfaces/Makefile b/src/interfaces/Makefile
index 7d56b29d28f..e6822caa206 100644
--- a/src/interfaces/Makefile
+++ b/src/interfaces/Makefile
@@ -14,7 +14,19 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = libpq ecpg
+ifeq ($(with_libcurl), yes)
+SUBDIRS += libpq-oauth
+else
+ALWAYS_SUBDIRS += libpq-oauth
+endif
+
$(recurse)
+$(recurse_always)
all-ecpg-recurse: all-libpq-recurse
install-ecpg-recurse: install-libpq-recurse
+
+ifeq ($(with_libcurl), yes)
+all-libpq-oauth-recurse: all-libpq-recurse
+install-libpq-oauth-recurse: install-libpq-recurse
+endif
diff --git a/src/interfaces/libpq-oauth/Makefile b/src/interfaces/libpq-oauth/Makefile
new file mode 100644
index 00000000000..5fd251a1d27
--- /dev/null
+++ b/src/interfaces/libpq-oauth/Makefile
@@ -0,0 +1,65 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for libpq-oauth
+#
+# Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/interfaces/libpq-oauth/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/interfaces/libpq-oauth
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+PGFILEDESC = "libpq-oauth - device authorization OAuth support"
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
+NAME = pq-oauth-$(MAJORVERSION)
+
+# Force the name "libpq-oauth" for both the static and shared libraries.
+override shlib := lib$(NAME)$(DLSUFFIX)
+override stlib := lib$(NAME).a
+
+override CPPFLAGS := -I$(libpq_srcdir) -I$(top_builddir)/src/port $(LIBCURL_CPPFLAGS) $(CPPFLAGS)
+
+OBJS = \
+ $(WIN32RES) \
+ oauth-curl.o
+
+# The shared library needs additional glue symbols.
+$(shlib): OBJS += oauth-utils.o
+$(shlib): oauth-utils.o
+
+SHLIB_LINK_INTERNAL = $(libpq_pgport_shlib)
+SHLIB_LINK = $(LIBCURL_LDFLAGS) $(LIBCURL_LDLIBS)
+SHLIB_PREREQS = submake-libpq
+SHLIB_EXPORTS = exports.txt
+
+# Disable -bundle_loader on macOS.
+BE_DLLLIBS =
+
+# By default, a library without an SONAME doesn't get a static library, so we
+# add it to the build explicitly.
+all: all-lib all-static-lib
+
+# Shared library stuff
+include $(top_srcdir)/src/Makefile.shlib
+
+# Ignore the standard rules for SONAME-less installation; we want both the
+# static and shared libraries to go into libdir.
+install: all installdirs $(stlib) $(shlib)
+ $(INSTALL_SHLIB) $(shlib) '$(DESTDIR)$(libdir)/$(shlib)'
+ $(INSTALL_STLIB) $(stlib) '$(DESTDIR)$(libdir)/$(stlib)'
+
+installdirs:
+ $(MKDIR_P) '$(DESTDIR)$(libdir)'
+
+uninstall:
+ rm -f '$(DESTDIR)$(libdir)/$(stlib)'
+ rm -f '$(DESTDIR)$(libdir)/$(shlib)'
+
+clean distclean: clean-lib
+ rm -f $(OBJS) oauth-utils.o
diff --git a/src/interfaces/libpq-oauth/README b/src/interfaces/libpq-oauth/README
new file mode 100644
index 00000000000..ef746617c71
--- /dev/null
+++ b/src/interfaces/libpq-oauth/README
@@ -0,0 +1,30 @@
+libpq-oauth is an optional module implementing the Device Authorization flow for
+OAuth clients (RFC 8628). It was originally developed as part of libpq core and
+later split out as its own shared library in order to isolate its dependency on
+libcurl. (End users who don't want the Curl dependency can simply choose not to
+install this module.)
+
+If a connection string allows the use of OAuth, the server asks for it, and a
+libpq client has not installed its own custom OAuth flow, libpq will attempt to
+delay-load this module using dlopen() and the following ABI. Failure to load
+results in a failed connection.
+
+= Load-Time ABI =
+
+This module ABI is an internal implementation detail, so it's subject to change
+without warning, even during minor releases (however unlikely). The compiled
+version of libpq-oauth should always match the compiled version of libpq.
+
+- PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+- void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+pg_fe_run_oauth_flow and pg_fe_cleanup_oauth_flow are implementations of
+conn->async_auth and conn->cleanup_async_auth, respectively.
+
+- void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+
+At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
+libpq_gettext(), which must be injected by libpq before the flow is run. It also
+relies on libpq to expose conn->errorMessage, via an errmsg_impl.
diff --git a/src/interfaces/libpq-oauth/exports.txt b/src/interfaces/libpq-oauth/exports.txt
new file mode 100644
index 00000000000..6891a83dbf9
--- /dev/null
+++ b/src/interfaces/libpq-oauth/exports.txt
@@ -0,0 +1,4 @@
+# src/interfaces/libpq-oauth/exports.txt
+libpq_oauth_init 1
+pg_fe_run_oauth_flow 2
+pg_fe_cleanup_oauth_flow 3
diff --git a/src/interfaces/libpq-oauth/meson.build b/src/interfaces/libpq-oauth/meson.build
new file mode 100644
index 00000000000..7634b7f6fb1
--- /dev/null
+++ b/src/interfaces/libpq-oauth/meson.build
@@ -0,0 +1,54 @@
+# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+
+oauth_flow_supported = (
+ libcurl.found()
+ and (header_macros.has_key('HAVE_SYS_EVENT_H')
+ or header_macros.has_key('HAVE_SYS_EPOLL_H'))
+)
+
+if libcurlopt.disabled()
+ subdir_done()
+elif not oauth_flow_supported
+ if libcurlopt.enabled()
+ error('client OAuth is not supported on this platform')
+ endif
+ subdir_done()
+endif
+
+libpq_oauth_sources = files(
+ 'oauth-curl.c',
+)
+
+# The shared library needs additional glue symbols.
+libpq_oauth_so_sources = files(
+ 'oauth-utils.c',
+)
+
+export_file = custom_target('libpq-oauth.exports',
+ kwargs: gen_export_kwargs,
+)
+
+# port needs to be in include path due to pthread-win32.h
+libpq_oauth_inc = include_directories('.', '../libpq', '../../port')
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
+libpq_oauth_name = 'libpq-oauth-@0@'.format(pg_version_major)
+
+libpq_oauth_st = static_library(libpq_oauth_name,
+ libpq_oauth_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_stlib_code, libpq_oauth_deps],
+ kwargs: default_lib_args,
+)
+
+libpq_oauth_so = shared_module(libpq_oauth_name,
+ libpq_oauth_sources + libpq_oauth_so_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_shlib_code, libpq, libpq_oauth_deps],
+ link_depends: export_file,
+ link_args: export_fmt.format(export_file.full_path()),
+ kwargs: default_lib_args,
+)
diff --git a/src/interfaces/libpq/fe-auth-oauth-curl.c b/src/interfaces/libpq-oauth/oauth-curl.c
similarity index 98%
rename from src/interfaces/libpq/fe-auth-oauth-curl.c
rename to src/interfaces/libpq-oauth/oauth-curl.c
index cd9c0323bb6..d52125415bc 100644
--- a/src/interfaces/libpq/fe-auth-oauth-curl.c
+++ b/src/interfaces/libpq-oauth/oauth-curl.c
@@ -1,6 +1,6 @@
/*-------------------------------------------------------------------------
*
- * fe-auth-oauth-curl.c
+ * oauth-curl.c
* The libcurl implementation of OAuth/OIDC authentication, using the
* OAuth Device Authorization Grant (RFC 8628).
*
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * src/interfaces/libpq/fe-auth-oauth-curl.c
+ * src/interfaces/libpq-oauth/oauth-curl.c
*
*-------------------------------------------------------------------------
*/
@@ -17,20 +17,23 @@
#include <curl/curl.h>
#include <math.h>
-#ifdef HAVE_SYS_EPOLL_H
+#include <unistd.h>
+
+#if defined(HAVE_SYS_EPOLL_H)
#include <sys/epoll.h>
#include <sys/timerfd.h>
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
#include <sys/event.h>
+#else
+#error libpq-oauth is not supported on this platform
#endif
-#include <unistd.h>
#include "common/jsonapi.h"
#include "fe-auth.h"
#include "fe-auth-oauth.h"
-#include "libpq-int.h"
#include "mb/pg_wchar.h"
+#include "oauth-curl.h"
+#include "oauth-utils.h"
/*
* It's generally prudent to set a maximum response size to buffer in memory,
@@ -1110,7 +1113,7 @@ parse_access_token(struct async_ctx *actx, struct token *tok)
static bool
setup_multiplexer(struct async_ctx *actx)
{
-#ifdef HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct epoll_event ev = {.events = EPOLLIN};
actx->mux = epoll_create1(EPOLL_CLOEXEC);
@@ -1134,8 +1137,7 @@ setup_multiplexer(struct async_ctx *actx)
}
return true;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
actx->mux = kqueue();
if (actx->mux < 0)
{
@@ -1158,10 +1160,9 @@ setup_multiplexer(struct async_ctx *actx)
}
return true;
+#else
+#error setup_multiplexer is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support the Device Authorization flow on this platform");
- return false;
}
/*
@@ -1174,7 +1175,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
{
struct async_ctx *actx = ctx;
-#ifdef HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct epoll_event ev = {0};
int res;
int op = EPOLL_CTL_ADD;
@@ -1230,8 +1231,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
}
return 0;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
struct kevent ev[2] = {0};
struct kevent ev_out[2];
struct timespec timeout = {0};
@@ -1312,10 +1312,9 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
}
return 0;
+#else
+#error register_socket is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support multiplexer sockets on this platform");
- return -1;
}
/*
@@ -1334,7 +1333,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
static bool
set_timer(struct async_ctx *actx, long timeout)
{
-#if HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct itimerspec spec = {0};
if (timeout < 0)
@@ -1363,8 +1362,7 @@ set_timer(struct async_ctx *actx, long timeout)
}
return true;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
struct kevent ev;
#ifdef __NetBSD__
@@ -1419,10 +1417,9 @@ set_timer(struct async_ctx *actx, long timeout)
}
return true;
+#else
+#error set_timer is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support timers on this platform");
- return false;
}
/*
@@ -1433,7 +1430,7 @@ set_timer(struct async_ctx *actx, long timeout)
static int
timer_expired(struct async_ctx *actx)
{
-#if HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct itimerspec spec = {0};
if (timerfd_gettime(actx->timerfd, &spec) < 0)
@@ -1453,8 +1450,7 @@ timer_expired(struct async_ctx *actx)
/* If the remaining time to expiration is zero, we're done. */
return (spec.it_value.tv_sec == 0
&& spec.it_value.tv_nsec == 0);
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
int res;
/* Is the timer queue ready? */
@@ -1466,10 +1462,9 @@ timer_expired(struct async_ctx *actx)
}
return (res > 0);
+#else
+#error timer_expired is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support timers on this platform");
- return -1;
}
/*
@@ -2487,8 +2482,9 @@ prompt_user(struct async_ctx *actx, PGconn *conn)
.verification_uri_complete = actx->authz.verification_uri_complete,
.expires_in = actx->authz.expires_in,
};
+ PQauthDataHook_type hook = PQgetAuthDataHook();
- res = PQauthDataHook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
+ res = hook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
if (!res)
{
diff --git a/src/interfaces/libpq-oauth/oauth-curl.h b/src/interfaces/libpq-oauth/oauth-curl.h
new file mode 100644
index 00000000000..248d0424ad0
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-curl.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-curl.h
+ *
+ * Definitions for OAuth Device Authorization module
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-curl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_CURL_H
+#define OAUTH_CURL_H
+
+#include "libpq-fe.h"
+
+/* Exported async-auth callbacks. */
+extern PGDLLEXPORT PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+extern PGDLLEXPORT void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+#endif /* OAUTH_CURL_H */
diff --git a/src/interfaces/libpq-oauth/oauth-utils.c b/src/interfaces/libpq-oauth/oauth-utils.c
new file mode 100644
index 00000000000..2bdbf904743
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.c
@@ -0,0 +1,202 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.c
+ *
+ * "Glue" helpers providing a copy of some internal APIs from libpq. At
+ * some point in the future, we might be able to deduplicate.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq-oauth/oauth-utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+
+#include "libpq-int.h"
+#include "oauth-utils.h"
+
+static libpq_gettext_func libpq_gettext_impl;
+static conn_errorMessage_func conn_errorMessage;
+
+pgthreadlock_t pg_g_threadlock;
+
+/*-
+ * Initializes libpq-oauth by setting necessary callbacks.
+ *
+ * The current implementation relies on the following private implementation
+ * details of libpq:
+ *
+ * - pg_g_threadlock: protects libcurl initialization if the underlying Curl
+ * installation is not threadsafe
+ *
+ * - libpq_gettext: translates error messages using libpq's message domain
+ *
+ * - conn->errorMessage: holds translated errors for the connection. This is
+ * handled through a translation shim, which avoids either depending on the
+ * offset of the errorMessage in PGconn, or needing to export the variadic
+ * libpq_append_conn_error().
+ */
+void
+libpq_oauth_init(pgthreadlock_t threadlock_impl,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl)
+{
+ pg_g_threadlock = threadlock_impl;
+ libpq_gettext_impl = gettext_impl;
+ conn_errorMessage = errmsg_impl;
+}
+
+/*
+ * Append a formatted string to the error message buffer of the given
+ * connection, after translating it. This is a copy of libpq's internal API.
+ */
+void
+libpq_append_conn_error(PGconn *conn, const char *fmt,...)
+{
+ int save_errno = errno;
+ bool done;
+ va_list args;
+ PQExpBuffer errorMessage = conn_errorMessage(conn);
+
+ Assert(fmt[strlen(fmt) - 1] != '\n');
+
+ if (PQExpBufferBroken(errorMessage))
+ return; /* already failed */
+
+ /* Loop in case we have to retry after enlarging the buffer. */
+ do
+ {
+ errno = save_errno;
+ va_start(args, fmt);
+ done = appendPQExpBufferVA(errorMessage, libpq_gettext(fmt), args);
+ va_end(args);
+ } while (!done);
+
+ appendPQExpBufferChar(errorMessage, '\n');
+}
+
+#ifdef ENABLE_NLS
+
+/*
+ * A shim that defers to the actual libpq_gettext().
+ */
+char *
+libpq_gettext(const char *msgid)
+{
+ if (!libpq_gettext_impl)
+ {
+ /*
+ * Possible if the libpq build doesn't enable NLS. That's a concerning
+ * mismatch, but in this particular case we can handle it. Try to warn
+ * a developer with an assertion, though.
+ */
+ Assert(false);
+
+ /*
+ * Note that callers of libpq_gettext() have to treat the return value
+ * as if it were const, because builds without NLS simply pass through
+ * their argument.
+ */
+ return unconstify(char *, msgid);
+ }
+
+ return libpq_gettext_impl(msgid);
+}
+
+#endif /* ENABLE_NLS */
+
+/*
+ * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
+ */
+bool
+oauth_unsafe_debugging_enabled(void)
+{
+ const char *env = getenv("PGOAUTHDEBUG");
+
+ return (env && strcmp(env, "UNSAFE") == 0);
+}
+
+/*
+ * Duplicate SOCK_ERRNO* definitions from libpq-int.h, for use by
+ * pq_block/reset_sigpipe().
+ */
+#ifdef WIN32
+#define SOCK_ERRNO (WSAGetLastError())
+#define SOCK_ERRNO_SET(e) WSASetLastError(e)
+#else
+#define SOCK_ERRNO errno
+#define SOCK_ERRNO_SET(e) (errno = (e))
+#endif
+
+/*
+ * Block SIGPIPE for this thread. This is a copy of libpq's internal API.
+ */
+int
+pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
+{
+ sigset_t sigpipe_sigset;
+ sigset_t sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ /* Block SIGPIPE and save previous mask for later reset */
+ SOCK_ERRNO_SET(pthread_sigmask(SIG_BLOCK, &sigpipe_sigset, osigset));
+ if (SOCK_ERRNO)
+ return -1;
+
+ /* We can have a pending SIGPIPE only if it was blocked before */
+ if (sigismember(osigset, SIGPIPE))
+ {
+ /* Is there a pending SIGPIPE? */
+ if (sigpending(&sigset) != 0)
+ return -1;
+
+ if (sigismember(&sigset, SIGPIPE))
+ *sigpipe_pending = true;
+ else
+ *sigpipe_pending = false;
+ }
+ else
+ *sigpipe_pending = false;
+
+ return 0;
+}
+
+/*
+ * Discard any pending SIGPIPE and reset the signal mask. This is a copy of
+ * libpq's internal API.
+ */
+void
+pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
+{
+ int save_errno = SOCK_ERRNO;
+ int signo;
+ sigset_t sigset;
+
+ /* Clear SIGPIPE only if none was pending */
+ if (got_epipe && !sigpipe_pending)
+ {
+ if (sigpending(&sigset) == 0 &&
+ sigismember(&sigset, SIGPIPE))
+ {
+ sigset_t sigpipe_sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ sigwait(&sigpipe_sigset, &signo);
+ }
+ }
+
+ /* Restore saved block mask */
+ pthread_sigmask(SIG_SETMASK, osigset, NULL);
+
+ SOCK_ERRNO_SET(save_errno);
+}
diff --git a/src/interfaces/libpq-oauth/oauth-utils.h b/src/interfaces/libpq-oauth/oauth-utils.h
new file mode 100644
index 00000000000..279fc113248
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.h
+ *
+ * Definitions providing missing libpq internal APIs
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_UTILS_H
+#define OAUTH_UTILS_H
+
+#include "libpq-fe.h"
+#include "pqexpbuffer.h"
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/* Initializes libpq-oauth. */
+extern PGDLLEXPORT void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+
+/* Duplicated APIs, copied from libpq. */
+extern void libpq_append_conn_error(PGconn *conn, const char *fmt,...) pg_attribute_printf(2, 3);
+extern bool oauth_unsafe_debugging_enabled(void);
+extern int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
+extern void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe);
+
+#endif /* OAUTH_UTILS_H */
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 90b0b65db6f..852b1948ecb 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -64,10 +64,6 @@ OBJS += \
fe-secure-gssapi.o
endif
-ifeq ($(with_libcurl),yes)
-OBJS += fe-auth-oauth-curl.o
-endif
-
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
@@ -86,7 +82,7 @@ endif
# that are built correctly for use in a shlib.
SHLIB_LINK_INTERNAL = -lpgcommon_shlib -lpgport_shlib
ifneq ($(PORTNAME), win32)
-SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lcurl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
+SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
else
SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi32 -lssl -lsocket -lnsl -lresolv -lintl -lm $(PTHREAD_LIBS), $(LIBS)) $(LDAP_LIBS_FE)
endif
@@ -107,6 +103,15 @@ all: all-lib libpq-refs-stamp
include $(top_srcdir)/src/Makefile.shlib
backend_src = $(top_srcdir)/src/backend
+# Add the correct implementations of the OAuth flow, if requested, for both
+# shared and static builds.
+ifeq ($(with_libcurl),yes)
+$(shlib): OBJS += fe-auth-oauth-dynamic.o
+$(shlib): fe-auth-oauth-dynamic.o
+$(stlib): OBJS += fe-auth-oauth-static.o
+$(stlib): fe-auth-oauth-static.o
+endif
+
# Check for functions that libpq must not call, currently just exit().
# (Ideally we'd reject abort() too, but there are various scenarios where
# build toolchains insert abort() calls, e.g. to implement assert().)
@@ -115,8 +120,6 @@ backend_src = $(top_srcdir)/src/backend
# which seems to insert references to that even in pure C code. Excluding
# __tsan_func_exit is necessary when using ThreadSanitizer data race detector
# which use this function for instrumentation of function exit.
-# libcurl registers an exit handler in the memory debugging code when running
-# with LeakSanitizer.
# Skip the test when profiling, as gcc may insert exit() calls for that.
# Also skip the test on platforms where libpq infrastructure may be provided
# by statically-linked libraries, as we can't expect them to honor this
@@ -124,7 +127,7 @@ backend_src = $(top_srcdir)/src/backend
libpq-refs-stamp: $(shlib)
ifneq ($(enable_coverage), yes)
ifeq (,$(filter solaris,$(PORTNAME)))
- @if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit -e _atexit | grep exit; then \
+ @if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit | grep exit; then \
echo 'libpq must not be calling any function which invokes exit'; exit 1; \
fi
endif
@@ -172,5 +175,6 @@ clean distclean: clean-lib
$(MAKE) -C test $@
rm -rf tmp_check
rm -f $(OBJS) pthread.h libpq-refs-stamp
+ rm -f fe-auth-oauth-dynamic.o fe-auth-oauth-static.o
# Might be left over from a Win32 client-only build
rm -f pg_config_paths.h
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index d5143766858..0625cf39e9a 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -210,3 +210,4 @@ PQsetAuthDataHook 207
PQgetAuthDataHook 208
PQdefaultAuthDataHook 209
PQfullProtocolVersion 210
+appendPQExpBufferVA 211
diff --git a/src/interfaces/libpq/fe-auth-oauth-dynamic.c b/src/interfaces/libpq/fe-auth-oauth-dynamic.c
new file mode 100644
index 00000000000..a84551a6307
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-oauth-dynamic.c
@@ -0,0 +1,109 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-oauth-dynamic.c
+ *
+ * Implements the builtin flow by loading the libpq-oauth plugin.
+ * See also fe-auth-oauth-static.c, for static builds.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-auth-oauth-dynamic.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#ifndef USE_LIBCURL
+#error this should only be compiled when OAuth support is enabled
+#endif
+
+#include <dlfcn.h>
+
+#include "fe-auth-oauth.h"
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/*
+ * This shim is injected into libpq-oauth so that it doesn't depend on the
+ * offset of conn->errorMessage.
+ *
+ * TODO: look into exporting libpq_append_conn_error or a comparable API from
+ * libpq, instead.
+ */
+static PQExpBuffer
+conn_errorMessage(PGconn *conn)
+{
+ return &conn->errorMessage;
+}
+
+/*
+ * Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
+ * callbacks into the connection's async auth handlers.
+ *
+ * Failure to load here results in a relatively quiet connection error, to
+ * handle the use case where the build supports loading a flow but a user does
+ * not want to install it. Troubleshooting of linker/loader failures can be done
+ * via PGOAUTHDEBUG.
+ */
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ void (*init) (pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+ PostgresPollingStatusType (*flow) (PGconn *conn);
+ void (*cleanup) (PGconn *conn);
+
+ state->builtin_flow = dlopen("libpq-oauth-" PG_MAJORVERSION DLSUFFIX,
+ RTLD_NOW | RTLD_LOCAL);
+ if (!state->builtin_flow)
+ {
+ /*
+ * For end users, this probably isn't an error condition, it just
+ * means the flow isn't installed. Developers and package maintainers
+ * may want to debug this via the PGOAUTHDEBUG envvar, though.
+ *
+ * Note that POSIX dlerror() isn't guaranteed to be threadsafe.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
+
+ return false;
+ }
+
+ if ((init = dlsym(state->builtin_flow, "libpq_oauth_init")) == NULL
+ || (flow = dlsym(state->builtin_flow, "pg_fe_run_oauth_flow")) == NULL
+ || (cleanup = dlsym(state->builtin_flow, "pg_fe_cleanup_oauth_flow")) == NULL)
+ {
+ /*
+ * This is more of an error condition than the one above, but due to
+ * the dlerror() threadsafety issue, lock it behind PGOAUTHDEBUG too.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
+
+ dlclose(state->builtin_flow);
+ return false;
+ }
+
+ /*
+ * Inject necessary function pointers into the module.
+ */
+ init(pg_g_threadlock,
+#ifdef ENABLE_NLS
+ libpq_gettext,
+#else
+ NULL,
+#endif
+ conn_errorMessage);
+
+ /* Set our asynchronous callbacks. */
+ conn->async_auth = flow;
+ conn->cleanup_async_auth = cleanup;
+
+ return true;
+}
diff --git a/src/interfaces/libpq/fe-auth-oauth-static.c b/src/interfaces/libpq/fe-auth-oauth-static.c
new file mode 100644
index 00000000000..25119bbb50c
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-oauth-static.c
@@ -0,0 +1,40 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-oauth-static.c
+ *
+ * Implements the builtin flow using the libpq-oauth.a staticlib.
+ * See also fe-auth-oauth-dynamic.c, which loads a plugin at runtime.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-auth-oauth-static.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#ifndef USE_LIBCURL
+#error this should only be compiled when OAuth support is enabled
+#endif
+
+#include "fe-auth-oauth.h"
+
+/* see libpq-oauth/oauth-curl.h */
+extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+/*
+ * Loads the builtin flow from libpq-oauth.a.
+ */
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ /* Set our asynchronous callbacks. */
+ conn->async_auth = pg_fe_run_oauth_flow;
+ conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
+
+ return true;
+}
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
index cf1a25e2ccc..0bad132b580 100644
--- a/src/interfaces/libpq/fe-auth-oauth.c
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -22,6 +22,7 @@
#include "fe-auth.h"
#include "fe-auth-oauth.h"
#include "mb/pg_wchar.h"
+#include "pg_config_paths.h"
/* The exported OAuth callback mechanism. */
static void *oauth_init(PGconn *conn, const char *password,
@@ -721,6 +722,21 @@ cleanup_user_oauth_flow(PGconn *conn)
state->async_ctx = NULL;
}
+#ifndef USE_LIBCURL
+
+/*
+ * This configuration doesn't support the builtin flow.
+ *
+ * Alternative implementations are in fe-auth-oauth-dynamic/-static.c.
+ */
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ return false;
+}
+
+#endif
+
/*
* Chooses an OAuth client flow for the connection, which will retrieve a Bearer
* token for presentation to the server.
@@ -792,18 +808,10 @@ setup_token_request(PGconn *conn, fe_oauth_state *state)
libpq_append_conn_error(conn, "user-defined OAuth flow failed");
goto fail;
}
- else
+ else if (!use_builtin_flow(conn, state))
{
-#if USE_LIBCURL
- /* Hand off to our built-in OAuth flow. */
- conn->async_auth = pg_fe_run_oauth_flow;
- conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
-
-#else
libpq_append_conn_error(conn, "no custom OAuth flows are available, and libpq was not built with libcurl support");
goto fail;
-
-#endif
}
return true;
diff --git a/src/interfaces/libpq/fe-auth-oauth.h b/src/interfaces/libpq/fe-auth-oauth.h
index 3f1a7503a01..687e664475f 100644
--- a/src/interfaces/libpq/fe-auth-oauth.h
+++ b/src/interfaces/libpq/fe-auth-oauth.h
@@ -33,12 +33,13 @@ typedef struct
PGconn *conn;
void *async_ctx;
+
+ void *builtin_flow;
} fe_oauth_state;
-extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
-extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
extern void pqClearOAuthToken(PGconn *conn);
extern bool oauth_unsafe_debugging_enabled(void);
+extern bool use_builtin_flow(PGconn *conn, fe_oauth_state *state);
/* Mechanisms in fe-auth-oauth.c */
extern const pg_fe_sasl_mech pg_oauth_mech;
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index 292fecf3320..bf880e053f8 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -18,6 +18,7 @@ libpq_sources = files(
'pqexpbuffer.c',
)
libpq_so_sources = [] # for shared lib, in addition to the above
+libpq_st_sources = [] # for static lib, in addition to the above
if host_system == 'windows'
libpq_sources += files('pthread-win32.c', 'win32.c')
@@ -38,8 +39,11 @@ if gssapi.found()
)
endif
+# Add the correct implementations of the OAuth flow, if requested, for both
+# shared and static builds.
if libcurl.found()
- libpq_sources += files('fe-auth-oauth-curl.c')
+ libpq_so_sources += files('fe-auth-oauth-dynamic.c')
+ libpq_st_sources += files('fe-auth-oauth-static.c')
endif
export_file = custom_target('libpq.exports',
@@ -59,7 +63,7 @@ libpq_c_args = ['-DSO_MAJOR_VERSION=5']
# more complexity than its worth (reusing object files requires also linking
# to the library on windows or breaks precompiled headers).
libpq_st = static_library('libpq',
- libpq_sources,
+ libpq_sources + libpq_st_sources,
include_directories: [libpq_inc],
c_args: libpq_c_args,
c_pch: pch_postgres_fe_h,
diff --git a/src/interfaces/libpq/nls.mk b/src/interfaces/libpq/nls.mk
index ae761265852..b87df277d93 100644
--- a/src/interfaces/libpq/nls.mk
+++ b/src/interfaces/libpq/nls.mk
@@ -13,15 +13,21 @@ GETTEXT_FILES = fe-auth.c \
fe-secure-common.c \
fe-secure-gssapi.c \
fe-secure-openssl.c \
- win32.c
-GETTEXT_TRIGGERS = libpq_append_conn_error:2 \
+ win32.c \
+ ../libpq-oauth/oauth-curl.c \
+ ../libpq-oauth/oauth-utils.c
+GETTEXT_TRIGGERS = actx_error:2 \
+ libpq_append_conn_error:2 \
libpq_append_error:2 \
libpq_gettext \
libpq_ngettext:1,2 \
+ oauth_parse_set_error:2 \
pqInternalNotice:2
-GETTEXT_FLAGS = libpq_append_conn_error:2:c-format \
+GETTEXT_FLAGS = actx_error:2:c-format \
+ libpq_append_conn_error:2:c-format \
libpq_append_error:2:c-format \
libpq_gettext:1:pass-c-format \
libpq_ngettext:1:pass-c-format \
libpq_ngettext:2:pass-c-format \
+ oauth_parse_set_error:2:c-format \
pqInternalNotice:2:c-format
diff --git a/src/makefiles/meson.build b/src/makefiles/meson.build
index 46d8da070e8..f2ba5b38124 100644
--- a/src/makefiles/meson.build
+++ b/src/makefiles/meson.build
@@ -201,6 +201,8 @@ pgxs_empty = [
'ICU_LIBS',
'LIBURING_CFLAGS', 'LIBURING_LIBS',
+
+ 'LIBCURL_CPPFLAGS', 'LIBCURL_LDFLAGS', 'LIBCURL_LDLIBS',
]
if host_system == 'windows' and cc.get_argument_syntax() != 'msvc'
--
2.34.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-11 00:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-14 16:12 ` Jacob Champion <[email protected]>
2025-04-14 18:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Wolfgang Walther <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-14 16:12 UTC (permalink / raw)
To: Wolfgang Walther <[email protected]>; +Cc: Jelte Fennema-Nio <[email protected]>; Christoph Berg <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Fri, Apr 11, 2025 at 9:21 AM Wolfgang Walther
<[email protected]> wrote:
> I tried to apply this patch to nixpkgs' libpq build [1]. First, I pinned
> a recent commit from master (one where the v5 patch will apply cleanly
> later) and enabled --with-libcurl [2].
(The [2] link is missing, I think.)
> 2. The statically linked build fails during configure:
I'm confused by this -- the build produces staticlibs alongside the
dynamically linked ones, so that's what I've been testing against.
What different options do you pass to configure for a "statically
linked build"?
> undefined reference to `psl_is_cookie_domain_acceptable'
> undefined reference to `nghttp2_session_check_request_allowed'
>
> I assume the many libs listed in Libs.private in libcurl.pc are not
> added automatically for this check?
Not unless there is some magic in PKG_CHECK_MODULES I've never heard
of (which is entirely possible!). Furthermore I imagine that the
transitive dependencies of all its dependencies are not added either.
Does your build method currently work for dependency forests like
libgssapi_krb5 and libldap? (I want to make sure I'm not accidentally
doing less work than we currently support for those other deps, but
I'm also not planning to add more feature work as part of this
particular open item.)
> I tried adding "make submake-libpq-oauth", but that doesn't exist.
There is no submake for this because no other targets depend on it.
Currently I don't have any plans to add one (but -C should work).
> When I do "make -C src/interfaces/libpq-oauth", I get this error:
>
> make: *** No rule to make target 'oauth-curl.o', needed by
> 'libpq-oauth-18.so'. Stop.
I cannot reproduce this. The CI seems happy, too. Is this patch the
only modification you've made to our build system, or are there more
changes?
I'm about to rewrite this part somewhat, so a deep dive may not be very helpful.
Thanks,
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-11 00:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-14 16:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-14 18:27 ` Wolfgang Walther <[email protected]>
2025-04-14 19:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Wolfgang Walther @ 2025-04-14 18:27 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Jelte Fennema-Nio <[email protected]>; Christoph Berg <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
Wolfgang Walther:
> So yes, not related to your patch. I do understand that PostgreSQL's
> autoconf build system is not designed for "static only", I am certainly
> not expecting you to fix that.
>
> I think meson will do better here, but I was not able to make that work,
> yet.
I did a basic meson build. Full postgresql package, not libpq-only.
The static-only build just works. On master that is. Same as the regular
build.
So yes, meson will handle the static stuff much better.
> I just tried the same thing on the bigger postgresql package, where the
> full build is run and not only libpq / libpq-oauth. It fails with the
> same error. No rule for oauth-curl.o.
Applying the v5 patch to the above meson build, will give me a different
error. This time for both the static-only and the regular build:
src/interfaces/libpq-oauth/meson.build:18:22: ERROR: File
oauth-curl.c does not exist.
This.. clears it up, because that file is indeed missing for me on disk.
I assume that's because this file is tracked as a rename in the v5
patch. I can apply this with git, but not directly in the nix build
system. TIL, I need to use "fetchpatch2" instead of "fetchpatch" for
that. Sure thing.
So, with the patch applied correctly, I get the following:
1. Meson regular build:
libpq-oauth-18.so
libpq.so
libpq.so.5
libpq.so.5.18
The libpq.so file has references to dlopen and libpq-auth-18.so, cool.
2. Meson static-only build:
libpq.a
libpq-oauth-18.a
The libpq.a file has no references to dlopen, but plenty of references
to curl stuff.
I'm not sure what the libpq-oauth-18.a file is for.
3. Back to the lipq-only build with autoconf, from where I started. I
only need to add the following line:
make -C src/interfaces/libpq-oauth install
and get this:
libpq-oauth-18.so
libpq.so
libpq.so.5
libpq.so.5.18
Sweet!
4. Of course the static-only build does not work with autoconf, but
that's expected.
So, sorry for the noise before. Now, that I know how to apply patches
with renames, I will try your next patch as well.
Best,
Wolfgang
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-11 00:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-14 16:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-14 18:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Wolfgang Walther <[email protected]>
@ 2025-04-14 19:14 ` Jacob Champion <[email protected]>
2025-04-14 20:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-14 19:14 UTC (permalink / raw)
To: Wolfgang Walther <[email protected]>; +Cc: Jelte Fennema-Nio <[email protected]>; Christoph Berg <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Mon, Apr 14, 2025 at 11:27 AM Wolfgang Walther
<[email protected]> wrote:
> src/interfaces/libpq-oauth/meson.build:18:22: ERROR: File
> oauth-curl.c does not exist.
>
> This.. clears it up, because that file is indeed missing for me on disk.
Aha! Okay, glad I don't need to track that down.
> libpq.a
> libpq-oauth-18.a
>
> The libpq.a file has no references to dlopen, but plenty of references
> to curl stuff.
Which references? libpq-oauth should be the only thing using Curl symbols:
$ nm src/interfaces/libpq/libpq.a | grep --count curl
0
$ nm src/interfaces/libpq-oauth/libpq-oauth-18.a | grep --count curl
116
> I'm not sure what the libpq-oauth-18.a file is for.
That implements the flow. You'll need to link that into your
application or it will complain about missing flow symbols. (I don't
think there's an easy way to combine the two libraries in our Autoconf
setup; the only ways I can think of right now would introduce a
circular dependency between libpq and libpq-oauth...)
Thanks!
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-11 00:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-14 16:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-14 18:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Wolfgang Walther <[email protected]>
2025-04-14 19:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-14 20:17 ` Jacob Champion <[email protected]>
2025-04-15 00:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-14 20:17 UTC (permalink / raw)
To: Wolfgang Walther <[email protected]>; +Cc: Jelte Fennema-Nio <[email protected]>; Christoph Berg <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Mon, Apr 14, 2025 at 12:46 PM Wolfgang Walther
<[email protected]> wrote:
> But that means we'd need a -lpq-oauth-18 or something like that in
> Libs.private in libpq.pc, right?
I believe so. I'm in the middle of the .pc stuff right now; v6 should
have the fixes as long as I don't get stuck.
Thanks,
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-11 00:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-14 16:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-14 18:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Wolfgang Walther <[email protected]>
2025-04-14 19:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-14 20:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-15 00:13 ` Jacob Champion <[email protected]>
2025-04-21 16:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-15 00:13 UTC (permalink / raw)
To: Wolfgang Walther <[email protected]>; +Cc: Jelte Fennema-Nio <[email protected]>; Christoph Berg <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>
On Mon, Apr 14, 2025 at 1:17 PM Jacob Champion
<[email protected]> wrote:
> I believe so. I'm in the middle of the .pc stuff right now; v6 should
> have the fixes as long as I don't get stuck.
Done in v6-0001. I think this is now architecturally complete, so if
reviewers are happy I can work on docs and the commit message. As a
summary:
- We provide a libpq-oauth-18.so module for shared builds, and a
corresponding .a for static builds, when OAuth is enabled.
- Platforms which cannot support the builtin flow now error out if you
request OAuth at configure time.
- When OAuth is enabled and there's no custom client flow, libpq.so
loads the module via dlopen(), which respects RPATH/LD_LIBRARY_PATH et
al. If it's not installed, OAuth doesn't continue.
- Static builds must link libpq-oauth-18.a explicitly. libpq.pc now
puts -lpq-oauth-18 in Libs.private, and libcurl in Requires.private.
- Internally, we compile separate versions of fe-auth-oauth.c to
handle the different cases (disabled, dynamic, static). This is
borrowed from src/common.
- The only new export from libpq is appendPQExpBufferVA. Other
internal symbols are shared with libpq-oauth via dependency injection.
v6-0002 is a WIP rename of the --with-libcurl option to
--with-oauth-client. I'm not sure I have all of the Meson corner cases
with auto_features figured out, but maybe it doesn't matter since it's
temporary (it targets a total of seven buildfarm animals, and once
they've switched we can remove the old name). I have added a separate
open item for this.
Thanks,
--Jacob
Attachments:
[application/octet-stream] v6-0002-oauth-rename-with-libcurl-to-with-oauth-client.patch (15.5K, ../../CAOYmi+=pFfkDFpRMXE87x73ee-viTgn6ztE_VkxnYE7owr_SbQ@mail.gmail.com/2-v6-0002-oauth-rename-with-libcurl-to-with-oauth-client.patch)
download | inline diff:
From 6ba708e512f18b6d0cada3f6657a4d7fd8b1058f Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Mon, 14 Apr 2025 16:34:09 -0700
Subject: [PATCH v6 2/2] oauth: rename --with-libcurl to --with-oauth-client
WIP, see mailing list.
Discussion: https://postgr.es/m/CAOYmi%2Bn9DHS_xUatuuspdC8tjtaMzY8P11Y9y5Fz%2B2pjikkL9g%40mail.gmail.com
---
.cirrus.tasks.yml | 2 +-
config/programs.m4 | 2 +-
configure | 56 +++++++++++++++----
configure.ac | 22 +++++---
meson.build | 16 ++++--
meson_options.txt | 6 +-
src/Makefile.global.in | 2 +-
src/include/pg_config.h.in | 7 ++-
src/interfaces/Makefile | 4 +-
src/interfaces/libpq/Makefile | 2 +-
src/interfaces/libpq/fe-auth-oauth.c | 4 +-
src/makefiles/meson.build | 3 +-
src/test/modules/oauth_validator/Makefile | 2 +-
src/test/modules/oauth_validator/meson.build | 2 +-
.../modules/oauth_validator/t/001_server.pl | 2 +-
.../modules/oauth_validator/t/002_client.pl | 2 +-
16 files changed, 94 insertions(+), 40 deletions(-)
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index 86a1fa9bbdb..30bdeb96738 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -350,11 +350,11 @@ LINUX_CONFIGURE_FEATURES: &LINUX_CONFIGURE_FEATURES >-
--with-gssapi
--with-icu
--with-ldap
- --with-libcurl
--with-libxml
--with-libxslt
--with-llvm
--with-lz4
+ --with-oauth-client
--with-pam
--with-perl
--with-python
diff --git a/config/programs.m4 b/config/programs.m4
index 0ad1e58b48d..328a4701cee 100644
--- a/config/programs.m4
+++ b/config/programs.m4
@@ -285,7 +285,7 @@ AC_DEFUN([PGAC_CHECK_STRIP],
AC_DEFUN([PGAC_CHECK_LIBCURL],
[
AC_CHECK_HEADER(curl/curl.h, [],
- [AC_MSG_ERROR([header file <curl/curl.h> is required for --with-libcurl])])
+ [AC_MSG_ERROR([header file <curl/curl.h> is required for --with-oauth-client])])
AC_CHECK_LIB(curl, curl_multi_init, [
AC_DEFINE([HAVE_LIBCURL], [1], [Define to 1 if you have the `curl' library (-lcurl).])
AC_SUBST(LIBCURL_LDLIBS, -lcurl)
diff --git a/configure b/configure
index df1da549c4c..a99b97006f2 100755
--- a/configure
+++ b/configure
@@ -713,7 +713,7 @@ LIBCURL_LDFLAGS
LIBCURL_CPPFLAGS
LIBCURL_LIBS
LIBCURL_CFLAGS
-with_libcurl
+with_oauth_client
with_uuid
LIBURING_LIBS
LIBURING_CFLAGS
@@ -874,6 +874,7 @@ with_libedit_preferred
with_liburing
with_uuid
with_ossp_uuid
+with_oauth_client
with_libcurl
with_libxml
with_libxslt
@@ -1590,7 +1591,8 @@ Optional Packages:
--with-liburing build with io_uring support, for asynchronous I/O
--with-uuid=LIB build contrib/uuid-ossp using LIB (bsd,e2fs,ossp)
--with-ossp-uuid obsolete spelling of --with-uuid=ossp
- --with-libcurl build with libcurl support
+ --with-oauth-client build OAuth Device Authorization support
+ --with-libcurl Deprecated. Use --with-oauth-client instead
--with-libxml build with XML support
--with-libxslt use XSLT support when building contrib/xml2
--with-system-tzdata=DIR
@@ -8918,8 +8920,36 @@ fi
#
# libcurl
#
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with libcurl support" >&5
-$as_echo_n "checking whether to build with libcurl support... " >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build OAuth Device Authorization support" >&5
+$as_echo_n "checking whether to build OAuth Device Authorization support... " >&6; }
+
+
+
+# Check whether --with-oauth-client was given.
+if test "${with_oauth_client+set}" = set; then :
+ withval=$with_oauth_client;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_BUILTIN_OAUTH 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-oauth-client option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_oauth_client=no
+
+fi
+
+
+
+# --with-libcurl is a deprecated equivalent. TODO: remove
@@ -8929,7 +8959,7 @@ if test "${with_libcurl+set}" = set; then :
case $withval in
yes)
-$as_echo "#define USE_LIBCURL 1" >>confdefs.h
+$as_echo "#define USE_BUILTIN_OAUTH 1" >>confdefs.h
;;
no)
@@ -8946,11 +8976,15 @@ else
fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_libcurl" >&5
-$as_echo "$with_libcurl" >&6; }
+if test "$with_libcurl" = yes ; then
+ with_oauth_client=yes
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_oauth_client" >&5
+$as_echo "$with_oauth_client" >&6; }
-if test "$with_libcurl" = yes ; then
+
+if test "$with_oauth_client" = yes ; then
# Check for libcurl 7.61.0 or higher (corresponding to RHEL8 and the ability
# to explicitly set TLS 1.3 ciphersuites).
@@ -12528,13 +12562,13 @@ fi
fi
-if test "$with_libcurl" = yes ; then
+if test "$with_oauth_client" = yes ; then
ac_fn_c_check_header_mongrel "$LINENO" "curl/curl.h" "ac_cv_header_curl_curl_h" "$ac_includes_default"
if test "x$ac_cv_header_curl_curl_h" = xyes; then :
else
- as_fn_error $? "header file <curl/curl.h> is required for --with-libcurl" "$LINENO" 5
+ as_fn_error $? "header file <curl/curl.h> is required for --with-oauth-client" "$LINENO" 5
fi
@@ -14350,7 +14384,7 @@ done
fi
-if test "$with_libcurl" = yes ; then
+if test "$with_oauth_client" = yes ; then
# Error out early if this platform can't support libpq-oauth.
if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
as_fn_error $? "client OAuth is not supported on this platform" "$LINENO" 5
diff --git a/configure.ac b/configure.ac
index 218aeea1b3b..7ffe8901250 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1022,13 +1022,21 @@ AC_SUBST(with_uuid)
#
# libcurl
#
-AC_MSG_CHECKING([whether to build with libcurl support])
-PGAC_ARG_BOOL(with, libcurl, no, [build with libcurl support],
- [AC_DEFINE([USE_LIBCURL], 1, [Define to 1 to build with libcurl support. (--with-libcurl)])])
-AC_MSG_RESULT([$with_libcurl])
-AC_SUBST(with_libcurl)
+AC_MSG_CHECKING([whether to build OAuth Device Authorization support])
+PGAC_ARG_BOOL(with, oauth-client, no, [build OAuth Device Authorization support],
+ [AC_DEFINE([USE_BUILTIN_OAUTH], 1, [Define to 1 to build with OAuth Device Authorization support. (--with-oauth-client)])])
+# --with-libcurl is a deprecated equivalent. TODO: remove
+PGAC_ARG_BOOL(with, libcurl, no, [Deprecated. Use --with-oauth-client instead],
+ [AC_DEFINE([USE_BUILTIN_OAUTH], 1, [Define to 1 to build with OAuth Device Authorization support. (--with-oauth-client)])])
if test "$with_libcurl" = yes ; then
+ with_oauth_client=yes
+fi
+
+AC_MSG_RESULT([$with_oauth_client])
+AC_SUBST(with_oauth_client)
+
+if test "$with_oauth_client" = yes ; then
# Check for libcurl 7.61.0 or higher (corresponding to RHEL8 and the ability
# to explicitly set TLS 1.3 ciphersuites).
PKG_CHECK_MODULES(LIBCURL, [libcurl >= 7.61.0])
@@ -1348,7 +1356,7 @@ failure. It is possible the compiler isn't looking in the proper directory.
Use --without-zlib to disable zlib support.])])
fi
-if test "$with_libcurl" = yes ; then
+if test "$with_oauth_client" = yes ; then
PGAC_CHECK_LIBCURL
fi
@@ -1645,7 +1653,7 @@ if test "$PORTNAME" = "win32" ; then
AC_CHECK_HEADERS(crtdefs.h)
fi
-if test "$with_libcurl" = yes ; then
+if test "$with_oauth_client" = yes ; then
# Error out early if this platform can't support libpq-oauth.
if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
AC_MSG_ERROR([client OAuth is not supported on this platform])
diff --git a/meson.build b/meson.build
index b436c362147..ab34d69dd1a 100644
--- a/meson.build
+++ b/meson.build
@@ -860,13 +860,19 @@ endif
# Library: libcurl
###############################################################
-libcurlopt = get_option('libcurl')
+oauthopt = get_option('oauth-client')
oauth_flow_supported = false
-if not libcurlopt.disabled()
+# -Dlibcurl is a deprecated equivalent. TODO: remove
+libcurlopt = get_option('libcurl')
+if oauthopt.auto() or libcurlopt.enabled()
+ oauthopt = libcurlopt
+endif
+
+if not oauthopt.disabled()
# Check for libcurl 7.61.0 or higher (corresponding to RHEL8 and the ability
# to explicitly set TLS 1.3 ciphersuites).
- libcurl = dependency('libcurl', version: '>= 7.61.0', required: libcurlopt)
+ libcurl = dependency('libcurl', version: '>= 7.61.0', required: oauthopt)
if libcurl.found()
# Check to see whether the current platform supports thread-safe Curl
# initialization.
@@ -950,8 +956,8 @@ if not libcurlopt.disabled()
)
if oauth_flow_supported
- cdata.set('USE_LIBCURL', 1)
- elif libcurlopt.enabled()
+ cdata.set('USE_BUILTIN_OAUTH', 1)
+ elif oauthopt.enabled()
error('client OAuth is not supported on this platform')
endif
diff --git a/meson_options.txt b/meson_options.txt
index dd7126da3a7..5d828b491a9 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -100,8 +100,9 @@ option('icu', type: 'feature', value: 'auto',
option('ldap', type: 'feature', value: 'auto',
description: 'LDAP support')
+# Deprecated. TODO: remove
option('libcurl', type : 'feature', value: 'auto',
- description: 'libcurl support')
+ description: 'Deprecated. Use -Doauth-client instead')
option('libedit_preferred', type: 'boolean', value: false,
description: 'Prefer BSD Libedit over GNU Readline')
@@ -121,6 +122,9 @@ option('llvm', type: 'feature', value: 'disabled',
option('lz4', type: 'feature', value: 'auto',
description: 'LZ4 support')
+option('oauth-client', type : 'feature', value: 'auto',
+ description: 'OAuth Device Authorization support')
+
option('nls', type: 'feature', value: 'auto',
description: 'Native language support')
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index eb9b5de75b4..0c0822c314b 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -195,11 +195,11 @@ with_systemd = @with_systemd@
with_gssapi = @with_gssapi@
with_krb_srvnam = @with_krb_srvnam@
with_ldap = @with_ldap@
-with_libcurl = @with_libcurl@
with_liburing = @with_liburing@
with_libxml = @with_libxml@
with_libxslt = @with_libxslt@
with_llvm = @with_llvm@
+with_oauth_client = @with_oauth_client@
with_system_tzdata = @with_system_tzdata@
with_uuid = @with_uuid@
with_zlib = @with_zlib@
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 9891b9b05c3..1e189581896 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -677,6 +677,10 @@
/* Define to 1 to build with BSD Authentication support. (--with-bsd-auth) */
#undef USE_BSD_AUTH
+/* Define to 1 to build with OAuth Device Authorization support.
+ (--with-oauth-client) */
+#undef USE_BUILTIN_OAUTH
+
/* Define to build with ICU support. (--with-icu) */
#undef USE_ICU
@@ -686,9 +690,6 @@
/* Define to 1 to build with LDAP support. (--with-ldap) */
#undef USE_LDAP
-/* Define to 1 to build with libcurl support. (--with-libcurl) */
-#undef USE_LIBCURL
-
/* Define to build with io_uring support. (--with-liburing) */
#undef USE_LIBURING
diff --git a/src/interfaces/Makefile b/src/interfaces/Makefile
index e6822caa206..ccb4a9b6e69 100644
--- a/src/interfaces/Makefile
+++ b/src/interfaces/Makefile
@@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = libpq ecpg
-ifeq ($(with_libcurl), yes)
+ifeq ($(with_oauth_client), yes)
SUBDIRS += libpq-oauth
else
ALWAYS_SUBDIRS += libpq-oauth
@@ -26,7 +26,7 @@ $(recurse_always)
all-ecpg-recurse: all-libpq-recurse
install-ecpg-recurse: install-libpq-recurse
-ifeq ($(with_libcurl), yes)
+ifeq ($(with_oauth_client), yes)
all-libpq-oauth-recurse: all-libpq-recurse
install-libpq-oauth-recurse: install-libpq-recurse
endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index d4c20066ce4..a835d94a142 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -102,7 +102,7 @@ ifeq ($(with_ssl),openssl)
PKG_CONFIG_REQUIRES_PRIVATE = libssl, libcrypto
endif
-ifeq ($(with_libcurl),yes)
+ifeq ($(with_oauth_client),yes)
# libpq.so doesn't link against libcurl, but libpq.a needs libpq-oauth, and
# libpq-oauth needs libcurl. Put both into *.private.
PKG_CONFIG_REQUIRES_PRIVATE += libcurl
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
index 5c285adccbd..af6db2eec28 100644
--- a/src/interfaces/libpq/fe-auth-oauth.c
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -738,7 +738,7 @@ cleanup_user_oauth_flow(PGconn *conn)
* executable.
*/
-#if !defined(USE_LIBCURL)
+#if !defined(USE_BUILTIN_OAUTH)
/*
* This configuration doesn't support the builtin flow.
@@ -859,7 +859,7 @@ use_builtin_flow(PGconn *conn, fe_oauth_state *state)
return true;
}
-#endif /* USE_LIBCURL */
+#endif /* USE_BUILTIN_OAUTH */
/*
diff --git a/src/makefiles/meson.build b/src/makefiles/meson.build
index f2ba5b38124..6160c172d75 100644
--- a/src/makefiles/meson.build
+++ b/src/makefiles/meson.build
@@ -75,6 +75,8 @@ pgxs_kv = {
'with_krb_srvnam': get_option('krb_srvnam'),
'krb_srvtab': krb_srvtab,
+ 'with_oauth_client': oauth_flow_supported ? 'yes' : 'no',
+
'STRIP': ' '.join(strip_cmd),
'STRIP_STATIC_LIB': ' '.join(strip_static_cmd),
'STRIP_SHARED_LIB': ' '.join(strip_shared_cmd),
@@ -233,7 +235,6 @@ pgxs_deps = {
'gssapi': gssapi,
'icu': icu,
'ldap': ldap,
- 'libcurl': libcurl,
'liburing': liburing,
'libxml': libxml,
'libxslt': libxslt,
diff --git a/src/test/modules/oauth_validator/Makefile b/src/test/modules/oauth_validator/Makefile
index 05b9f06ed73..57733dc533f 100644
--- a/src/test/modules/oauth_validator/Makefile
+++ b/src/test/modules/oauth_validator/Makefile
@@ -34,7 +34,7 @@ include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
export PYTHON
-export with_libcurl
+export with_oauth_client
export with_python
endif
diff --git a/src/test/modules/oauth_validator/meson.build b/src/test/modules/oauth_validator/meson.build
index 36d1b26369f..84d169cb8e1 100644
--- a/src/test/modules/oauth_validator/meson.build
+++ b/src/test/modules/oauth_validator/meson.build
@@ -78,7 +78,7 @@ tests += {
],
'env': {
'PYTHON': python.path(),
- 'with_libcurl': libcurl.found() ? 'yes' : 'no',
+ 'with_oauth_client': oauth_flow_supported ? 'yes' : 'no',
'with_python': 'yes',
},
},
diff --git a/src/test/modules/oauth_validator/t/001_server.pl b/src/test/modules/oauth_validator/t/001_server.pl
index d88994abc24..01b5e1c3c43 100644
--- a/src/test/modules/oauth_validator/t/001_server.pl
+++ b/src/test/modules/oauth_validator/t/001_server.pl
@@ -33,7 +33,7 @@ unless (check_pg_config("#define HAVE_SYS_EVENT_H 1")
'OAuth server-side tests are not supported on this platform';
}
-if ($ENV{with_libcurl} ne 'yes')
+if ($ENV{with_oauth_client} ne 'yes')
{
plan skip_all => 'client-side OAuth not supported by this build';
}
diff --git a/src/test/modules/oauth_validator/t/002_client.pl b/src/test/modules/oauth_validator/t/002_client.pl
index 54769f12f57..1e329b328a6 100644
--- a/src/test/modules/oauth_validator/t/002_client.pl
+++ b/src/test/modules/oauth_validator/t/002_client.pl
@@ -104,7 +104,7 @@ $node->log_check("validator receives correct token",
$log_start,
log_like => [ qr/oauth_validator: token="my-token", role="$user"/, ]);
-if ($ENV{with_libcurl} ne 'yes')
+if ($ENV{with_oauth_client} ne 'yes')
{
# libpq should help users out if no OAuth support is built in.
test(
--
2.34.1
[application/octet-stream] v6-0001-WIP-split-Device-Authorization-flow-into-dlopen-d.patch (44.8K, ../../CAOYmi+=pFfkDFpRMXE87x73ee-viTgn6ztE_VkxnYE7owr_SbQ@mail.gmail.com/3-v6-0001-WIP-split-Device-Authorization-flow-into-dlopen-d.patch)
download | inline diff:
From a202bd932ea390c97df10fcbb0cc3b60419453be Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Wed, 26 Mar 2025 10:55:28 -0700
Subject: [PATCH v6 1/2] WIP: split Device Authorization flow into dlopen'd
module
See notes on mailing list.
Co-authored-by: Daniel Gustafsson <[email protected]>
---
config/programs.m4 | 17 +-
configure | 50 ++++-
configure.ac | 26 ++-
meson.build | 32 ++-
src/Makefile.global.in | 3 +
src/interfaces/Makefile | 12 ++
src/interfaces/libpq-oauth/Makefile | 65 ++++++
src/interfaces/libpq-oauth/README | 43 ++++
src/interfaces/libpq-oauth/exports.txt | 4 +
src/interfaces/libpq-oauth/meson.build | 43 ++++
.../oauth-curl.c} | 60 +++---
src/interfaces/libpq-oauth/oauth-curl.h | 24 +++
src/interfaces/libpq-oauth/oauth-utils.c | 202 ++++++++++++++++++
src/interfaces/libpq-oauth/oauth-utils.h | 35 +++
src/interfaces/libpq/Makefile | 36 +++-
src/interfaces/libpq/exports.txt | 1 +
src/interfaces/libpq/fe-auth-oauth.c | 151 ++++++++++++-
src/interfaces/libpq/fe-auth-oauth.h | 5 +-
src/interfaces/libpq/meson.build | 28 ++-
src/interfaces/libpq/nls.mk | 12 +-
src/makefiles/meson.build | 2 +
21 files changed, 763 insertions(+), 88 deletions(-)
create mode 100644 src/interfaces/libpq-oauth/Makefile
create mode 100644 src/interfaces/libpq-oauth/README
create mode 100644 src/interfaces/libpq-oauth/exports.txt
create mode 100644 src/interfaces/libpq-oauth/meson.build
rename src/interfaces/{libpq/fe-auth-oauth-curl.c => libpq-oauth/oauth-curl.c} (98%)
create mode 100644 src/interfaces/libpq-oauth/oauth-curl.h
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.c
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.h
diff --git a/config/programs.m4 b/config/programs.m4
index 0a07feb37cc..0ad1e58b48d 100644
--- a/config/programs.m4
+++ b/config/programs.m4
@@ -286,9 +286,20 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
[
AC_CHECK_HEADER(curl/curl.h, [],
[AC_MSG_ERROR([header file <curl/curl.h> is required for --with-libcurl])])
- AC_CHECK_LIB(curl, curl_multi_init, [],
+ AC_CHECK_LIB(curl, curl_multi_init, [
+ AC_DEFINE([HAVE_LIBCURL], [1], [Define to 1 if you have the `curl' library (-lcurl).])
+ AC_SUBST(LIBCURL_LDLIBS, -lcurl)
+ ],
[AC_MSG_ERROR([library 'curl' does not provide curl_multi_init])])
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+ LIBS="$LIBCURL_LDLIBS $LIBS"
+
# Check to see whether the current platform supports threadsafe Curl
# initialization.
AC_CACHE_CHECK([for curl_global_init thread safety], [pgac_cv__libcurl_threadsafe_init],
@@ -338,4 +349,8 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
*** lookups. Rebuild libcurl with the AsynchDNS feature enabled in order
*** to use it with libpq.])
fi
+
+ CPPFLAGS=$pgac_save_CPPFLAGS
+ LDFLAGS=$pgac_save_LDFLAGS
+ LIBS=$pgac_save_LIBS
])# PGAC_CHECK_LIBCURL
diff --git a/configure b/configure
index 8f4a5ab28ec..df1da549c4c 100755
--- a/configure
+++ b/configure
@@ -655,6 +655,7 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+LIBCURL_LDLIBS
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -708,6 +709,8 @@ XML2_LIBS
XML2_CFLAGS
XML2_CONFIG
with_libxml
+LIBCURL_LDFLAGS
+LIBCURL_CPPFLAGS
LIBCURL_LIBS
LIBCURL_CFLAGS
with_libcurl
@@ -9042,19 +9045,27 @@ $as_echo "yes" >&6; }
fi
- # We only care about -I, -D, and -L switches;
- # note that -lcurl will be added by PGAC_CHECK_LIBCURL below.
+ # Curl's flags are kept separate from the standard CPPFLAGS/LDFLAGS. We use
+ # them only for libpq-oauth.
+ LIBCURL_CPPFLAGS=
+ LIBCURL_LDFLAGS=
+
+ # We only care about -I, -D, and -L switches. Note that -lcurl will be added
+ # to LIBCURL_LDLIBS by PGAC_CHECK_LIBCURL, below.
for pgac_option in $LIBCURL_CFLAGS; do
case $pgac_option in
- -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+ -I*|-D*) LIBCURL_CPPFLAGS="$LIBCURL_CPPFLAGS $pgac_option";;
esac
done
for pgac_option in $LIBCURL_LIBS; do
case $pgac_option in
- -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+ -L*) LIBCURL_LDFLAGS="$LIBCURL_LDFLAGS $pgac_option";;
esac
done
+
+
+
# OAuth requires python for testing
if test "$with_python" != yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: *** OAuth support tests require --with-python to run" >&5
@@ -12517,9 +12528,6 @@ fi
fi
-# XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
-# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
-# dependency on that platform?
if test "$with_libcurl" = yes ; then
ac_fn_c_check_header_mongrel "$LINENO" "curl/curl.h" "ac_cv_header_curl_curl_h" "$ac_includes_default"
@@ -12567,17 +12575,26 @@ fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_curl_curl_multi_init" >&5
$as_echo "$ac_cv_lib_curl_curl_multi_init" >&6; }
if test "x$ac_cv_lib_curl_curl_multi_init" = xyes; then :
- cat >>confdefs.h <<_ACEOF
-#define HAVE_LIBCURL 1
-_ACEOF
- LIBS="-lcurl $LIBS"
+
+$as_echo "#define HAVE_LIBCURL 1" >>confdefs.h
+
+ LIBCURL_LDLIBS=-lcurl
+
else
as_fn_error $? "library 'curl' does not provide curl_multi_init" "$LINENO" 5
fi
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+ LIBS="$LIBCURL_LDLIBS $LIBS"
+
# Check to see whether the current platform supports threadsafe Curl
# initialization.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_global_init thread safety" >&5
@@ -12681,6 +12698,10 @@ $as_echo "$pgac_cv__libcurl_async_dns" >&6; }
*** to use it with libpq." "$LINENO" 5
fi
+ CPPFLAGS=$pgac_save_CPPFLAGS
+ LDFLAGS=$pgac_save_LDFLAGS
+ LIBS=$pgac_save_LIBS
+
fi
if test "$with_gssapi" = yes ; then
@@ -14329,6 +14350,13 @@ done
fi
+if test "$with_libcurl" = yes ; then
+ # Error out early if this platform can't support libpq-oauth.
+ if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
+ as_fn_error $? "client OAuth is not supported on this platform" "$LINENO" 5
+ fi
+fi
+
##
## Types, structures, compiler characteristics
##
diff --git a/configure.ac b/configure.ac
index fc5f7475d07..218aeea1b3b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1033,19 +1033,27 @@ if test "$with_libcurl" = yes ; then
# to explicitly set TLS 1.3 ciphersuites).
PKG_CHECK_MODULES(LIBCURL, [libcurl >= 7.61.0])
- # We only care about -I, -D, and -L switches;
- # note that -lcurl will be added by PGAC_CHECK_LIBCURL below.
+ # Curl's flags are kept separate from the standard CPPFLAGS/LDFLAGS. We use
+ # them only for libpq-oauth.
+ LIBCURL_CPPFLAGS=
+ LIBCURL_LDFLAGS=
+
+ # We only care about -I, -D, and -L switches. Note that -lcurl will be added
+ # to LIBCURL_LDLIBS by PGAC_CHECK_LIBCURL, below.
for pgac_option in $LIBCURL_CFLAGS; do
case $pgac_option in
- -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+ -I*|-D*) LIBCURL_CPPFLAGS="$LIBCURL_CPPFLAGS $pgac_option";;
esac
done
for pgac_option in $LIBCURL_LIBS; do
case $pgac_option in
- -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+ -L*) LIBCURL_LDFLAGS="$LIBCURL_LDFLAGS $pgac_option";;
esac
done
+ AC_SUBST(LIBCURL_CPPFLAGS)
+ AC_SUBST(LIBCURL_LDFLAGS)
+
# OAuth requires python for testing
if test "$with_python" != yes; then
AC_MSG_WARN([*** OAuth support tests require --with-python to run])
@@ -1340,9 +1348,6 @@ failure. It is possible the compiler isn't looking in the proper directory.
Use --without-zlib to disable zlib support.])])
fi
-# XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
-# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
-# dependency on that platform?
if test "$with_libcurl" = yes ; then
PGAC_CHECK_LIBCURL
fi
@@ -1640,6 +1645,13 @@ if test "$PORTNAME" = "win32" ; then
AC_CHECK_HEADERS(crtdefs.h)
fi
+if test "$with_libcurl" = yes ; then
+ # Error out early if this platform can't support libpq-oauth.
+ if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
+ AC_MSG_ERROR([client OAuth is not supported on this platform])
+ fi
+fi
+
##
## Types, structures, compiler characteristics
##
diff --git a/meson.build b/meson.build
index 27717ad8976..b436c362147 100644
--- a/meson.build
+++ b/meson.build
@@ -107,6 +107,7 @@ os_deps = []
backend_both_deps = []
backend_deps = []
libpq_deps = []
+libpq_oauth_deps = []
pg_sysroot = ''
@@ -860,13 +861,13 @@ endif
###############################################################
libcurlopt = get_option('libcurl')
+oauth_flow_supported = false
+
if not libcurlopt.disabled()
# Check for libcurl 7.61.0 or higher (corresponding to RHEL8 and the ability
# to explicitly set TLS 1.3 ciphersuites).
libcurl = dependency('libcurl', version: '>= 7.61.0', required: libcurlopt)
if libcurl.found()
- cdata.set('USE_LIBCURL', 1)
-
# Check to see whether the current platform supports thread-safe Curl
# initialization.
libcurl_threadsafe_init = false
@@ -938,6 +939,22 @@ if not libcurlopt.disabled()
endif
endif
+ # Check that the current platform supports our builtin flow. This requires
+ # libcurl and one of either epoll or kqueue.
+ oauth_flow_supported = (
+ libcurl.found()
+ and (cc.check_header('sys/event.h', required: false,
+ args: test_c_args, include_directories: postgres_inc)
+ or cc.check_header('sys/epoll.h', required: false,
+ args: test_c_args, include_directories: postgres_inc))
+ )
+
+ if oauth_flow_supported
+ cdata.set('USE_LIBCURL', 1)
+ elif libcurlopt.enabled()
+ error('client OAuth is not supported on this platform')
+ endif
+
else
libcurl = not_found_dep
endif
@@ -3251,17 +3268,18 @@ libpq_deps += [
gssapi,
ldap_r,
- # XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
- # during gss_acquire_cred(). This is possibly related to Curl's Heimdal
- # dependency on that platform?
- libcurl,
libintl,
ssl,
]
+libpq_oauth_deps += [
+ libcurl,
+]
+
subdir('src/interfaces/libpq')
-# fe_utils depends on libpq
+# fe_utils and libpq-oauth depends on libpq
subdir('src/fe_utils')
+subdir('src/interfaces/libpq-oauth')
# for frontend binaries
frontend_code = declare_dependency(
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 737b2dd1869..eb9b5de75b4 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -343,6 +343,9 @@ perl_embed_ldflags = @perl_embed_ldflags@
AWK = @AWK@
LN_S = @LN_S@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBCURL_LDFLAGS = @LIBCURL_LDFLAGS@
+LIBCURL_LDLIBS = @LIBCURL_LDLIBS@
MSGFMT = @MSGFMT@
MSGFMT_FLAGS = @MSGFMT_FLAGS@
MSGMERGE = @MSGMERGE@
diff --git a/src/interfaces/Makefile b/src/interfaces/Makefile
index 7d56b29d28f..e6822caa206 100644
--- a/src/interfaces/Makefile
+++ b/src/interfaces/Makefile
@@ -14,7 +14,19 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = libpq ecpg
+ifeq ($(with_libcurl), yes)
+SUBDIRS += libpq-oauth
+else
+ALWAYS_SUBDIRS += libpq-oauth
+endif
+
$(recurse)
+$(recurse_always)
all-ecpg-recurse: all-libpq-recurse
install-ecpg-recurse: install-libpq-recurse
+
+ifeq ($(with_libcurl), yes)
+all-libpq-oauth-recurse: all-libpq-recurse
+install-libpq-oauth-recurse: install-libpq-recurse
+endif
diff --git a/src/interfaces/libpq-oauth/Makefile b/src/interfaces/libpq-oauth/Makefile
new file mode 100644
index 00000000000..5fd251a1d27
--- /dev/null
+++ b/src/interfaces/libpq-oauth/Makefile
@@ -0,0 +1,65 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for libpq-oauth
+#
+# Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/interfaces/libpq-oauth/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/interfaces/libpq-oauth
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+PGFILEDESC = "libpq-oauth - device authorization OAuth support"
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
+NAME = pq-oauth-$(MAJORVERSION)
+
+# Force the name "libpq-oauth" for both the static and shared libraries.
+override shlib := lib$(NAME)$(DLSUFFIX)
+override stlib := lib$(NAME).a
+
+override CPPFLAGS := -I$(libpq_srcdir) -I$(top_builddir)/src/port $(LIBCURL_CPPFLAGS) $(CPPFLAGS)
+
+OBJS = \
+ $(WIN32RES) \
+ oauth-curl.o
+
+# The shared library needs additional glue symbols.
+$(shlib): OBJS += oauth-utils.o
+$(shlib): oauth-utils.o
+
+SHLIB_LINK_INTERNAL = $(libpq_pgport_shlib)
+SHLIB_LINK = $(LIBCURL_LDFLAGS) $(LIBCURL_LDLIBS)
+SHLIB_PREREQS = submake-libpq
+SHLIB_EXPORTS = exports.txt
+
+# Disable -bundle_loader on macOS.
+BE_DLLLIBS =
+
+# By default, a library without an SONAME doesn't get a static library, so we
+# add it to the build explicitly.
+all: all-lib all-static-lib
+
+# Shared library stuff
+include $(top_srcdir)/src/Makefile.shlib
+
+# Ignore the standard rules for SONAME-less installation; we want both the
+# static and shared libraries to go into libdir.
+install: all installdirs $(stlib) $(shlib)
+ $(INSTALL_SHLIB) $(shlib) '$(DESTDIR)$(libdir)/$(shlib)'
+ $(INSTALL_STLIB) $(stlib) '$(DESTDIR)$(libdir)/$(stlib)'
+
+installdirs:
+ $(MKDIR_P) '$(DESTDIR)$(libdir)'
+
+uninstall:
+ rm -f '$(DESTDIR)$(libdir)/$(stlib)'
+ rm -f '$(DESTDIR)$(libdir)/$(shlib)'
+
+clean distclean: clean-lib
+ rm -f $(OBJS) oauth-utils.o
diff --git a/src/interfaces/libpq-oauth/README b/src/interfaces/libpq-oauth/README
new file mode 100644
index 00000000000..45def6c1ab6
--- /dev/null
+++ b/src/interfaces/libpq-oauth/README
@@ -0,0 +1,43 @@
+libpq-oauth is an optional module implementing the Device Authorization flow for
+OAuth clients (RFC 8628). It was originally developed as part of libpq core and
+later split out as its own shared library in order to isolate its dependency on
+libcurl. (End users who don't want the Curl dependency can simply choose not to
+install this module.)
+
+If a connection string allows the use of OAuth, and the server asks for it, and
+a libpq client has not installed its own custom OAuth flow, libpq will attempt
+to delay-load this module using dlopen() and the following ABI. Failure to load
+results in a failed connection.
+
+= Load-Time ABI =
+
+This module ABI is an internal implementation detail, so it's subject to change
+across major releases; the name of the module (libpq-oauth-MAJOR) reflects this.
+The module exports the following symbols:
+
+- PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+- void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+pg_fe_run_oauth_flow and pg_fe_cleanup_oauth_flow are implementations of
+conn->async_auth and conn->cleanup_async_auth, respectively.
+
+- void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+
+At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
+libpq_gettext(), which must be injected by libpq using this initialization
+function before the flow is run. It also relies on libpq to expose
+conn->errorMessage, via the errmsg_impl.
+
+This dependency injection is done to ensure that the module ABI is decoupled
+from the internals of `struct pg_conn`. This way we can safely search the
+standard dlopen() paths (e.g. RPATH, LD_LIBRARY_PATH, the SO cache) for an
+implementation module to use, even if that module wasn't compiled at the same
+time as libpq.
+
+= Static Build =
+
+The static library libpq.a does not perform any dynamic loading. If the builtin
+flow is enabled, the application is expected to link against libpq-oauth-*.a
+directly to provide the necessary symbols.
diff --git a/src/interfaces/libpq-oauth/exports.txt b/src/interfaces/libpq-oauth/exports.txt
new file mode 100644
index 00000000000..6891a83dbf9
--- /dev/null
+++ b/src/interfaces/libpq-oauth/exports.txt
@@ -0,0 +1,4 @@
+# src/interfaces/libpq-oauth/exports.txt
+libpq_oauth_init 1
+pg_fe_run_oauth_flow 2
+pg_fe_cleanup_oauth_flow 3
diff --git a/src/interfaces/libpq-oauth/meson.build b/src/interfaces/libpq-oauth/meson.build
new file mode 100644
index 00000000000..cf597e1da1e
--- /dev/null
+++ b/src/interfaces/libpq-oauth/meson.build
@@ -0,0 +1,43 @@
+# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+
+if not oauth_flow_supported
+ subdir_done()
+endif
+
+libpq_oauth_sources = files(
+ 'oauth-curl.c',
+)
+
+# The shared library needs additional glue symbols.
+libpq_oauth_so_sources = files(
+ 'oauth-utils.c',
+)
+
+export_file = custom_target('libpq-oauth.exports',
+ kwargs: gen_export_kwargs,
+)
+
+# port needs to be in include path due to pthread-win32.h
+libpq_oauth_inc = include_directories('.', '../libpq', '../../port')
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
+libpq_oauth_name = 'libpq-oauth-@0@'.format(pg_version_major)
+
+libpq_oauth_st = static_library(libpq_oauth_name,
+ libpq_oauth_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_stlib_code, libpq_oauth_deps],
+ kwargs: default_lib_args,
+)
+
+libpq_oauth_so = shared_module(libpq_oauth_name,
+ libpq_oauth_sources + libpq_oauth_so_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_shlib_code, libpq, libpq_oauth_deps],
+ link_depends: export_file,
+ link_args: export_fmt.format(export_file.full_path()),
+ kwargs: default_lib_args,
+)
diff --git a/src/interfaces/libpq/fe-auth-oauth-curl.c b/src/interfaces/libpq-oauth/oauth-curl.c
similarity index 98%
rename from src/interfaces/libpq/fe-auth-oauth-curl.c
rename to src/interfaces/libpq-oauth/oauth-curl.c
index cd9c0323bb6..d52125415bc 100644
--- a/src/interfaces/libpq/fe-auth-oauth-curl.c
+++ b/src/interfaces/libpq-oauth/oauth-curl.c
@@ -1,6 +1,6 @@
/*-------------------------------------------------------------------------
*
- * fe-auth-oauth-curl.c
+ * oauth-curl.c
* The libcurl implementation of OAuth/OIDC authentication, using the
* OAuth Device Authorization Grant (RFC 8628).
*
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * src/interfaces/libpq/fe-auth-oauth-curl.c
+ * src/interfaces/libpq-oauth/oauth-curl.c
*
*-------------------------------------------------------------------------
*/
@@ -17,20 +17,23 @@
#include <curl/curl.h>
#include <math.h>
-#ifdef HAVE_SYS_EPOLL_H
+#include <unistd.h>
+
+#if defined(HAVE_SYS_EPOLL_H)
#include <sys/epoll.h>
#include <sys/timerfd.h>
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
#include <sys/event.h>
+#else
+#error libpq-oauth is not supported on this platform
#endif
-#include <unistd.h>
#include "common/jsonapi.h"
#include "fe-auth.h"
#include "fe-auth-oauth.h"
-#include "libpq-int.h"
#include "mb/pg_wchar.h"
+#include "oauth-curl.h"
+#include "oauth-utils.h"
/*
* It's generally prudent to set a maximum response size to buffer in memory,
@@ -1110,7 +1113,7 @@ parse_access_token(struct async_ctx *actx, struct token *tok)
static bool
setup_multiplexer(struct async_ctx *actx)
{
-#ifdef HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct epoll_event ev = {.events = EPOLLIN};
actx->mux = epoll_create1(EPOLL_CLOEXEC);
@@ -1134,8 +1137,7 @@ setup_multiplexer(struct async_ctx *actx)
}
return true;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
actx->mux = kqueue();
if (actx->mux < 0)
{
@@ -1158,10 +1160,9 @@ setup_multiplexer(struct async_ctx *actx)
}
return true;
+#else
+#error setup_multiplexer is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support the Device Authorization flow on this platform");
- return false;
}
/*
@@ -1174,7 +1175,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
{
struct async_ctx *actx = ctx;
-#ifdef HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct epoll_event ev = {0};
int res;
int op = EPOLL_CTL_ADD;
@@ -1230,8 +1231,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
}
return 0;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
struct kevent ev[2] = {0};
struct kevent ev_out[2];
struct timespec timeout = {0};
@@ -1312,10 +1312,9 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
}
return 0;
+#else
+#error register_socket is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support multiplexer sockets on this platform");
- return -1;
}
/*
@@ -1334,7 +1333,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
static bool
set_timer(struct async_ctx *actx, long timeout)
{
-#if HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct itimerspec spec = {0};
if (timeout < 0)
@@ -1363,8 +1362,7 @@ set_timer(struct async_ctx *actx, long timeout)
}
return true;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
struct kevent ev;
#ifdef __NetBSD__
@@ -1419,10 +1417,9 @@ set_timer(struct async_ctx *actx, long timeout)
}
return true;
+#else
+#error set_timer is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support timers on this platform");
- return false;
}
/*
@@ -1433,7 +1430,7 @@ set_timer(struct async_ctx *actx, long timeout)
static int
timer_expired(struct async_ctx *actx)
{
-#if HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct itimerspec spec = {0};
if (timerfd_gettime(actx->timerfd, &spec) < 0)
@@ -1453,8 +1450,7 @@ timer_expired(struct async_ctx *actx)
/* If the remaining time to expiration is zero, we're done. */
return (spec.it_value.tv_sec == 0
&& spec.it_value.tv_nsec == 0);
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
int res;
/* Is the timer queue ready? */
@@ -1466,10 +1462,9 @@ timer_expired(struct async_ctx *actx)
}
return (res > 0);
+#else
+#error timer_expired is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support timers on this platform");
- return -1;
}
/*
@@ -2487,8 +2482,9 @@ prompt_user(struct async_ctx *actx, PGconn *conn)
.verification_uri_complete = actx->authz.verification_uri_complete,
.expires_in = actx->authz.expires_in,
};
+ PQauthDataHook_type hook = PQgetAuthDataHook();
- res = PQauthDataHook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
+ res = hook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
if (!res)
{
diff --git a/src/interfaces/libpq-oauth/oauth-curl.h b/src/interfaces/libpq-oauth/oauth-curl.h
new file mode 100644
index 00000000000..248d0424ad0
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-curl.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-curl.h
+ *
+ * Definitions for OAuth Device Authorization module
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-curl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_CURL_H
+#define OAUTH_CURL_H
+
+#include "libpq-fe.h"
+
+/* Exported async-auth callbacks. */
+extern PGDLLEXPORT PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+extern PGDLLEXPORT void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+#endif /* OAUTH_CURL_H */
diff --git a/src/interfaces/libpq-oauth/oauth-utils.c b/src/interfaces/libpq-oauth/oauth-utils.c
new file mode 100644
index 00000000000..2bdbf904743
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.c
@@ -0,0 +1,202 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.c
+ *
+ * "Glue" helpers providing a copy of some internal APIs from libpq. At
+ * some point in the future, we might be able to deduplicate.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq-oauth/oauth-utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+
+#include "libpq-int.h"
+#include "oauth-utils.h"
+
+static libpq_gettext_func libpq_gettext_impl;
+static conn_errorMessage_func conn_errorMessage;
+
+pgthreadlock_t pg_g_threadlock;
+
+/*-
+ * Initializes libpq-oauth by setting necessary callbacks.
+ *
+ * The current implementation relies on the following private implementation
+ * details of libpq:
+ *
+ * - pg_g_threadlock: protects libcurl initialization if the underlying Curl
+ * installation is not threadsafe
+ *
+ * - libpq_gettext: translates error messages using libpq's message domain
+ *
+ * - conn->errorMessage: holds translated errors for the connection. This is
+ * handled through a translation shim, which avoids either depending on the
+ * offset of the errorMessage in PGconn, or needing to export the variadic
+ * libpq_append_conn_error().
+ */
+void
+libpq_oauth_init(pgthreadlock_t threadlock_impl,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl)
+{
+ pg_g_threadlock = threadlock_impl;
+ libpq_gettext_impl = gettext_impl;
+ conn_errorMessage = errmsg_impl;
+}
+
+/*
+ * Append a formatted string to the error message buffer of the given
+ * connection, after translating it. This is a copy of libpq's internal API.
+ */
+void
+libpq_append_conn_error(PGconn *conn, const char *fmt,...)
+{
+ int save_errno = errno;
+ bool done;
+ va_list args;
+ PQExpBuffer errorMessage = conn_errorMessage(conn);
+
+ Assert(fmt[strlen(fmt) - 1] != '\n');
+
+ if (PQExpBufferBroken(errorMessage))
+ return; /* already failed */
+
+ /* Loop in case we have to retry after enlarging the buffer. */
+ do
+ {
+ errno = save_errno;
+ va_start(args, fmt);
+ done = appendPQExpBufferVA(errorMessage, libpq_gettext(fmt), args);
+ va_end(args);
+ } while (!done);
+
+ appendPQExpBufferChar(errorMessage, '\n');
+}
+
+#ifdef ENABLE_NLS
+
+/*
+ * A shim that defers to the actual libpq_gettext().
+ */
+char *
+libpq_gettext(const char *msgid)
+{
+ if (!libpq_gettext_impl)
+ {
+ /*
+ * Possible if the libpq build doesn't enable NLS. That's a concerning
+ * mismatch, but in this particular case we can handle it. Try to warn
+ * a developer with an assertion, though.
+ */
+ Assert(false);
+
+ /*
+ * Note that callers of libpq_gettext() have to treat the return value
+ * as if it were const, because builds without NLS simply pass through
+ * their argument.
+ */
+ return unconstify(char *, msgid);
+ }
+
+ return libpq_gettext_impl(msgid);
+}
+
+#endif /* ENABLE_NLS */
+
+/*
+ * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
+ */
+bool
+oauth_unsafe_debugging_enabled(void)
+{
+ const char *env = getenv("PGOAUTHDEBUG");
+
+ return (env && strcmp(env, "UNSAFE") == 0);
+}
+
+/*
+ * Duplicate SOCK_ERRNO* definitions from libpq-int.h, for use by
+ * pq_block/reset_sigpipe().
+ */
+#ifdef WIN32
+#define SOCK_ERRNO (WSAGetLastError())
+#define SOCK_ERRNO_SET(e) WSASetLastError(e)
+#else
+#define SOCK_ERRNO errno
+#define SOCK_ERRNO_SET(e) (errno = (e))
+#endif
+
+/*
+ * Block SIGPIPE for this thread. This is a copy of libpq's internal API.
+ */
+int
+pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
+{
+ sigset_t sigpipe_sigset;
+ sigset_t sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ /* Block SIGPIPE and save previous mask for later reset */
+ SOCK_ERRNO_SET(pthread_sigmask(SIG_BLOCK, &sigpipe_sigset, osigset));
+ if (SOCK_ERRNO)
+ return -1;
+
+ /* We can have a pending SIGPIPE only if it was blocked before */
+ if (sigismember(osigset, SIGPIPE))
+ {
+ /* Is there a pending SIGPIPE? */
+ if (sigpending(&sigset) != 0)
+ return -1;
+
+ if (sigismember(&sigset, SIGPIPE))
+ *sigpipe_pending = true;
+ else
+ *sigpipe_pending = false;
+ }
+ else
+ *sigpipe_pending = false;
+
+ return 0;
+}
+
+/*
+ * Discard any pending SIGPIPE and reset the signal mask. This is a copy of
+ * libpq's internal API.
+ */
+void
+pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
+{
+ int save_errno = SOCK_ERRNO;
+ int signo;
+ sigset_t sigset;
+
+ /* Clear SIGPIPE only if none was pending */
+ if (got_epipe && !sigpipe_pending)
+ {
+ if (sigpending(&sigset) == 0 &&
+ sigismember(&sigset, SIGPIPE))
+ {
+ sigset_t sigpipe_sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ sigwait(&sigpipe_sigset, &signo);
+ }
+ }
+
+ /* Restore saved block mask */
+ pthread_sigmask(SIG_SETMASK, osigset, NULL);
+
+ SOCK_ERRNO_SET(save_errno);
+}
diff --git a/src/interfaces/libpq-oauth/oauth-utils.h b/src/interfaces/libpq-oauth/oauth-utils.h
new file mode 100644
index 00000000000..279fc113248
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.h
+ *
+ * Definitions providing missing libpq internal APIs
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_UTILS_H
+#define OAUTH_UTILS_H
+
+#include "libpq-fe.h"
+#include "pqexpbuffer.h"
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/* Initializes libpq-oauth. */
+extern PGDLLEXPORT void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+
+/* Duplicated APIs, copied from libpq. */
+extern void libpq_append_conn_error(PGconn *conn, const char *fmt,...) pg_attribute_printf(2, 3);
+extern bool oauth_unsafe_debugging_enabled(void);
+extern int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
+extern void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe);
+
+#endif /* OAUTH_UTILS_H */
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 90b0b65db6f..d4c20066ce4 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -31,7 +31,6 @@ endif
OBJS = \
$(WIN32RES) \
- fe-auth-oauth.o \
fe-auth-scram.o \
fe-cancel.o \
fe-connect.o \
@@ -64,9 +63,11 @@ OBJS += \
fe-secure-gssapi.o
endif
-ifeq ($(with_libcurl),yes)
-OBJS += fe-auth-oauth-curl.o
-endif
+# The OAuth implementation differs depending on the type of library being built.
+OBJS_STATIC = fe-auth-oauth.o
+
+fe-auth-oauth_shlib.o: override CPPFLAGS_SHLIB += -DUSE_DYNAMIC_OAUTH
+OBJS_SHLIB = fe-auth-oauth_shlib.o
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
@@ -86,7 +87,7 @@ endif
# that are built correctly for use in a shlib.
SHLIB_LINK_INTERNAL = -lpgcommon_shlib -lpgport_shlib
ifneq ($(PORTNAME), win32)
-SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lcurl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
+SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
else
SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi32 -lssl -lsocket -lnsl -lresolv -lintl -lm $(PTHREAD_LIBS), $(LIBS)) $(LDAP_LIBS_FE)
endif
@@ -101,12 +102,26 @@ ifeq ($(with_ssl),openssl)
PKG_CONFIG_REQUIRES_PRIVATE = libssl, libcrypto
endif
+ifeq ($(with_libcurl),yes)
+# libpq.so doesn't link against libcurl, but libpq.a needs libpq-oauth, and
+# libpq-oauth needs libcurl. Put both into *.private.
+PKG_CONFIG_REQUIRES_PRIVATE += libcurl
+%.pc: override SHLIB_LINK_INTERNAL += -lpq-oauth-$(MAJORVERSION)
+endif
+
all: all-lib libpq-refs-stamp
# Shared library stuff
include $(top_srcdir)/src/Makefile.shlib
backend_src = $(top_srcdir)/src/backend
+# Add shlib-/stlib-specific objects.
+$(shlib): override OBJS += $(OBJS_SHLIB)
+$(shlib): $(OBJS_SHLIB)
+
+$(stlib): override OBJS += $(OBJS_STATIC)
+$(stlib): $(OBJS_STATIC)
+
# Check for functions that libpq must not call, currently just exit().
# (Ideally we'd reject abort() too, but there are various scenarios where
# build toolchains insert abort() calls, e.g. to implement assert().)
@@ -115,8 +130,6 @@ backend_src = $(top_srcdir)/src/backend
# which seems to insert references to that even in pure C code. Excluding
# __tsan_func_exit is necessary when using ThreadSanitizer data race detector
# which use this function for instrumentation of function exit.
-# libcurl registers an exit handler in the memory debugging code when running
-# with LeakSanitizer.
# Skip the test when profiling, as gcc may insert exit() calls for that.
# Also skip the test on platforms where libpq infrastructure may be provided
# by statically-linked libraries, as we can't expect them to honor this
@@ -124,7 +137,7 @@ backend_src = $(top_srcdir)/src/backend
libpq-refs-stamp: $(shlib)
ifneq ($(enable_coverage), yes)
ifeq (,$(filter solaris,$(PORTNAME)))
- @if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit -e _atexit | grep exit; then \
+ @if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit | grep exit; then \
echo 'libpq must not be calling any function which invokes exit'; exit 1; \
fi
endif
@@ -138,6 +151,11 @@ fe-misc.o: fe-misc.c $(top_builddir)/src/port/pg_config_paths.h
$(top_builddir)/src/port/pg_config_paths.h:
$(MAKE) -C $(top_builddir)/src/port pg_config_paths.h
+# Use src/common/Makefile's trick for tracking dependencies of shlib-specific
+# objects.
+%_shlib.o: %.c %.o
+ $(CC) $(CFLAGS) $(CFLAGS_SL) $(CPPFLAGS) $(CPPFLAGS_SHLIB) -c $< -o $@
+
install: all installdirs install-lib
$(INSTALL_DATA) $(srcdir)/libpq-fe.h '$(DESTDIR)$(includedir)'
$(INSTALL_DATA) $(srcdir)/libpq-events.h '$(DESTDIR)$(includedir)'
@@ -171,6 +189,6 @@ uninstall: uninstall-lib
clean distclean: clean-lib
$(MAKE) -C test $@
rm -rf tmp_check
- rm -f $(OBJS) pthread.h libpq-refs-stamp
+ rm -f $(OBJS) $(OBJS_SHLIB) $(OBJS_STATIC) pthread.h libpq-refs-stamp
# Might be left over from a Win32 client-only build
rm -f pg_config_paths.h
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index d5143766858..0625cf39e9a 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -210,3 +210,4 @@ PQsetAuthDataHook 207
PQgetAuthDataHook 208
PQdefaultAuthDataHook 209
PQfullProtocolVersion 210
+appendPQExpBufferVA 211
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
index cf1a25e2ccc..5c285adccbd 100644
--- a/src/interfaces/libpq/fe-auth-oauth.c
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -15,6 +15,10 @@
#include "postgres_fe.h"
+#ifdef USE_DYNAMIC_OAUTH
+#include <dlfcn.h>
+#endif
+
#include "common/base64.h"
#include "common/hmac.h"
#include "common/jsonapi.h"
@@ -721,6 +725,143 @@ cleanup_user_oauth_flow(PGconn *conn)
state->async_ctx = NULL;
}
+/*-------------
+ * Builtin Flow
+ *
+ * There are three potential implementations of use_builtin_flow:
+ *
+ * 1) If the OAuth client is disabled at configuration time, return false.
+ * Dependent clients must provide their own flow.
+ * 2) If the OAuth client is enabled and USE_DYNAMIC_OAUTH is defined, dlopen()
+ * the libpq-oauth plugin and use its implementation.
+ * 3) Otherwise, use flow callbacks that are statically linked into the
+ * executable.
+ */
+
+#if !defined(USE_LIBCURL)
+
+/*
+ * This configuration doesn't support the builtin flow.
+ */
+
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ return false;
+}
+
+#elif defined(USE_DYNAMIC_OAUTH)
+
+/*
+ * Use the builtin flow in the libpq-oauth plugin, which is loaded at runtime.
+ */
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/*
+ * This shim is injected into libpq-oauth so that it doesn't depend on the
+ * offset of conn->errorMessage.
+ *
+ * TODO: look into exporting libpq_append_conn_error or a comparable API from
+ * libpq, instead.
+ */
+static PQExpBuffer
+conn_errorMessage(PGconn *conn)
+{
+ return &conn->errorMessage;
+}
+
+/*
+ * Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
+ * callbacks into the connection's async auth handlers.
+ *
+ * Failure to load here results in a relatively quiet connection error, to
+ * handle the use case where the build supports loading a flow but a user does
+ * not want to install it. Troubleshooting of linker/loader failures can be done
+ * via PGOAUTHDEBUG.
+ */
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ void (*init) (pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+ PostgresPollingStatusType (*flow) (PGconn *conn);
+ void (*cleanup) (PGconn *conn);
+
+ state->builtin_flow = dlopen("libpq-oauth-" PG_MAJORVERSION DLSUFFIX,
+ RTLD_NOW | RTLD_LOCAL);
+ if (!state->builtin_flow)
+ {
+ /*
+ * For end users, this probably isn't an error condition, it just
+ * means the flow isn't installed. Developers and package maintainers
+ * may want to debug this via the PGOAUTHDEBUG envvar, though.
+ *
+ * Note that POSIX dlerror() isn't guaranteed to be threadsafe.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
+
+ return false;
+ }
+
+ if ((init = dlsym(state->builtin_flow, "libpq_oauth_init")) == NULL
+ || (flow = dlsym(state->builtin_flow, "pg_fe_run_oauth_flow")) == NULL
+ || (cleanup = dlsym(state->builtin_flow, "pg_fe_cleanup_oauth_flow")) == NULL)
+ {
+ /*
+ * This is more of an error condition than the one above, but due to
+ * the dlerror() threadsafety issue, lock it behind PGOAUTHDEBUG too.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
+
+ dlclose(state->builtin_flow);
+ return false;
+ }
+
+ /*
+ * Inject necessary function pointers into the module.
+ */
+ init(pg_g_threadlock,
+#ifdef ENABLE_NLS
+ libpq_gettext,
+#else
+ NULL,
+#endif
+ conn_errorMessage);
+
+ /* Set our asynchronous callbacks. */
+ conn->async_auth = flow;
+ conn->cleanup_async_auth = cleanup;
+
+ return true;
+}
+
+#else
+
+/*
+ * Use the builtin flow in libpq-oauth.a (see libpq-oauth/oauth-curl.h).
+ */
+
+extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ /* Set our asynchronous callbacks. */
+ conn->async_auth = pg_fe_run_oauth_flow;
+ conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
+
+ return true;
+}
+
+#endif /* USE_LIBCURL */
+
+
/*
* Chooses an OAuth client flow for the connection, which will retrieve a Bearer
* token for presentation to the server.
@@ -792,18 +933,10 @@ setup_token_request(PGconn *conn, fe_oauth_state *state)
libpq_append_conn_error(conn, "user-defined OAuth flow failed");
goto fail;
}
- else
+ else if (!use_builtin_flow(conn, state))
{
-#if USE_LIBCURL
- /* Hand off to our built-in OAuth flow. */
- conn->async_auth = pg_fe_run_oauth_flow;
- conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
-
-#else
libpq_append_conn_error(conn, "no custom OAuth flows are available, and libpq was not built with libcurl support");
goto fail;
-
-#endif
}
return true;
diff --git a/src/interfaces/libpq/fe-auth-oauth.h b/src/interfaces/libpq/fe-auth-oauth.h
index 3f1a7503a01..687e664475f 100644
--- a/src/interfaces/libpq/fe-auth-oauth.h
+++ b/src/interfaces/libpq/fe-auth-oauth.h
@@ -33,12 +33,13 @@ typedef struct
PGconn *conn;
void *async_ctx;
+
+ void *builtin_flow;
} fe_oauth_state;
-extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
-extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
extern void pqClearOAuthToken(PGconn *conn);
extern bool oauth_unsafe_debugging_enabled(void);
+extern bool use_builtin_flow(PGconn *conn, fe_oauth_state *state);
/* Mechanisms in fe-auth-oauth.c */
extern const pg_fe_sasl_mech pg_oauth_mech;
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index 292fecf3320..63e48d9fcfb 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -18,6 +18,7 @@ libpq_sources = files(
'pqexpbuffer.c',
)
libpq_so_sources = [] # for shared lib, in addition to the above
+libpq_st_sources = [] # for static lib, in addition to the above
if host_system == 'windows'
libpq_sources += files('pthread-win32.c', 'win32.c')
@@ -38,10 +39,6 @@ if gssapi.found()
)
endif
-if libcurl.found()
- libpq_sources += files('fe-auth-oauth-curl.c')
-endif
-
export_file = custom_target('libpq.exports',
kwargs: gen_export_kwargs,
)
@@ -50,6 +47,9 @@ export_file = custom_target('libpq.exports',
libpq_inc = include_directories('.', '../../port')
libpq_c_args = ['-DSO_MAJOR_VERSION=5']
+# The OAuth implementation differs depending on the type of library being built.
+libpq_so_c_args = ['-DUSE_DYNAMIC_OAUTH']
+
# Not using both_libraries() here as
# 1) resource files should only be in the shared library
# 2) we want the .pc file to include a dependency to {pgport,common}_static for
@@ -59,7 +59,7 @@ libpq_c_args = ['-DSO_MAJOR_VERSION=5']
# more complexity than its worth (reusing object files requires also linking
# to the library on windows or breaks precompiled headers).
libpq_st = static_library('libpq',
- libpq_sources,
+ libpq_sources + libpq_st_sources,
include_directories: [libpq_inc],
c_args: libpq_c_args,
c_pch: pch_postgres_fe_h,
@@ -70,7 +70,7 @@ libpq_st = static_library('libpq',
libpq_so = shared_library('libpq',
libpq_sources + libpq_so_sources,
include_directories: [libpq_inc, postgres_inc],
- c_args: libpq_c_args,
+ c_args: libpq_c_args + libpq_so_c_args,
c_pch: pch_postgres_fe_h,
version: '5.' + pg_version_major.to_string(),
soversion: host_system != 'windows' ? '5' : '',
@@ -86,12 +86,26 @@ libpq = declare_dependency(
include_directories: [include_directories('.')]
)
+private_deps = [
+ frontend_stlib_code,
+ libpq_deps,
+]
+
+if oauth_flow_supported
+ # libpq.so doesn't link against libcurl, but libpq.a needs libpq-oauth, and
+ # libpq-oauth needs libcurl. Put both into *.private.
+ private_deps += [
+ libpq_oauth_deps,
+ '-lpq-oauth-@0@'.format(pg_version_major),
+ ]
+endif
+
pkgconfig.generate(
name: 'libpq',
description: 'PostgreSQL libpq library',
url: pg_url,
libraries: libpq,
- libraries_private: [frontend_stlib_code, libpq_deps],
+ libraries_private: private_deps,
)
install_headers(
diff --git a/src/interfaces/libpq/nls.mk b/src/interfaces/libpq/nls.mk
index ae761265852..b87df277d93 100644
--- a/src/interfaces/libpq/nls.mk
+++ b/src/interfaces/libpq/nls.mk
@@ -13,15 +13,21 @@ GETTEXT_FILES = fe-auth.c \
fe-secure-common.c \
fe-secure-gssapi.c \
fe-secure-openssl.c \
- win32.c
-GETTEXT_TRIGGERS = libpq_append_conn_error:2 \
+ win32.c \
+ ../libpq-oauth/oauth-curl.c \
+ ../libpq-oauth/oauth-utils.c
+GETTEXT_TRIGGERS = actx_error:2 \
+ libpq_append_conn_error:2 \
libpq_append_error:2 \
libpq_gettext \
libpq_ngettext:1,2 \
+ oauth_parse_set_error:2 \
pqInternalNotice:2
-GETTEXT_FLAGS = libpq_append_conn_error:2:c-format \
+GETTEXT_FLAGS = actx_error:2:c-format \
+ libpq_append_conn_error:2:c-format \
libpq_append_error:2:c-format \
libpq_gettext:1:pass-c-format \
libpq_ngettext:1:pass-c-format \
libpq_ngettext:2:pass-c-format \
+ oauth_parse_set_error:2:c-format \
pqInternalNotice:2:c-format
diff --git a/src/makefiles/meson.build b/src/makefiles/meson.build
index 46d8da070e8..f2ba5b38124 100644
--- a/src/makefiles/meson.build
+++ b/src/makefiles/meson.build
@@ -201,6 +201,8 @@ pgxs_empty = [
'ICU_LIBS',
'LIBURING_CFLAGS', 'LIBURING_LIBS',
+
+ 'LIBCURL_CPPFLAGS', 'LIBCURL_LDFLAGS', 'LIBCURL_LDLIBS',
]
if host_system == 'windows' and cc.get_argument_syntax() != 'msvc'
--
2.34.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-11 00:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-14 16:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-14 18:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Wolfgang Walther <[email protected]>
2025-04-14 19:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-14 20:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 00:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-21 16:57 ` Jacob Champion <[email protected]>
2025-05-01 20:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-21 16:57 UTC (permalink / raw)
To: Ivan Kush <[email protected]>; +Cc: Wolfgang Walther <[email protected]>; pgsql-hackers
On Sun, Apr 20, 2025 at 10:12 AM Ivan Kush <[email protected]> wrote:
> I'm testing OAuth Device Flow implementation on Google. Met several
> problems.
Hi Ivan, thank you for testing and reporting! Unfortunately, yeah,
Google is a known problem [1]. They've taken several liberties with
the spec, as you point out.
We have some options for dealing with them, since their documentation
instructs clients to hardcode their API entry points instead of using
discovery. (That makes it easy for us to figure out when we're talking
to Google, and potentially switch to a quirks mode.)
But! Before we do that: How do you intend to authorize tokens issued
by Google? Last I checked, they still had no way to register an
application-specific scope, making it very dangerous IMO to use a
public flow [2]. Do you have an architecture where this usage is safe,
and/or have they added custom scopes? (I deprioritized handling the
nonstandard behavior when I couldn't prove to myself that it was
possible to use the Google version of Device Authorization safely, but
I'm happy to jump back into that if we have a good use case.)
> 1) In Device Authorization Request Google returns 428 code on pending
> https://developers.google.com/identity/protocols/oauth2/limited-input-device#authorization-pending
Right. I believe there were other nonstandard errors in other corner
cases, too. :(
> I suggest to add a GUC in postgresql.conf that contains additional
> non-standard error codes for a specific service.
> oauth_add_error_codes = [
> {
> issuer: google
> add_err_codes: [428],
> },
> {
> issuer: someservice
> add_err_code: [403],
> }
> ]
> So Google can contain 400,401,428
The server config doesn't help us much, since this is a client-side
feature. Any "global" configuration is probably going to be done
through environment variables or a service file [3].
> Additionally write parsing of such json-like config-values. Will be cool
> to create serializer, that matches struct to such json-like GUC.
I'm not too excited about a separate configuration DSL. I'm guessing
most end users, if they really want Google as their Device
Authorization provider, would rather have us switch over to "Google
mode" once we notice the magic Google endpoint is in use.
> 2) Google requires client_secret only in the Device Access Token Request
> (Section 3.3 RFC-8628).
> ...
> But Postgres sends client_secret in both request, also in Device
> Authorization Request.
Yes. See 3.1 (Device Authorization Request):
The client authentication requirements of Section 3.2.1 of [RFC6749]
apply to requests on this endpoint, which means that confidential
clients (those that have established client credentials) authenticate
in the same manner as when making requests to the token endpoint, and
public clients provide the "client_id" parameter to identify
themselves.
> I suggest to remove send secret on Device Authorization Request.
This breaks Okta, at minimum. We can't do it across the board. (As for
Azure, I haven't figured out how to configure it to *require* a
confidential client secret for the device flow -- which makes a
certain amount of sense since the flow is public -- but its v2
endpoint doesn't mind being *sent* a secret.)
> 3) Additionally if secret exists PG sends it only using Basic Auth. But
> RFC contain only MAY word about Basic Auth. Section 2.3.1 RFC 6749,
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-11 00:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-14 16:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-14 18:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Wolfgang Walther <[email protected]>
2025-04-14 19:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-14 20:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 00:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 16:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-05-01 20:41 ` Jacob Champion <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2025-05-01 20:41 UTC (permalink / raw)
To: Ivan Kush <[email protected]>; +Cc: pgsql-hackers
On Mon, Apr 21, 2025 at 9:57 AM Jacob Champion
<[email protected]> wrote:
> So to recap: I'm happy to add a Google compatibility mode, but I'd
> like to gather some evidence that their device flow can actually
> authorize tokens for third parties safely, before we commit to that.
> Thoughts?
Hi Ivan, I know the thread has been deep in discussion around the
module split, but I was wondering if you'd had any thoughts on the
Google safety problem?
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-15 12:31 ` Peter Eisentraut <[email protected]>
2025-04-15 15:34 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 19:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 3 replies; 194+ messages in thread
From: Peter Eisentraut @ 2025-04-15 12:31 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Jelte Fennema-Nio <[email protected]>
On 10.04.25 01:08, Jacob Champion wrote:
> Christoph noted that this was also confusing from the packaging side,
> earlier, and Daniel proposed -Doauth-client/--with-oauth-client as the
> feature switch name instead. Any objections? Unfortunately it would
> mean a buildfarm email is in order, so we should get it locked in.
We had that discussion early in the development, and I still think it's
not the right choice.
The general idea, at least on the Autoconf side, is that --with-FOO
means enable all the features that require library FOO. For example,
--with-ldap enables all the LDAP-related features, including
authentication support in libpq, authentication support in the server,
and service lookup in libpq. --with-[open]ssl enables all the features
that use OpenSSL, including SSL support in the client and server but
also encryption support in pgcrypto.
The naming system you propose has problems:
First, what if we add another kind of "oauth-client" that doesn't use
libcurl, how would you extend the set of options?
Second, what if we add some kind of oauth plugin for the server that
uses libcurl, how would you extend the set of options?
If you used that system for options in the ldap or openssl cases, you'd
end up with maybe six options (and packagers would forget to turn on
half of them). But worse, what you are hiding is the information what
dependencies you are pulling in, which is the actual reason for the
options. (If there was no external dependency, there would be no option
at all.)
This seems unnecessarily complicated and inconsistent. Once you have
made the choice of taking the libcurl dependency, why not build
everything that requires it?
(Nitpick: If you go with this kind of option, it should be --enable-XXX
on the Autoconf side.)
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
@ 2025-04-15 15:34 ` Christoph Berg <[email protected]>
2025-04-15 18:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2 siblings, 1 reply; 194+ messages in thread
From: Christoph Berg @ 2025-04-15 15:34 UTC (permalink / raw)
To: Peter Eisentraut <[email protected]>; +Cc: Jacob Champion <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Jelte Fennema-Nio <[email protected]>
Re: Peter Eisentraut
> But worse, what you are hiding is the information what dependencies
> you are pulling in, which is the actual reason for the options. (If there
> was no external dependency, there would be no option at all.)
>
> This seems unnecessarily complicated and inconsistent. Once you have made
> the choice of taking the libcurl dependency, why not build everything that
> requires it?
I agree with this reasoning and retract my suggestion to rename the option.
Thanks for the explanation,
Christoph
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 15:34 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
@ 2025-04-15 18:02 ` Jacob Champion <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-15 18:02 UTC (permalink / raw)
To: Christoph Berg <[email protected]>; Peter Eisentraut <[email protected]>; Jacob Champion <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Jelte Fennema-Nio <[email protected]>
On Tue, Apr 15, 2025 at 8:34 AM Christoph Berg <[email protected]> wrote:
> I agree with this reasoning and retract my suggestion to rename the option.
(Thank you for chiming in; having the packager feedback has been
extremely helpful.)
While I have you, may I ask whether you're okay (from the packager
perspective) with the current division of dynamic and static
behaviors?
Dynamic: --with-libcurl builds a runtime-loadable module, and if you
don't install it, OAuth isn't supported (i.e. it's optional)
Static: --with-libcurl builds an additional linkable staticlib, which
you must link into your application (i.e. not optional)
I want to make absolutely sure the existing packager requests are not
conflicting. :D
Thanks,
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
@ 2025-04-15 17:53 ` Jacob Champion <[email protected]>
2025-04-15 18:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2 siblings, 2 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-15 17:53 UTC (permalink / raw)
To: Peter Eisentraut <[email protected]>; +Cc: Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Jelte Fennema-Nio <[email protected]>
On Tue, Apr 15, 2025 at 5:31 AM Peter Eisentraut <[email protected]> wrote:
> On 10.04.25 01:08, Jacob Champion wrote:
> > Christoph noted that this was also confusing from the packaging side,
> > earlier,
Since Christoph has withdrawn the request, I will drop -0002.
However, I'll go ahead and put some of my opinions down on paper here:
> The general idea, at least on the Autoconf side, is that --with-FOO
> means enable all the features that require library FOO.
I don't think this is particularly user-friendly if it's not obvious
what feature is enabled by FOO.
LDAP? PAM? Sure. SSL? Eh, I think the pgcrypto coupling is a little
strange -- that's not implied by "SSL" at all! -- but it's not
problematic enough to complain loudly. --with-gssapi selects... some
dependency... which may or may not come from a particular library.
--with-bsd-auth doesn't add any library dependencies at all, instead
depending on the kernel, but it makes sense.
But there's no connection between "libcurl" and "OAuth Device
Authorization flow" in anyone's mind except the people who have worked
on that feature.
If the argument is that we'd need to switch to --enable-oauth-client
rather than --with-oauth-client, that works for me. But I don't quite
understand the desire to stick to the existing configuration
methodology for something that's very different from an end-user
perspective.
> The naming system you propose has problems:
>
> First, what if we add another kind of "oauth-client" that doesn't use
> libcurl, how would you extend the set of options?
With an extension to the values that you can provide to
--with-oauth-client, similarly to what was originally proposed for
--with-ssl.
> Second, what if we add some kind of oauth plugin for the server that
> uses libcurl, how would you extend the set of options?
With a new option.
But let me turn this around, because we currently have the opposite
problem: if someone comes in and adds a completely new feature
depending on libcurl, and you want OAuth but you do not want that new
feature -- or vice-versa -- what do you do? In other words, what if
your concern is not with libcurl, but with the feature itself?
> But worse, what you are hiding is the information what
> dependencies you are pulling in, which is the actual reason for the
> options. (If there was no external dependency, there would be no option
> at all.)
I'm not sure I agree, either practically or philosophically. I like to
see the build dependencies, definitely, but I also like to see the
features. (Meson will make both things visible separately, for that
matter.)
> This seems unnecessarily complicated and inconsistent. Once you have
> made the choice of taking the libcurl dependency, why not build
> everything that requires it?
Simply because the end user or packager might not want to.
In any case -- I won't die on this particular hill, and I'm happy to
continue forward with 0001 alone.
Thanks!
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-15 18:57 ` Christoph Berg <[email protected]>
2025-04-15 19:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Christoph Berg @ 2025-04-15 18:57 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Jelte Fennema-Nio <[email protected]>
Re: Jacob Champion
> But there's no connection between "libcurl" and "OAuth Device
> Authorization flow" in anyone's mind except the people who have worked
> on that feature.
Fwiw that was exactly the reason I originally voiced the idea to
rename.
> But let me turn this around, because we currently have the opposite
> problem: if someone comes in and adds a completely new feature
> depending on libcurl, and you want OAuth but you do not want that new
> feature -- or vice-versa -- what do you do? In other words, what if
> your concern is not with libcurl, but with the feature itself?
What made me reconsider was Peter saying that what defines the blast
radius of some feature is usually the extra dependency pulled in. If
you don't like tracking OpenSSL problems, build without it. If you
don't like libcurl, build without it. That's the "we are going to be
hated by security scanner people" argument that brought up this
sub-thread.
Now if the feature itself were a problem, that might change how
configuration should be working. Is "libpq can now initiate oauth
requests" something people would like to be able to control?
Re: Jacob Champion
> Dynamic: --with-libcurl builds a runtime-loadable module, and if you
> don't install it, OAuth isn't supported (i.e. it's optional)
Ok.
> Static: --with-libcurl builds an additional linkable staticlib, which
> you must link into your application (i.e. not optional)
Debian does not care really about static libs. We are currently
shipping libpq.a, but if it breaks in any funny way, we might as well
remove it.
Christoph
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 18:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
@ 2025-04-15 19:44 ` Jacob Champion <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-15 19:44 UTC (permalink / raw)
To: Christoph Berg <[email protected]>; Jacob Champion <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Jelte Fennema-Nio <[email protected]>
On Tue, Apr 15, 2025 at 11:57 AM Christoph Berg <[email protected]> wrote:
> What made me reconsider was Peter saying that what defines the blast
> radius of some feature is usually the extra dependency pulled in. If
> you don't like tracking OpenSSL problems, build without it. If you
> don't like libcurl, build without it. That's the "we are going to be
> hated by security scanner people" argument that brought up this
> sub-thread.
>
> Now if the feature itself were a problem, that might change how
> configuration should be working. Is "libpq can now initiate oauth
> requests" something people would like to be able to control?
Well... I'd sure like to live in a world where users thought about the
implications and risks of what they're using and why, rather than
farming a decision out to a static analysis tool. ("And as long as I'm
dreaming, I'd like a pony.")
But end users already control the initiation of OAuth requests (it's
opt-in via the connection string), so that's not really the goal.
> Debian does not care really about static libs. We are currently
> shipping libpq.a, but if it breaks in any funny way, we might as well
> remove it.
Awesome. I think we have a consensus.
Thanks!
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-18 00:47 ` Jacob Champion <[email protected]>
2025-04-18 17:01 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 2 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-18 00:47 UTC (permalink / raw)
To: Jelte Fennema-Nio <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>
On Tue, Apr 15, 2025 at 2:38 PM Jelte Fennema-Nio <[email protected]> wrote:
> libpq_append_conn_error(conn, "no custom OAuth flows are available,
> and libpq-oauth could not be loaded library could not be loaded. Try
> installing the libpq-oauth package from the same source that you
> installed libpq from");
Thanks! I think that's a little too prescriptive for packagers,
personally, but I agree that the current message isn't correct
anymore. I've gone with "no custom OAuth flows are available, and the
builtin flow is not installed". (I suppose packagers could patch in a
platform-specific message if they really wanted?)
--
Other changes in v7:
- The option name remains --with-libcurl.
- Daniel and I have tweaked the documentation, and a draft commit message is up
- Removed the ENABLE_NLS-mismatch assertion in oauth-utils.c; we don't
need to care anymore
- Added an initialization mutex
I was feeling paranoid about injecting dependency pointers
concurrently to their use in another thread. They're _supposed_ to be
constant... but I have no doubt that someone somewhere knows of a
platform/compiler/linker combo where that blows up anyway.
Initialization is now run once, under pthread_mutex protection.
- Fixed module load on macOS
The green CI was masking a bug with its use of DYLD_LIBRARY_PATH: we
don't make use of RPATH on macOS, so after installing libpq, it lost
the ability to find libpq-oauth. (A stale installation due to SIP
weirdness was masking this on my local machine; sorry for not catching
it before.)
I have swapped to using an absolute path on Mac only, because unlike
LD_LIBRARY_PATH on *nix, DYLD_LIBRARY_PATH can still override absolute
paths in dlopen()! Whee. I could use a sanity check from a native Mac
developer, but I believe this mirrors the expected behavior for a
"typical" runtime dependency: libraries point directly to the things
they depend on.
With those, I have no more TODOs and I believe this is ready for a
final review round.
Thanks,
--Jacob
Attachments:
[application/octet-stream] v7-0001-oauth-Move-the-builtin-flow-into-a-separate-modul.patch (53.7K, ../../CAOYmi+=j9nLQFjQ8z0vyQmuhNMwsFbzvne_2S2pTbBGir4q6EQ@mail.gmail.com/2-v7-0001-oauth-Move-the-builtin-flow-into-a-separate-modul.patch)
download | inline diff:
From 942ad5391e2acbb143ffcfec3d5bf8023d4a17ad Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Wed, 26 Mar 2025 10:55:28 -0700
Subject: [PATCH v7] oauth: Move the builtin flow into a separate module
The additional packaging footprint of the OAuth Curl dependency, as well
as the existence of libcurl in the address space even if OAuth isn't
ever used by a client, has raised some concerns. Split off this
dependency into a separate loadable module called libpq-oauth.
When configured using --with-libcurl, libpq.so searches for this new
module via dlopen(). End users may choose not to install the libpq-oauth
module, in which case the default flow is disabled.
For static applications using libpq.a, the libpq-oauth staticlib is a
mandatory link-time dependency for --with-libcurl builds. libpq.pc has
been updated accordingly.
The default flow relies on some libpq internals. Some of these can be
safely duplicated (such as the SIGPIPE handlers), but others need to be
shared between libpq and libpq-oauth for thread-safety. To avoid exporting
these internals to all libpq clients forever, these dependencies are
instead injected from the libpq side via an initialization function.
This also lets libpq communicate the offset of conn->errorMessage to
libpq-oauth, so that we can function without crashing if the module on
the search path came from a different build of Postgres.
This ABI is considered "private". The module has no SONAME or version
symlinks, and it's named libpq-oauth-<major>.so to avoid mixing and
matching across major Postgres versions. (Future improvements may
promote this "OAuth flow plugin" to a first-class concept, at which
point we would need a public API to replace this anyway.)
Additionally, NLS support for error messages in b3f0be788a was
incomplete, because the new error macros weren't being scanned by
xgettext. Fix that now.
Per request from Tom Lane and Bruce Momjian. Based on an initial patch
by Daniel Gustafsson, who also contributed docs changes. The "bare"
dlopen() concept came from Thomas Munro. Many many people reviewed the
design and implementation; thank you!
Co-authored-by: Daniel Gustafsson <[email protected]>
Reviewed-by: Andres Freund <[email protected]>
Reviewed-by: Christoph Berg <[email protected]>
Reviewed-by: Jelte Fennema-Nio <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Wolfgang Walther <[email protected]>
Discussion: https://postgr.es/m/641687.1742360249%40sss.pgh.pa.us
---
config/programs.m4 | 17 +-
configure | 50 ++++-
configure.ac | 26 ++-
doc/src/sgml/installation.sgml | 8 +
doc/src/sgml/libpq.sgml | 30 ++-
meson.build | 32 ++-
src/Makefile.global.in | 3 +
src/interfaces/Makefile | 12 ++
src/interfaces/libpq-oauth/Makefile | 65 ++++++
src/interfaces/libpq-oauth/README | 43 ++++
src/interfaces/libpq-oauth/exports.txt | 4 +
src/interfaces/libpq-oauth/meson.build | 43 ++++
.../oauth-curl.c} | 60 +++---
src/interfaces/libpq-oauth/oauth-curl.h | 24 +++
src/interfaces/libpq-oauth/oauth-utils.c | 198 ++++++++++++++++++
src/interfaces/libpq-oauth/oauth-utils.h | 35 ++++
src/interfaces/libpq/Makefile | 36 +++-
src/interfaces/libpq/exports.txt | 1 +
src/interfaces/libpq/fe-auth-oauth.c | 197 ++++++++++++++++-
src/interfaces/libpq/fe-auth-oauth.h | 5 +-
src/interfaces/libpq/meson.build | 25 ++-
src/interfaces/libpq/nls.mk | 12 +-
src/makefiles/meson.build | 2 +
src/test/modules/oauth_validator/meson.build | 2 +-
.../modules/oauth_validator/t/002_client.pl | 2 +-
25 files changed, 834 insertions(+), 98 deletions(-)
create mode 100644 src/interfaces/libpq-oauth/Makefile
create mode 100644 src/interfaces/libpq-oauth/README
create mode 100644 src/interfaces/libpq-oauth/exports.txt
create mode 100644 src/interfaces/libpq-oauth/meson.build
rename src/interfaces/{libpq/fe-auth-oauth-curl.c => libpq-oauth/oauth-curl.c} (98%)
create mode 100644 src/interfaces/libpq-oauth/oauth-curl.h
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.c
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.h
diff --git a/config/programs.m4 b/config/programs.m4
index 0a07feb37cc..0ad1e58b48d 100644
--- a/config/programs.m4
+++ b/config/programs.m4
@@ -286,9 +286,20 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
[
AC_CHECK_HEADER(curl/curl.h, [],
[AC_MSG_ERROR([header file <curl/curl.h> is required for --with-libcurl])])
- AC_CHECK_LIB(curl, curl_multi_init, [],
+ AC_CHECK_LIB(curl, curl_multi_init, [
+ AC_DEFINE([HAVE_LIBCURL], [1], [Define to 1 if you have the `curl' library (-lcurl).])
+ AC_SUBST(LIBCURL_LDLIBS, -lcurl)
+ ],
[AC_MSG_ERROR([library 'curl' does not provide curl_multi_init])])
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+ LIBS="$LIBCURL_LDLIBS $LIBS"
+
# Check to see whether the current platform supports threadsafe Curl
# initialization.
AC_CACHE_CHECK([for curl_global_init thread safety], [pgac_cv__libcurl_threadsafe_init],
@@ -338,4 +349,8 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
*** lookups. Rebuild libcurl with the AsynchDNS feature enabled in order
*** to use it with libpq.])
fi
+
+ CPPFLAGS=$pgac_save_CPPFLAGS
+ LDFLAGS=$pgac_save_LDFLAGS
+ LIBS=$pgac_save_LIBS
])# PGAC_CHECK_LIBCURL
diff --git a/configure b/configure
index 0936010718d..a4c4bcb40ea 100755
--- a/configure
+++ b/configure
@@ -655,6 +655,7 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+LIBCURL_LDLIBS
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -711,6 +712,8 @@ with_libxml
LIBNUMA_LIBS
LIBNUMA_CFLAGS
with_libnuma
+LIBCURL_LDFLAGS
+LIBCURL_CPPFLAGS
LIBCURL_LIBS
LIBCURL_CFLAGS
with_libcurl
@@ -9053,19 +9056,27 @@ $as_echo "yes" >&6; }
fi
- # We only care about -I, -D, and -L switches;
- # note that -lcurl will be added by PGAC_CHECK_LIBCURL below.
+ # Curl's flags are kept separate from the standard CPPFLAGS/LDFLAGS. We use
+ # them only for libpq-oauth.
+ LIBCURL_CPPFLAGS=
+ LIBCURL_LDFLAGS=
+
+ # We only care about -I, -D, and -L switches. Note that -lcurl will be added
+ # to LIBCURL_LDLIBS by PGAC_CHECK_LIBCURL, below.
for pgac_option in $LIBCURL_CFLAGS; do
case $pgac_option in
- -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+ -I*|-D*) LIBCURL_CPPFLAGS="$LIBCURL_CPPFLAGS $pgac_option";;
esac
done
for pgac_option in $LIBCURL_LIBS; do
case $pgac_option in
- -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+ -L*) LIBCURL_LDFLAGS="$LIBCURL_LDFLAGS $pgac_option";;
esac
done
+
+
+
# OAuth requires python for testing
if test "$with_python" != yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: *** OAuth support tests require --with-python to run" >&5
@@ -12704,9 +12715,6 @@ fi
fi
-# XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
-# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
-# dependency on that platform?
if test "$with_libcurl" = yes ; then
ac_fn_c_check_header_mongrel "$LINENO" "curl/curl.h" "ac_cv_header_curl_curl_h" "$ac_includes_default"
@@ -12754,17 +12762,26 @@ fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_curl_curl_multi_init" >&5
$as_echo "$ac_cv_lib_curl_curl_multi_init" >&6; }
if test "x$ac_cv_lib_curl_curl_multi_init" = xyes; then :
- cat >>confdefs.h <<_ACEOF
-#define HAVE_LIBCURL 1
-_ACEOF
- LIBS="-lcurl $LIBS"
+
+$as_echo "#define HAVE_LIBCURL 1" >>confdefs.h
+
+ LIBCURL_LDLIBS=-lcurl
+
else
as_fn_error $? "library 'curl' does not provide curl_multi_init" "$LINENO" 5
fi
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+ LIBS="$LIBCURL_LDLIBS $LIBS"
+
# Check to see whether the current platform supports threadsafe Curl
# initialization.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_global_init thread safety" >&5
@@ -12868,6 +12885,10 @@ $as_echo "$pgac_cv__libcurl_async_dns" >&6; }
*** to use it with libpq." "$LINENO" 5
fi
+ CPPFLAGS=$pgac_save_CPPFLAGS
+ LDFLAGS=$pgac_save_LDFLAGS
+ LIBS=$pgac_save_LIBS
+
fi
if test "$with_gssapi" = yes ; then
@@ -14516,6 +14537,13 @@ done
fi
+if test "$with_libcurl" = yes ; then
+ # Error out early if this platform can't support libpq-oauth.
+ if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
+ as_fn_error $? "client OAuth is not supported on this platform" "$LINENO" 5
+ fi
+fi
+
##
## Types, structures, compiler characteristics
##
diff --git a/configure.ac b/configure.ac
index 2a78cddd825..5d90bf0e979 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1033,19 +1033,27 @@ if test "$with_libcurl" = yes ; then
# to explicitly set TLS 1.3 ciphersuites).
PKG_CHECK_MODULES(LIBCURL, [libcurl >= 7.61.0])
- # We only care about -I, -D, and -L switches;
- # note that -lcurl will be added by PGAC_CHECK_LIBCURL below.
+ # Curl's flags are kept separate from the standard CPPFLAGS/LDFLAGS. We use
+ # them only for libpq-oauth.
+ LIBCURL_CPPFLAGS=
+ LIBCURL_LDFLAGS=
+
+ # We only care about -I, -D, and -L switches. Note that -lcurl will be added
+ # to LIBCURL_LDLIBS by PGAC_CHECK_LIBCURL, below.
for pgac_option in $LIBCURL_CFLAGS; do
case $pgac_option in
- -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+ -I*|-D*) LIBCURL_CPPFLAGS="$LIBCURL_CPPFLAGS $pgac_option";;
esac
done
for pgac_option in $LIBCURL_LIBS; do
case $pgac_option in
- -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+ -L*) LIBCURL_LDFLAGS="$LIBCURL_LDFLAGS $pgac_option";;
esac
done
+ AC_SUBST(LIBCURL_CPPFLAGS)
+ AC_SUBST(LIBCURL_LDFLAGS)
+
# OAuth requires python for testing
if test "$with_python" != yes; then
AC_MSG_WARN([*** OAuth support tests require --with-python to run])
@@ -1354,9 +1362,6 @@ failure. It is possible the compiler isn't looking in the proper directory.
Use --without-zlib to disable zlib support.])])
fi
-# XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
-# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
-# dependency on that platform?
if test "$with_libcurl" = yes ; then
PGAC_CHECK_LIBCURL
fi
@@ -1654,6 +1659,13 @@ if test "$PORTNAME" = "win32" ; then
AC_CHECK_HEADERS(crtdefs.h)
fi
+if test "$with_libcurl" = yes ; then
+ # Error out early if this platform can't support libpq-oauth.
+ if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
+ AC_MSG_ERROR([client OAuth is not supported on this platform])
+ fi
+fi
+
##
## Types, structures, compiler characteristics
##
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 077bcc20759..d928b103d22 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -313,6 +313,14 @@
</para>
</listitem>
+ <listitem>
+ <para>
+ You need <productname>Curl</productname> to build an optional module
+ which implements the <link linkend="libpq-oauth">OAuth Device
+ Authorization flow</link> for client applications.
+ </para>
+ </listitem>
+
<listitem>
<para>
You need <productname>LZ4</productname>, if you want to support
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 37102c235b0..ae536b2da07 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -10226,15 +10226,20 @@ void PQinitSSL(int do_ssl);
<title>OAuth Support</title>
<para>
- libpq implements support for the OAuth v2 Device Authorization client flow,
+ <application>libpq</application> implements support for the OAuth v2 Device Authorization client flow,
documented in
<ulink url="https://datatracker.ietf.org/doc/html/rfc8628">RFC 8628</ulink>,
- which it will attempt to use by default if the server
+ as an optional module. See the <link linkend="configure-option-with-libcurl">
+ installation documentation</link> for information on how to enable support
+ for Device Authorization as a builtin flow.
+ </para>
+ <para>
+ When support is enabled and the optional module installed, <application>libpq</application>
+ will use the builtin flow by default if the server
<link linkend="auth-oauth">requests a bearer token</link> during
authentication. This flow can be utilized even if the system running the
client application does not have a usable web browser, for example when
- running a client via <application>SSH</application>. Client applications may implement their own flows
- instead; see <xref linkend="libpq-oauth-authdata-hooks"/>.
+ running a client via <acronym>SSH</acronym>.
</para>
<para>
The builtin flow will, by default, print a URL to visit and a user code to
@@ -10251,6 +10256,11 @@ Visit https://example.com/device and enter the code: ABCD-EFGH
they match expectations, before continuing. Permissions should not be given
to untrusted third parties.
</para>
+ <para>
+ Client applications may implement their own flows to customize interaction
+ and integration with applications. See <xref linkend="libpq-oauth-authdata-hooks"/>
+ for more information on how add a custom flow to <application>libpq</application>.
+ </para>
<para>
For an OAuth client flow to be usable, the connection string must at minimum
contain <xref linkend="libpq-connect-oauth-issuer"/> and
@@ -10366,7 +10376,9 @@ typedef struct _PGpromptOAuthDevice
</synopsis>
</para>
<para>
- The OAuth Device Authorization flow included in <application>libpq</application>
+ The OAuth Device Authorization flow which
+ <link linkend="configure-option-with-libcurl">can be included</link>
+ in <application>libpq</application>
requires the end user to visit a URL with a browser, then enter a code
which permits <application>libpq</application> to connect to the server
on their behalf. The default prompt simply prints the
@@ -10378,7 +10390,8 @@ typedef struct _PGpromptOAuthDevice
This callback is only invoked during the builtin device
authorization flow. If the application installs a
<link linkend="libpq-oauth-authdata-oauth-bearer-token">custom OAuth
- flow</link>, this authdata type will not be used.
+ flow</link>, or <application>libpq</application> was not built with
+ support for the builtin flow, this authdata type will not be used.
</para>
<para>
If a non-NULL <structfield>verification_uri_complete</structfield> is
@@ -10400,8 +10413,9 @@ typedef struct _PGpromptOAuthDevice
</term>
<listitem>
<para>
- Replaces the entire OAuth flow with a custom implementation. The hook
- should either directly return a Bearer token for the current
+ Adds a custom implementation of a flow, replacing the builtin flow if
+ it is <link linkend="configure-option-with-libcurl">installed</link>.
+ The hook should either directly return a Bearer token for the current
user/issuer/scope combination, if one is available without blocking, or
else set up an asynchronous callback to retrieve one.
</para>
diff --git a/meson.build b/meson.build
index a1516e54529..b902d4a00f8 100644
--- a/meson.build
+++ b/meson.build
@@ -107,6 +107,7 @@ os_deps = []
backend_both_deps = []
backend_deps = []
libpq_deps = []
+libpq_oauth_deps = []
pg_sysroot = ''
@@ -860,13 +861,13 @@ endif
###############################################################
libcurlopt = get_option('libcurl')
+oauth_flow_supported = false
+
if not libcurlopt.disabled()
# Check for libcurl 7.61.0 or higher (corresponding to RHEL8 and the ability
# to explicitly set TLS 1.3 ciphersuites).
libcurl = dependency('libcurl', version: '>= 7.61.0', required: libcurlopt)
if libcurl.found()
- cdata.set('USE_LIBCURL', 1)
-
# Check to see whether the current platform supports thread-safe Curl
# initialization.
libcurl_threadsafe_init = false
@@ -938,6 +939,22 @@ if not libcurlopt.disabled()
endif
endif
+ # Check that the current platform supports our builtin flow. This requires
+ # libcurl and one of either epoll or kqueue.
+ oauth_flow_supported = (
+ libcurl.found()
+ and (cc.check_header('sys/event.h', required: false,
+ args: test_c_args, include_directories: postgres_inc)
+ or cc.check_header('sys/epoll.h', required: false,
+ args: test_c_args, include_directories: postgres_inc))
+ )
+
+ if oauth_flow_supported
+ cdata.set('USE_LIBCURL', 1)
+ elif libcurlopt.enabled()
+ error('client OAuth is not supported on this platform')
+ endif
+
else
libcurl = not_found_dep
endif
@@ -3272,17 +3289,18 @@ libpq_deps += [
gssapi,
ldap_r,
- # XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
- # during gss_acquire_cred(). This is possibly related to Curl's Heimdal
- # dependency on that platform?
- libcurl,
libintl,
ssl,
]
+libpq_oauth_deps += [
+ libcurl,
+]
+
subdir('src/interfaces/libpq')
-# fe_utils depends on libpq
+# fe_utils and libpq-oauth depends on libpq
subdir('src/fe_utils')
+subdir('src/interfaces/libpq-oauth')
# for frontend binaries
frontend_code = declare_dependency(
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 6722fbdf365..04952b533de 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -347,6 +347,9 @@ perl_embed_ldflags = @perl_embed_ldflags@
AWK = @AWK@
LN_S = @LN_S@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBCURL_LDFLAGS = @LIBCURL_LDFLAGS@
+LIBCURL_LDLIBS = @LIBCURL_LDLIBS@
MSGFMT = @MSGFMT@
MSGFMT_FLAGS = @MSGFMT_FLAGS@
MSGMERGE = @MSGMERGE@
diff --git a/src/interfaces/Makefile b/src/interfaces/Makefile
index 7d56b29d28f..e6822caa206 100644
--- a/src/interfaces/Makefile
+++ b/src/interfaces/Makefile
@@ -14,7 +14,19 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = libpq ecpg
+ifeq ($(with_libcurl), yes)
+SUBDIRS += libpq-oauth
+else
+ALWAYS_SUBDIRS += libpq-oauth
+endif
+
$(recurse)
+$(recurse_always)
all-ecpg-recurse: all-libpq-recurse
install-ecpg-recurse: install-libpq-recurse
+
+ifeq ($(with_libcurl), yes)
+all-libpq-oauth-recurse: all-libpq-recurse
+install-libpq-oauth-recurse: install-libpq-recurse
+endif
diff --git a/src/interfaces/libpq-oauth/Makefile b/src/interfaces/libpq-oauth/Makefile
new file mode 100644
index 00000000000..5fd251a1d27
--- /dev/null
+++ b/src/interfaces/libpq-oauth/Makefile
@@ -0,0 +1,65 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for libpq-oauth
+#
+# Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/interfaces/libpq-oauth/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/interfaces/libpq-oauth
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+PGFILEDESC = "libpq-oauth - device authorization OAuth support"
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
+NAME = pq-oauth-$(MAJORVERSION)
+
+# Force the name "libpq-oauth" for both the static and shared libraries.
+override shlib := lib$(NAME)$(DLSUFFIX)
+override stlib := lib$(NAME).a
+
+override CPPFLAGS := -I$(libpq_srcdir) -I$(top_builddir)/src/port $(LIBCURL_CPPFLAGS) $(CPPFLAGS)
+
+OBJS = \
+ $(WIN32RES) \
+ oauth-curl.o
+
+# The shared library needs additional glue symbols.
+$(shlib): OBJS += oauth-utils.o
+$(shlib): oauth-utils.o
+
+SHLIB_LINK_INTERNAL = $(libpq_pgport_shlib)
+SHLIB_LINK = $(LIBCURL_LDFLAGS) $(LIBCURL_LDLIBS)
+SHLIB_PREREQS = submake-libpq
+SHLIB_EXPORTS = exports.txt
+
+# Disable -bundle_loader on macOS.
+BE_DLLLIBS =
+
+# By default, a library without an SONAME doesn't get a static library, so we
+# add it to the build explicitly.
+all: all-lib all-static-lib
+
+# Shared library stuff
+include $(top_srcdir)/src/Makefile.shlib
+
+# Ignore the standard rules for SONAME-less installation; we want both the
+# static and shared libraries to go into libdir.
+install: all installdirs $(stlib) $(shlib)
+ $(INSTALL_SHLIB) $(shlib) '$(DESTDIR)$(libdir)/$(shlib)'
+ $(INSTALL_STLIB) $(stlib) '$(DESTDIR)$(libdir)/$(stlib)'
+
+installdirs:
+ $(MKDIR_P) '$(DESTDIR)$(libdir)'
+
+uninstall:
+ rm -f '$(DESTDIR)$(libdir)/$(stlib)'
+ rm -f '$(DESTDIR)$(libdir)/$(shlib)'
+
+clean distclean: clean-lib
+ rm -f $(OBJS) oauth-utils.o
diff --git a/src/interfaces/libpq-oauth/README b/src/interfaces/libpq-oauth/README
new file mode 100644
index 00000000000..45def6c1ab6
--- /dev/null
+++ b/src/interfaces/libpq-oauth/README
@@ -0,0 +1,43 @@
+libpq-oauth is an optional module implementing the Device Authorization flow for
+OAuth clients (RFC 8628). It was originally developed as part of libpq core and
+later split out as its own shared library in order to isolate its dependency on
+libcurl. (End users who don't want the Curl dependency can simply choose not to
+install this module.)
+
+If a connection string allows the use of OAuth, and the server asks for it, and
+a libpq client has not installed its own custom OAuth flow, libpq will attempt
+to delay-load this module using dlopen() and the following ABI. Failure to load
+results in a failed connection.
+
+= Load-Time ABI =
+
+This module ABI is an internal implementation detail, so it's subject to change
+across major releases; the name of the module (libpq-oauth-MAJOR) reflects this.
+The module exports the following symbols:
+
+- PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+- void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+pg_fe_run_oauth_flow and pg_fe_cleanup_oauth_flow are implementations of
+conn->async_auth and conn->cleanup_async_auth, respectively.
+
+- void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+
+At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
+libpq_gettext(), which must be injected by libpq using this initialization
+function before the flow is run. It also relies on libpq to expose
+conn->errorMessage, via the errmsg_impl.
+
+This dependency injection is done to ensure that the module ABI is decoupled
+from the internals of `struct pg_conn`. This way we can safely search the
+standard dlopen() paths (e.g. RPATH, LD_LIBRARY_PATH, the SO cache) for an
+implementation module to use, even if that module wasn't compiled at the same
+time as libpq.
+
+= Static Build =
+
+The static library libpq.a does not perform any dynamic loading. If the builtin
+flow is enabled, the application is expected to link against libpq-oauth-*.a
+directly to provide the necessary symbols.
diff --git a/src/interfaces/libpq-oauth/exports.txt b/src/interfaces/libpq-oauth/exports.txt
new file mode 100644
index 00000000000..6891a83dbf9
--- /dev/null
+++ b/src/interfaces/libpq-oauth/exports.txt
@@ -0,0 +1,4 @@
+# src/interfaces/libpq-oauth/exports.txt
+libpq_oauth_init 1
+pg_fe_run_oauth_flow 2
+pg_fe_cleanup_oauth_flow 3
diff --git a/src/interfaces/libpq-oauth/meson.build b/src/interfaces/libpq-oauth/meson.build
new file mode 100644
index 00000000000..cf597e1da1e
--- /dev/null
+++ b/src/interfaces/libpq-oauth/meson.build
@@ -0,0 +1,43 @@
+# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+
+if not oauth_flow_supported
+ subdir_done()
+endif
+
+libpq_oauth_sources = files(
+ 'oauth-curl.c',
+)
+
+# The shared library needs additional glue symbols.
+libpq_oauth_so_sources = files(
+ 'oauth-utils.c',
+)
+
+export_file = custom_target('libpq-oauth.exports',
+ kwargs: gen_export_kwargs,
+)
+
+# port needs to be in include path due to pthread-win32.h
+libpq_oauth_inc = include_directories('.', '../libpq', '../../port')
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
+libpq_oauth_name = 'libpq-oauth-@0@'.format(pg_version_major)
+
+libpq_oauth_st = static_library(libpq_oauth_name,
+ libpq_oauth_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_stlib_code, libpq_oauth_deps],
+ kwargs: default_lib_args,
+)
+
+libpq_oauth_so = shared_module(libpq_oauth_name,
+ libpq_oauth_sources + libpq_oauth_so_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_shlib_code, libpq, libpq_oauth_deps],
+ link_depends: export_file,
+ link_args: export_fmt.format(export_file.full_path()),
+ kwargs: default_lib_args,
+)
diff --git a/src/interfaces/libpq/fe-auth-oauth-curl.c b/src/interfaces/libpq-oauth/oauth-curl.c
similarity index 98%
rename from src/interfaces/libpq/fe-auth-oauth-curl.c
rename to src/interfaces/libpq-oauth/oauth-curl.c
index cd9c0323bb6..d52125415bc 100644
--- a/src/interfaces/libpq/fe-auth-oauth-curl.c
+++ b/src/interfaces/libpq-oauth/oauth-curl.c
@@ -1,6 +1,6 @@
/*-------------------------------------------------------------------------
*
- * fe-auth-oauth-curl.c
+ * oauth-curl.c
* The libcurl implementation of OAuth/OIDC authentication, using the
* OAuth Device Authorization Grant (RFC 8628).
*
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * src/interfaces/libpq/fe-auth-oauth-curl.c
+ * src/interfaces/libpq-oauth/oauth-curl.c
*
*-------------------------------------------------------------------------
*/
@@ -17,20 +17,23 @@
#include <curl/curl.h>
#include <math.h>
-#ifdef HAVE_SYS_EPOLL_H
+#include <unistd.h>
+
+#if defined(HAVE_SYS_EPOLL_H)
#include <sys/epoll.h>
#include <sys/timerfd.h>
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
#include <sys/event.h>
+#else
+#error libpq-oauth is not supported on this platform
#endif
-#include <unistd.h>
#include "common/jsonapi.h"
#include "fe-auth.h"
#include "fe-auth-oauth.h"
-#include "libpq-int.h"
#include "mb/pg_wchar.h"
+#include "oauth-curl.h"
+#include "oauth-utils.h"
/*
* It's generally prudent to set a maximum response size to buffer in memory,
@@ -1110,7 +1113,7 @@ parse_access_token(struct async_ctx *actx, struct token *tok)
static bool
setup_multiplexer(struct async_ctx *actx)
{
-#ifdef HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct epoll_event ev = {.events = EPOLLIN};
actx->mux = epoll_create1(EPOLL_CLOEXEC);
@@ -1134,8 +1137,7 @@ setup_multiplexer(struct async_ctx *actx)
}
return true;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
actx->mux = kqueue();
if (actx->mux < 0)
{
@@ -1158,10 +1160,9 @@ setup_multiplexer(struct async_ctx *actx)
}
return true;
+#else
+#error setup_multiplexer is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support the Device Authorization flow on this platform");
- return false;
}
/*
@@ -1174,7 +1175,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
{
struct async_ctx *actx = ctx;
-#ifdef HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct epoll_event ev = {0};
int res;
int op = EPOLL_CTL_ADD;
@@ -1230,8 +1231,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
}
return 0;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
struct kevent ev[2] = {0};
struct kevent ev_out[2];
struct timespec timeout = {0};
@@ -1312,10 +1312,9 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
}
return 0;
+#else
+#error register_socket is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support multiplexer sockets on this platform");
- return -1;
}
/*
@@ -1334,7 +1333,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
static bool
set_timer(struct async_ctx *actx, long timeout)
{
-#if HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct itimerspec spec = {0};
if (timeout < 0)
@@ -1363,8 +1362,7 @@ set_timer(struct async_ctx *actx, long timeout)
}
return true;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
struct kevent ev;
#ifdef __NetBSD__
@@ -1419,10 +1417,9 @@ set_timer(struct async_ctx *actx, long timeout)
}
return true;
+#else
+#error set_timer is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support timers on this platform");
- return false;
}
/*
@@ -1433,7 +1430,7 @@ set_timer(struct async_ctx *actx, long timeout)
static int
timer_expired(struct async_ctx *actx)
{
-#if HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct itimerspec spec = {0};
if (timerfd_gettime(actx->timerfd, &spec) < 0)
@@ -1453,8 +1450,7 @@ timer_expired(struct async_ctx *actx)
/* If the remaining time to expiration is zero, we're done. */
return (spec.it_value.tv_sec == 0
&& spec.it_value.tv_nsec == 0);
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
int res;
/* Is the timer queue ready? */
@@ -1466,10 +1462,9 @@ timer_expired(struct async_ctx *actx)
}
return (res > 0);
+#else
+#error timer_expired is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support timers on this platform");
- return -1;
}
/*
@@ -2487,8 +2482,9 @@ prompt_user(struct async_ctx *actx, PGconn *conn)
.verification_uri_complete = actx->authz.verification_uri_complete,
.expires_in = actx->authz.expires_in,
};
+ PQauthDataHook_type hook = PQgetAuthDataHook();
- res = PQauthDataHook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
+ res = hook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
if (!res)
{
diff --git a/src/interfaces/libpq-oauth/oauth-curl.h b/src/interfaces/libpq-oauth/oauth-curl.h
new file mode 100644
index 00000000000..248d0424ad0
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-curl.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-curl.h
+ *
+ * Definitions for OAuth Device Authorization module
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-curl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_CURL_H
+#define OAUTH_CURL_H
+
+#include "libpq-fe.h"
+
+/* Exported async-auth callbacks. */
+extern PGDLLEXPORT PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+extern PGDLLEXPORT void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+#endif /* OAUTH_CURL_H */
diff --git a/src/interfaces/libpq-oauth/oauth-utils.c b/src/interfaces/libpq-oauth/oauth-utils.c
new file mode 100644
index 00000000000..62ba4299ec1
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.c
@@ -0,0 +1,198 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.c
+ *
+ * "Glue" helpers providing a copy of some internal APIs from libpq. At
+ * some point in the future, we might be able to deduplicate.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq-oauth/oauth-utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+
+#include "libpq-int.h"
+#include "oauth-utils.h"
+
+static libpq_gettext_func libpq_gettext_impl;
+static conn_errorMessage_func conn_errorMessage;
+
+pgthreadlock_t pg_g_threadlock;
+
+/*-
+ * Initializes libpq-oauth by setting necessary callbacks.
+ *
+ * The current implementation relies on the following private implementation
+ * details of libpq:
+ *
+ * - pg_g_threadlock: protects libcurl initialization if the underlying Curl
+ * installation is not threadsafe
+ *
+ * - libpq_gettext: translates error messages using libpq's message domain
+ *
+ * - conn->errorMessage: holds translated errors for the connection. This is
+ * handled through a translation shim, which avoids either depending on the
+ * offset of the errorMessage in PGconn, or needing to export the variadic
+ * libpq_append_conn_error().
+ */
+void
+libpq_oauth_init(pgthreadlock_t threadlock_impl,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl)
+{
+ pg_g_threadlock = threadlock_impl;
+ libpq_gettext_impl = gettext_impl;
+ conn_errorMessage = errmsg_impl;
+}
+
+/*
+ * Append a formatted string to the error message buffer of the given
+ * connection, after translating it. This is a copy of libpq's internal API.
+ */
+void
+libpq_append_conn_error(PGconn *conn, const char *fmt,...)
+{
+ int save_errno = errno;
+ bool done;
+ va_list args;
+ PQExpBuffer errorMessage = conn_errorMessage(conn);
+
+ Assert(fmt[strlen(fmt) - 1] != '\n');
+
+ if (PQExpBufferBroken(errorMessage))
+ return; /* already failed */
+
+ /* Loop in case we have to retry after enlarging the buffer. */
+ do
+ {
+ errno = save_errno;
+ va_start(args, fmt);
+ done = appendPQExpBufferVA(errorMessage, libpq_gettext(fmt), args);
+ va_end(args);
+ } while (!done);
+
+ appendPQExpBufferChar(errorMessage, '\n');
+}
+
+#ifdef ENABLE_NLS
+
+/*
+ * A shim that defers to the actual libpq_gettext().
+ */
+char *
+libpq_gettext(const char *msgid)
+{
+ if (!libpq_gettext_impl)
+ {
+ /*
+ * Possible if the libpq build didn't enable NLS but the libpq-oauth
+ * build did. That's an odd mismatch, but we can handle it.
+ *
+ * Note that callers of libpq_gettext() have to treat the return value
+ * as if it were const, because builds without NLS simply pass through
+ * their argument.
+ */
+ return unconstify(char *, msgid);
+ }
+
+ return libpq_gettext_impl(msgid);
+}
+
+#endif /* ENABLE_NLS */
+
+/*
+ * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
+ */
+bool
+oauth_unsafe_debugging_enabled(void)
+{
+ const char *env = getenv("PGOAUTHDEBUG");
+
+ return (env && strcmp(env, "UNSAFE") == 0);
+}
+
+/*
+ * Duplicate SOCK_ERRNO* definitions from libpq-int.h, for use by
+ * pq_block/reset_sigpipe().
+ */
+#ifdef WIN32
+#define SOCK_ERRNO (WSAGetLastError())
+#define SOCK_ERRNO_SET(e) WSASetLastError(e)
+#else
+#define SOCK_ERRNO errno
+#define SOCK_ERRNO_SET(e) (errno = (e))
+#endif
+
+/*
+ * Block SIGPIPE for this thread. This is a copy of libpq's internal API.
+ */
+int
+pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
+{
+ sigset_t sigpipe_sigset;
+ sigset_t sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ /* Block SIGPIPE and save previous mask for later reset */
+ SOCK_ERRNO_SET(pthread_sigmask(SIG_BLOCK, &sigpipe_sigset, osigset));
+ if (SOCK_ERRNO)
+ return -1;
+
+ /* We can have a pending SIGPIPE only if it was blocked before */
+ if (sigismember(osigset, SIGPIPE))
+ {
+ /* Is there a pending SIGPIPE? */
+ if (sigpending(&sigset) != 0)
+ return -1;
+
+ if (sigismember(&sigset, SIGPIPE))
+ *sigpipe_pending = true;
+ else
+ *sigpipe_pending = false;
+ }
+ else
+ *sigpipe_pending = false;
+
+ return 0;
+}
+
+/*
+ * Discard any pending SIGPIPE and reset the signal mask. This is a copy of
+ * libpq's internal API.
+ */
+void
+pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
+{
+ int save_errno = SOCK_ERRNO;
+ int signo;
+ sigset_t sigset;
+
+ /* Clear SIGPIPE only if none was pending */
+ if (got_epipe && !sigpipe_pending)
+ {
+ if (sigpending(&sigset) == 0 &&
+ sigismember(&sigset, SIGPIPE))
+ {
+ sigset_t sigpipe_sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ sigwait(&sigpipe_sigset, &signo);
+ }
+ }
+
+ /* Restore saved block mask */
+ pthread_sigmask(SIG_SETMASK, osigset, NULL);
+
+ SOCK_ERRNO_SET(save_errno);
+}
diff --git a/src/interfaces/libpq-oauth/oauth-utils.h b/src/interfaces/libpq-oauth/oauth-utils.h
new file mode 100644
index 00000000000..279fc113248
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.h
+ *
+ * Definitions providing missing libpq internal APIs
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_UTILS_H
+#define OAUTH_UTILS_H
+
+#include "libpq-fe.h"
+#include "pqexpbuffer.h"
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/* Initializes libpq-oauth. */
+extern PGDLLEXPORT void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+
+/* Duplicated APIs, copied from libpq. */
+extern void libpq_append_conn_error(PGconn *conn, const char *fmt,...) pg_attribute_printf(2, 3);
+extern bool oauth_unsafe_debugging_enabled(void);
+extern int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
+extern void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe);
+
+#endif /* OAUTH_UTILS_H */
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 90b0b65db6f..d4c20066ce4 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -31,7 +31,6 @@ endif
OBJS = \
$(WIN32RES) \
- fe-auth-oauth.o \
fe-auth-scram.o \
fe-cancel.o \
fe-connect.o \
@@ -64,9 +63,11 @@ OBJS += \
fe-secure-gssapi.o
endif
-ifeq ($(with_libcurl),yes)
-OBJS += fe-auth-oauth-curl.o
-endif
+# The OAuth implementation differs depending on the type of library being built.
+OBJS_STATIC = fe-auth-oauth.o
+
+fe-auth-oauth_shlib.o: override CPPFLAGS_SHLIB += -DUSE_DYNAMIC_OAUTH
+OBJS_SHLIB = fe-auth-oauth_shlib.o
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
@@ -86,7 +87,7 @@ endif
# that are built correctly for use in a shlib.
SHLIB_LINK_INTERNAL = -lpgcommon_shlib -lpgport_shlib
ifneq ($(PORTNAME), win32)
-SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lcurl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
+SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
else
SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi32 -lssl -lsocket -lnsl -lresolv -lintl -lm $(PTHREAD_LIBS), $(LIBS)) $(LDAP_LIBS_FE)
endif
@@ -101,12 +102,26 @@ ifeq ($(with_ssl),openssl)
PKG_CONFIG_REQUIRES_PRIVATE = libssl, libcrypto
endif
+ifeq ($(with_libcurl),yes)
+# libpq.so doesn't link against libcurl, but libpq.a needs libpq-oauth, and
+# libpq-oauth needs libcurl. Put both into *.private.
+PKG_CONFIG_REQUIRES_PRIVATE += libcurl
+%.pc: override SHLIB_LINK_INTERNAL += -lpq-oauth-$(MAJORVERSION)
+endif
+
all: all-lib libpq-refs-stamp
# Shared library stuff
include $(top_srcdir)/src/Makefile.shlib
backend_src = $(top_srcdir)/src/backend
+# Add shlib-/stlib-specific objects.
+$(shlib): override OBJS += $(OBJS_SHLIB)
+$(shlib): $(OBJS_SHLIB)
+
+$(stlib): override OBJS += $(OBJS_STATIC)
+$(stlib): $(OBJS_STATIC)
+
# Check for functions that libpq must not call, currently just exit().
# (Ideally we'd reject abort() too, but there are various scenarios where
# build toolchains insert abort() calls, e.g. to implement assert().)
@@ -115,8 +130,6 @@ backend_src = $(top_srcdir)/src/backend
# which seems to insert references to that even in pure C code. Excluding
# __tsan_func_exit is necessary when using ThreadSanitizer data race detector
# which use this function for instrumentation of function exit.
-# libcurl registers an exit handler in the memory debugging code when running
-# with LeakSanitizer.
# Skip the test when profiling, as gcc may insert exit() calls for that.
# Also skip the test on platforms where libpq infrastructure may be provided
# by statically-linked libraries, as we can't expect them to honor this
@@ -124,7 +137,7 @@ backend_src = $(top_srcdir)/src/backend
libpq-refs-stamp: $(shlib)
ifneq ($(enable_coverage), yes)
ifeq (,$(filter solaris,$(PORTNAME)))
- @if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit -e _atexit | grep exit; then \
+ @if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit | grep exit; then \
echo 'libpq must not be calling any function which invokes exit'; exit 1; \
fi
endif
@@ -138,6 +151,11 @@ fe-misc.o: fe-misc.c $(top_builddir)/src/port/pg_config_paths.h
$(top_builddir)/src/port/pg_config_paths.h:
$(MAKE) -C $(top_builddir)/src/port pg_config_paths.h
+# Use src/common/Makefile's trick for tracking dependencies of shlib-specific
+# objects.
+%_shlib.o: %.c %.o
+ $(CC) $(CFLAGS) $(CFLAGS_SL) $(CPPFLAGS) $(CPPFLAGS_SHLIB) -c $< -o $@
+
install: all installdirs install-lib
$(INSTALL_DATA) $(srcdir)/libpq-fe.h '$(DESTDIR)$(includedir)'
$(INSTALL_DATA) $(srcdir)/libpq-events.h '$(DESTDIR)$(includedir)'
@@ -171,6 +189,6 @@ uninstall: uninstall-lib
clean distclean: clean-lib
$(MAKE) -C test $@
rm -rf tmp_check
- rm -f $(OBJS) pthread.h libpq-refs-stamp
+ rm -f $(OBJS) $(OBJS_SHLIB) $(OBJS_STATIC) pthread.h libpq-refs-stamp
# Might be left over from a Win32 client-only build
rm -f pg_config_paths.h
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index d5143766858..0625cf39e9a 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -210,3 +210,4 @@ PQsetAuthDataHook 207
PQgetAuthDataHook 208
PQdefaultAuthDataHook 209
PQfullProtocolVersion 210
+appendPQExpBufferVA 211
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
index cf1a25e2ccc..20c848ec9e0 100644
--- a/src/interfaces/libpq/fe-auth-oauth.c
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -15,6 +15,10 @@
#include "postgres_fe.h"
+#ifdef USE_DYNAMIC_OAUTH
+#include <dlfcn.h>
+#endif
+
#include "common/base64.h"
#include "common/hmac.h"
#include "common/jsonapi.h"
@@ -22,6 +26,7 @@
#include "fe-auth.h"
#include "fe-auth-oauth.h"
#include "mb/pg_wchar.h"
+#include "pg_config_paths.h"
/* The exported OAuth callback mechanism. */
static void *oauth_init(PGconn *conn, const char *password,
@@ -721,6 +726,186 @@ cleanup_user_oauth_flow(PGconn *conn)
state->async_ctx = NULL;
}
+/*-------------
+ * Builtin Flow
+ *
+ * There are three potential implementations of use_builtin_flow:
+ *
+ * 1) If the OAuth client is disabled at configuration time, return false.
+ * Dependent clients must provide their own flow.
+ * 2) If the OAuth client is enabled and USE_DYNAMIC_OAUTH is defined, dlopen()
+ * the libpq-oauth plugin and use its implementation.
+ * 3) Otherwise, use flow callbacks that are statically linked into the
+ * executable.
+ */
+
+#if !defined(USE_LIBCURL)
+
+/*
+ * This configuration doesn't support the builtin flow.
+ */
+
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ return false;
+}
+
+#elif defined(USE_DYNAMIC_OAUTH)
+
+/*
+ * Use the builtin flow in the libpq-oauth plugin, which is loaded at runtime.
+ */
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/*
+ * This shim is injected into libpq-oauth so that it doesn't depend on the
+ * offset of conn->errorMessage.
+ *
+ * TODO: look into exporting libpq_append_conn_error or a comparable API from
+ * libpq, instead.
+ */
+static PQExpBuffer
+conn_errorMessage(PGconn *conn)
+{
+ return &conn->errorMessage;
+}
+
+/*
+ * Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
+ * callbacks into the connection's async auth handlers.
+ *
+ * Failure to load here results in a relatively quiet connection error, to
+ * handle the use case where the build supports loading a flow but a user does
+ * not want to install it. Troubleshooting of linker/loader failures can be done
+ * via PGOAUTHDEBUG.
+ */
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ static bool initialized = false;
+ static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
+ int lockerr;
+
+ void (*init) (pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+ PostgresPollingStatusType (*flow) (PGconn *conn);
+ void (*cleanup) (PGconn *conn);
+
+ /*
+ * On macOS only, load the module using its absolute install path; the
+ * standard search behavior is not very helpful for this use case. Unlike
+ * on other platforms, DYLD_LIBRARY_PATH is used as a fallback even with
+ * absolute paths (modulo SIP effects), so tests can continue to work.
+ *
+ * On the other platforms, load the module using only the basename, to
+ * rely on the runtime linker's standard search behavior.
+ */
+ const char *const module_name =
+#if defined(__darwin__)
+ LIBDIR "/libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
+#else
+ "libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
+#endif
+
+ state->builtin_flow = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
+ if (!state->builtin_flow)
+ {
+ /*
+ * For end users, this probably isn't an error condition, it just
+ * means the flow isn't installed. Developers and package maintainers
+ * may want to debug this via the PGOAUTHDEBUG envvar, though.
+ *
+ * Note that POSIX dlerror() isn't guaranteed to be threadsafe.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
+
+ return false;
+ }
+
+ if ((init = dlsym(state->builtin_flow, "libpq_oauth_init")) == NULL
+ || (flow = dlsym(state->builtin_flow, "pg_fe_run_oauth_flow")) == NULL
+ || (cleanup = dlsym(state->builtin_flow, "pg_fe_cleanup_oauth_flow")) == NULL)
+ {
+ /*
+ * This is more of an error condition than the one above, but due to
+ * the dlerror() threadsafety issue, lock it behind PGOAUTHDEBUG too.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
+
+ dlclose(state->builtin_flow);
+ return false;
+ }
+
+ /*
+ * Past this point, we do not unload the module. It stays in the process
+ * permanently.
+ */
+
+ /*
+ * We need to inject necessary function pointers into the module. This
+ * only needs to be done once -- even if the pointers are constant,
+ * assigning them while another thread is executing the flows feels like
+ * tempting fate.
+ */
+ if ((lockerr = pthread_mutex_lock(&init_mutex)) != 0)
+ {
+ /* Should not happen... but don't continue if it does. */
+ Assert(false);
+
+ libpq_append_conn_error(conn, "failed to lock mutex (%d)", lockerr);
+ return false;
+ }
+
+ if (!initialized)
+ {
+ init(pg_g_threadlock,
+#ifdef ENABLE_NLS
+ libpq_gettext,
+#else
+ NULL,
+#endif
+ conn_errorMessage);
+
+ initialized = true;
+ }
+
+ pthread_mutex_unlock(&init_mutex);
+
+ /* Set our asynchronous callbacks. */
+ conn->async_auth = flow;
+ conn->cleanup_async_auth = cleanup;
+
+ return true;
+}
+
+#else
+
+/*
+ * Use the builtin flow in libpq-oauth.a (see libpq-oauth/oauth-curl.h).
+ */
+
+extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ /* Set our asynchronous callbacks. */
+ conn->async_auth = pg_fe_run_oauth_flow;
+ conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
+
+ return true;
+}
+
+#endif /* USE_LIBCURL */
+
+
/*
* Chooses an OAuth client flow for the connection, which will retrieve a Bearer
* token for presentation to the server.
@@ -792,18 +977,10 @@ setup_token_request(PGconn *conn, fe_oauth_state *state)
libpq_append_conn_error(conn, "user-defined OAuth flow failed");
goto fail;
}
- else
+ else if (!use_builtin_flow(conn, state))
{
-#if USE_LIBCURL
- /* Hand off to our built-in OAuth flow. */
- conn->async_auth = pg_fe_run_oauth_flow;
- conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
-
-#else
- libpq_append_conn_error(conn, "no custom OAuth flows are available, and libpq was not built with libcurl support");
+ libpq_append_conn_error(conn, "no custom OAuth flows are available, and the builtin flow is not installed");
goto fail;
-
-#endif
}
return true;
diff --git a/src/interfaces/libpq/fe-auth-oauth.h b/src/interfaces/libpq/fe-auth-oauth.h
index 3f1a7503a01..687e664475f 100644
--- a/src/interfaces/libpq/fe-auth-oauth.h
+++ b/src/interfaces/libpq/fe-auth-oauth.h
@@ -33,12 +33,13 @@ typedef struct
PGconn *conn;
void *async_ctx;
+
+ void *builtin_flow;
} fe_oauth_state;
-extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
-extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
extern void pqClearOAuthToken(PGconn *conn);
extern bool oauth_unsafe_debugging_enabled(void);
+extern bool use_builtin_flow(PGconn *conn, fe_oauth_state *state);
/* Mechanisms in fe-auth-oauth.c */
extern const pg_fe_sasl_mech pg_oauth_mech;
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index 292fecf3320..29b3451e3aa 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -38,10 +38,6 @@ if gssapi.found()
)
endif
-if libcurl.found()
- libpq_sources += files('fe-auth-oauth-curl.c')
-endif
-
export_file = custom_target('libpq.exports',
kwargs: gen_export_kwargs,
)
@@ -50,6 +46,9 @@ export_file = custom_target('libpq.exports',
libpq_inc = include_directories('.', '../../port')
libpq_c_args = ['-DSO_MAJOR_VERSION=5']
+# The OAuth implementation differs depending on the type of library being built.
+libpq_so_c_args = ['-DUSE_DYNAMIC_OAUTH']
+
# Not using both_libraries() here as
# 1) resource files should only be in the shared library
# 2) we want the .pc file to include a dependency to {pgport,common}_static for
@@ -70,7 +69,7 @@ libpq_st = static_library('libpq',
libpq_so = shared_library('libpq',
libpq_sources + libpq_so_sources,
include_directories: [libpq_inc, postgres_inc],
- c_args: libpq_c_args,
+ c_args: libpq_c_args + libpq_so_c_args,
c_pch: pch_postgres_fe_h,
version: '5.' + pg_version_major.to_string(),
soversion: host_system != 'windows' ? '5' : '',
@@ -86,12 +85,26 @@ libpq = declare_dependency(
include_directories: [include_directories('.')]
)
+private_deps = [
+ frontend_stlib_code,
+ libpq_deps,
+]
+
+if oauth_flow_supported
+ # libpq.so doesn't link against libcurl, but libpq.a needs libpq-oauth, and
+ # libpq-oauth needs libcurl. Put both into *.private.
+ private_deps += [
+ libpq_oauth_deps,
+ '-lpq-oauth-@0@'.format(pg_version_major),
+ ]
+endif
+
pkgconfig.generate(
name: 'libpq',
description: 'PostgreSQL libpq library',
url: pg_url,
libraries: libpq,
- libraries_private: [frontend_stlib_code, libpq_deps],
+ libraries_private: private_deps,
)
install_headers(
diff --git a/src/interfaces/libpq/nls.mk b/src/interfaces/libpq/nls.mk
index ae761265852..b87df277d93 100644
--- a/src/interfaces/libpq/nls.mk
+++ b/src/interfaces/libpq/nls.mk
@@ -13,15 +13,21 @@ GETTEXT_FILES = fe-auth.c \
fe-secure-common.c \
fe-secure-gssapi.c \
fe-secure-openssl.c \
- win32.c
-GETTEXT_TRIGGERS = libpq_append_conn_error:2 \
+ win32.c \
+ ../libpq-oauth/oauth-curl.c \
+ ../libpq-oauth/oauth-utils.c
+GETTEXT_TRIGGERS = actx_error:2 \
+ libpq_append_conn_error:2 \
libpq_append_error:2 \
libpq_gettext \
libpq_ngettext:1,2 \
+ oauth_parse_set_error:2 \
pqInternalNotice:2
-GETTEXT_FLAGS = libpq_append_conn_error:2:c-format \
+GETTEXT_FLAGS = actx_error:2:c-format \
+ libpq_append_conn_error:2:c-format \
libpq_append_error:2:c-format \
libpq_gettext:1:pass-c-format \
libpq_ngettext:1:pass-c-format \
libpq_ngettext:2:pass-c-format \
+ oauth_parse_set_error:2:c-format \
pqInternalNotice:2:c-format
diff --git a/src/makefiles/meson.build b/src/makefiles/meson.build
index 55da678ec27..91a8de1ee9b 100644
--- a/src/makefiles/meson.build
+++ b/src/makefiles/meson.build
@@ -203,6 +203,8 @@ pgxs_empty = [
'LIBNUMA_CFLAGS', 'LIBNUMA_LIBS',
'LIBURING_CFLAGS', 'LIBURING_LIBS',
+
+ 'LIBCURL_CPPFLAGS', 'LIBCURL_LDFLAGS', 'LIBCURL_LDLIBS',
]
if host_system == 'windows' and cc.get_argument_syntax() != 'msvc'
diff --git a/src/test/modules/oauth_validator/meson.build b/src/test/modules/oauth_validator/meson.build
index 36d1b26369f..e190f9cf15a 100644
--- a/src/test/modules/oauth_validator/meson.build
+++ b/src/test/modules/oauth_validator/meson.build
@@ -78,7 +78,7 @@ tests += {
],
'env': {
'PYTHON': python.path(),
- 'with_libcurl': libcurl.found() ? 'yes' : 'no',
+ 'with_libcurl': oauth_flow_supported ? 'yes' : 'no',
'with_python': 'yes',
},
},
diff --git a/src/test/modules/oauth_validator/t/002_client.pl b/src/test/modules/oauth_validator/t/002_client.pl
index 54769f12f57..c23b53ac98f 100644
--- a/src/test/modules/oauth_validator/t/002_client.pl
+++ b/src/test/modules/oauth_validator/t/002_client.pl
@@ -111,7 +111,7 @@ if ($ENV{with_libcurl} ne 'yes')
"fails without custom hook installed",
flags => ["--no-hook"],
expected_stderr =>
- qr/no custom OAuth flows are available, and libpq was not built with libcurl support/
+ qr/no custom OAuth flows are available, and the builtin flow is not installed/
);
}
--
2.34.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-18 17:01 ` Jacob Champion <[email protected]>
1 sibling, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-18 17:01 UTC (permalink / raw)
To: Jelte Fennema-Nio <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>
On Thu, Apr 17, 2025 at 5:47 PM Jacob Champion
<[email protected]> wrote:
> With those, I have no more TODOs and I believe this is ready for a
> final review round.
Some ABI self-review. These references to conn->errorMessage also need
the indirection treatment, which I'm working on now:
> if (actx->errctx)
> {
> appendPQExpBufferStr(&conn->errorMessage,
> libpq_gettext(actx->errctx));
> appendPQExpBufferStr(&conn->errorMessage, ": ");
> ...
I was searching backwards through history to confirm that we don't
rearrange struct pg_conn in back branches; turns out that was a false
assumption. See e8f60e6fe2:
While at it, fix some places where parameter-related infrastructure
was added with the aid of a dartboard, or perhaps with the aid of
the anti-pattern "add new stuff at the end". It should be safe
to rearrange the contents of struct pg_conn even in released
branches, since that's private to libpq (and we'd have to move
some fields in some builds to fix this, anyway).
So that means, I think, the name needs to go back to -<major>-<minor>,
unless anyone can think of a clever way around it. (Injecting
conn->errorMessage to avoid the messiness around ENABLE_GSS et al is
still useful, but injecting every single offset doesn't seem
maintainable to me.) Sorry, Christoph; I know that's not what you were
hoping for.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-21 23:19 ` Jacob Champion <[email protected]>
2025-04-22 23:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
1 sibling, 2 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-21 23:19 UTC (permalink / raw)
To: Christoph Berg <[email protected]>; Jacob Champion <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Sat, Apr 19, 2025 at 5:04 AM Christoph Berg <[email protected]> wrote:
> How about this:
>
> No libpq OAuth flows are available. (Try installing the libpq-oauth package.)
Tweaked for capitalization/punctuation rules, and removing the first
"libpq" mention (which I don't think helps a user of, say, psql):
no OAuth flows are available (try installing the libpq-oauth package)
v8 also makes the following changes:
- Per ABI comment upthread, we are back to major-minor versioning for
the shared library (e.g. libpq-oauth-18-0.so). 0001 adds the macros
and makefile variables to make this easy, and 0002 is the bulk of the
change now.
- Since libpq-oauth.a is going to be discovered at compile time, not
runtime, I've removed the versioning from that filename. Static
clients need to match them anyway, so we don't need that additional
packaging headache.
- conn->errorMessage is now decoupled from oauth-curl.c. Separate
object file builds are made using the same technique as libpq.
Thanks,
--Jacob
-: ----------- > 1: 5f87f11b18e Add minor-version counterpart to (PG_)MAJORVERSION
1: 942ad5391e2 ! 2: 4c9cc7f69af oauth: Move the builtin flow into a separate module
@@ Commit message
the search path came from a different build of Postgres.
This ABI is considered "private". The module has no SONAME or version
- symlinks, and it's named libpq-oauth-<major>.so to avoid mixing and
- matching across major Postgres versions. (Future improvements may
- promote this "OAuth flow plugin" to a first-class concept, at which
- point we would need a public API to replace this anyway.)
+ symlinks, and it's named libpq-oauth-<major>-<minor>.so to avoid mixing
+ and matching across Postgres versions, in case internal struct order
+ needs to change. (Future improvements may promote this "OAuth flow
+ plugin" to a first-class concept, at which point we would need a public
+ API to replace this anyway.)
Additionally, NLS support for error messages in b3f0be788a was
incomplete, because the new error macros weren't being scanned by
@@ src/interfaces/libpq-oauth/Makefile (new)
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
-+NAME = pq-oauth-$(MAJORVERSION)
++NAME = pq-oauth-$(MAJORVERSION)-$(MINORVERSION)
+
-+# Force the name "libpq-oauth" for both the static and shared libraries.
++# Force the name "libpq-oauth" for both the static and shared libraries. The
++# staticlib doesn't need version information in its name.
+override shlib := lib$(NAME)$(DLSUFFIX)
-+override stlib := lib$(NAME).a
++override stlib := libpq-oauth.a
+
+override CPPFLAGS := -I$(libpq_srcdir) -I$(top_builddir)/src/port $(LIBCURL_CPPFLAGS) $(CPPFLAGS)
+
+OBJS = \
-+ $(WIN32RES) \
-+ oauth-curl.o
++ $(WIN32RES)
++
++OBJS_STATIC = oauth-curl.o
+
+# The shared library needs additional glue symbols.
-+$(shlib): OBJS += oauth-utils.o
-+$(shlib): oauth-utils.o
++OBJS_SHLIB = \
++ oauth-curl_shlib.o \
++ oauth-utils.o \
++
++oauth-utils.o: override CPPFLAGS += -DUSE_DYNAMIC_OAUTH
++oauth-curl_shlib.o: override CPPFLAGS_SHLIB += -DUSE_DYNAMIC_OAUTH
++
++# Add shlib-/stlib-specific objects.
++$(shlib): override OBJS += $(OBJS_SHLIB)
++$(shlib): $(OBJS_SHLIB)
++
++$(stlib): override OBJS += $(OBJS_STATIC)
++$(stlib): $(OBJS_STATIC)
+
+SHLIB_LINK_INTERNAL = $(libpq_pgport_shlib)
+SHLIB_LINK = $(LIBCURL_LDFLAGS) $(LIBCURL_LDLIBS)
@@ src/interfaces/libpq-oauth/Makefile (new)
+# Shared library stuff
+include $(top_srcdir)/src/Makefile.shlib
+
++# Use src/common/Makefile's trick for tracking dependencies of shlib-specific
++# objects.
++%_shlib.o: %.c %.o
++ $(CC) $(CFLAGS) $(CFLAGS_SL) $(CPPFLAGS) $(CPPFLAGS_SHLIB) -c $< -o $@
++
+# Ignore the standard rules for SONAME-less installation; we want both the
+# static and shared libraries to go into libdir.
+install: all installdirs $(stlib) $(shlib)
@@ src/interfaces/libpq-oauth/Makefile (new)
+ rm -f '$(DESTDIR)$(libdir)/$(shlib)'
+
+clean distclean: clean-lib
-+ rm -f $(OBJS) oauth-utils.o
++ rm -f $(OBJS) $(OBJS_STATIC) $(OBJS_SHLIB)
## src/interfaces/libpq-oauth/README (new) ##
@@
@@ src/interfaces/libpq-oauth/README (new)
+= Load-Time ABI =
+
+This module ABI is an internal implementation detail, so it's subject to change
-+across major releases; the name of the module (libpq-oauth-MAJOR) reflects this.
++across releases; the name of the module (libpq-oauth-MAJOR-MINOR) reflects this.
+The module exports the following symbols:
+
+- PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
@@ src/interfaces/libpq-oauth/README (new)
+
+At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
+libpq_gettext(), which must be injected by libpq using this initialization
-+function before the flow is run. It also relies on libpq to expose
-+conn->errorMessage, via the errmsg_impl.
++function before the flow is run.
+
-+This dependency injection is done to ensure that the module ABI is decoupled
-+from the internals of `struct pg_conn`. This way we can safely search the
-+standard dlopen() paths (e.g. RPATH, LD_LIBRARY_PATH, the SO cache) for an
-+implementation module to use, even if that module wasn't compiled at the same
-+time as libpq.
++It also relies on libpq to expose conn->errorMessage, via the errmsg_impl. This
++is done to decouple the module ABI from the offset of errorMessage, which can
++change positions depending on configure-time options. This way we can safely
++search the standard dlopen() paths (e.g. RPATH, LD_LIBRARY_PATH, the SO cache)
++for an implementation module to use, even if that module wasn't compiled at the
++same time as libpq.
+
+= Static Build =
+
+The static library libpq.a does not perform any dynamic loading. If the builtin
-+flow is enabled, the application is expected to link against libpq-oauth-*.a
++flow is enabled, the application is expected to link against libpq-oauth.a
+directly to provide the necessary symbols.
## src/interfaces/libpq-oauth/exports.txt (new) ##
@@ src/interfaces/libpq-oauth/meson.build (new)
+libpq_oauth_so_sources = files(
+ 'oauth-utils.c',
+)
++libpq_oauth_so_c_args = ['-DUSE_DYNAMIC_OAUTH']
+
+export_file = custom_target('libpq-oauth.exports',
+ kwargs: gen_export_kwargs,
@@ src/interfaces/libpq-oauth/meson.build (new)
+# port needs to be in include path due to pthread-win32.h
+libpq_oauth_inc = include_directories('.', '../libpq', '../../port')
+
-+# This is an internal module; we don't want an SONAME and therefore do not set
-+# SO_MAJOR_VERSION.
-+libpq_oauth_name = 'libpq-oauth-@0@'.format(pg_version_major)
-+
-+libpq_oauth_st = static_library(libpq_oauth_name,
++libpq_oauth_st = static_library('libpq-oauth',
+ libpq_oauth_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_pch: pch_postgres_fe_h,
@@ src/interfaces/libpq-oauth/meson.build (new)
+ kwargs: default_lib_args,
+)
+
++# This is an internal module; we don't want an SONAME and therefore do not set
++# SO_MAJOR_VERSION.
++libpq_oauth_name = 'libpq-oauth-@0@-@1@'.format(pg_version_major, pg_version_minor)
++
+libpq_oauth_so = shared_module(libpq_oauth_name,
+ libpq_oauth_sources + libpq_oauth_so_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
++ c_args: libpq_so_c_args,
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_shlib_code, libpq, libpq_oauth_deps],
+ link_depends: export_file,
@@ src/interfaces/libpq/fe-auth-oauth-curl.c => src/interfaces/libpq-oauth/oauth-cu
-#include "libpq-int.h"
#include "mb/pg_wchar.h"
+#include "oauth-curl.h"
++#ifdef USE_DYNAMIC_OAUTH
+#include "oauth-utils.h"
++#endif
/*
* It's generally prudent to set a maximum response size to buffer in memory,
@@ src/interfaces/libpq-oauth/oauth-curl.c: prompt_user(struct async_ctx *actx, PGc
if (!res)
{
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ {
+ fe_oauth_state *state = conn->sasl_state;
+ struct async_ctx *actx;
++ PQExpBuffer errbuf;
+
+ if (!initialize_curl(conn))
+ return PGRES_POLLING_FAILED;
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+
+ error_return:
+
++ /*
++ * For the dynamic module build, we can't safely rely on the offset of
++ * conn->errorMessage, since it depends on build options like USE_SSL et
++ * al. libpq gives us a translator function instead.
++ */
++#ifdef USE_DYNAMIC_OAUTH
++ errbuf = conn_errorMessage(conn);
++#else
++ errbuf = &conn->errorMessage;
++#endif
++
+ /*
+ * Assemble the three parts of our error: context, body, and detail. See
+ * also the documentation for struct async_ctx.
+ */
+ if (actx->errctx)
+ {
+- appendPQExpBufferStr(&conn->errorMessage,
+- libpq_gettext(actx->errctx));
+- appendPQExpBufferStr(&conn->errorMessage, ": ");
++ appendPQExpBufferStr(errbuf, libpq_gettext(actx->errctx));
++ appendPQExpBufferStr(errbuf, ": ");
+ }
+
+ if (PQExpBufferDataBroken(actx->errbuf))
+- appendPQExpBufferStr(&conn->errorMessage,
+- libpq_gettext("out of memory"));
++ appendPQExpBufferStr(errbuf, libpq_gettext("out of memory"));
+ else
+- appendPQExpBufferStr(&conn->errorMessage, actx->errbuf.data);
++ appendPQExpBufferStr(errbuf, actx->errbuf.data);
+
+ if (actx->curl_err[0])
+ {
+ size_t len;
+
+- appendPQExpBuffer(&conn->errorMessage,
+- " (libcurl: %s)", actx->curl_err);
++ appendPQExpBuffer(errbuf, " (libcurl: %s)", actx->curl_err);
+
+ /* Sometimes libcurl adds a newline to the error buffer. :( */
+- len = conn->errorMessage.len;
+- if (len >= 2 && conn->errorMessage.data[len - 2] == '\n')
++ len = errbuf->len;
++ if (len >= 2 && errbuf->data[len - 2] == '\n')
+ {
+- conn->errorMessage.data[len - 2] = ')';
+- conn->errorMessage.data[len - 1] = '\0';
+- conn->errorMessage.len--;
++ errbuf->data[len - 2] = ')';
++ errbuf->data[len - 1] = '\0';
++ errbuf->len--;
+ }
+ }
+
+- appendPQExpBufferChar(&conn->errorMessage, '\n');
++ appendPQExpBufferChar(errbuf, '\n');
+
+ return PGRES_POLLING_FAILED;
+ }
## src/interfaces/libpq-oauth/oauth-curl.h (new) ##
@@
@@ src/interfaces/libpq-oauth/oauth-utils.c (new)
+#include "libpq-int.h"
+#include "oauth-utils.h"
+
++#ifndef USE_DYNAMIC_OAUTH
++#error oauth-utils.c is not supported in static builds
++#endif
++
+static libpq_gettext_func libpq_gettext_impl;
-+static conn_errorMessage_func conn_errorMessage;
+
+pgthreadlock_t pg_g_threadlock;
++conn_errorMessage_func conn_errorMessage;
+
+/*-
+ * Initializes libpq-oauth by setting necessary callbacks.
@@ src/interfaces/libpq-oauth/oauth-utils.h (new)
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+
++/* Callback to safely obtain conn->errorMessage from a PGconn. */
++extern conn_errorMessage_func conn_errorMessage;
++
+/* Duplicated APIs, copied from libpq. */
+extern void libpq_append_conn_error(PGconn *conn, const char *fmt,...) pg_attribute_printf(2, 3);
+extern bool oauth_unsafe_debugging_enabled(void);
@@ src/interfaces/libpq/Makefile: ifeq ($(with_ssl),openssl)
+# libpq.so doesn't link against libcurl, but libpq.a needs libpq-oauth, and
+# libpq-oauth needs libcurl. Put both into *.private.
+PKG_CONFIG_REQUIRES_PRIVATE += libcurl
-+%.pc: override SHLIB_LINK_INTERNAL += -lpq-oauth-$(MAJORVERSION)
++%.pc: override SHLIB_LINK_INTERNAL += -lpq-oauth
+endif
+
all: all-lib libpq-refs-stamp
@@ src/interfaces/libpq/fe-auth-oauth.c: cleanup_user_oauth_flow(PGconn *conn)
+ */
+ const char *const module_name =
+#if defined(__darwin__)
-+ LIBDIR "/libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
++ LIBDIR "/libpq-oauth-" PG_MAJORVERSION "-" PG_MINORVERSION DLSUFFIX;
+#else
-+ "libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
++ "libpq-oauth-" PG_MAJORVERSION "-" PG_MINORVERSION DLSUFFIX;
+#endif
+
+ state->builtin_flow = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
@@ src/interfaces/libpq/fe-auth-oauth.c: setup_token_request(PGconn *conn, fe_oauth
-
-#else
- libpq_append_conn_error(conn, "no custom OAuth flows are available, and libpq was not built with libcurl support");
-+ libpq_append_conn_error(conn, "no custom OAuth flows are available, and the builtin flow is not installed");
++ libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
goto fail;
-
-#endif
@@ src/interfaces/libpq/meson.build: libpq = declare_dependency(
+ # libpq-oauth needs libcurl. Put both into *.private.
+ private_deps += [
+ libpq_oauth_deps,
-+ '-lpq-oauth-@0@'.format(pg_version_major),
++ '-lpq-oauth',
+ ]
+endif
+
@@ src/test/modules/oauth_validator/t/002_client.pl: if ($ENV{with_libcurl} ne 'yes
flags => ["--no-hook"],
expected_stderr =>
- qr/no custom OAuth flows are available, and libpq was not built with libcurl support/
-+ qr/no custom OAuth flows are available, and the builtin flow is not installed/
++ qr/no OAuth flows are available \(try installing the libpq-oauth package\)/
);
}
Attachments:
[text/plain] since-v7.diff.txt (13.0K, ../../CAOYmi+k9uN2OQ_sXjztZYPSpPbfGmkkv4yhtPhyjNQ6vy7SSfA@mail.gmail.com/2-since-v7.diff.txt)
download | inline:
-: ----------- > 1: 5f87f11b18e Add minor-version counterpart to (PG_)MAJORVERSION
1: 942ad5391e2 ! 2: 4c9cc7f69af oauth: Move the builtin flow into a separate module
@@ Commit message
the search path came from a different build of Postgres.
This ABI is considered "private". The module has no SONAME or version
- symlinks, and it's named libpq-oauth-<major>.so to avoid mixing and
- matching across major Postgres versions. (Future improvements may
- promote this "OAuth flow plugin" to a first-class concept, at which
- point we would need a public API to replace this anyway.)
+ symlinks, and it's named libpq-oauth-<major>-<minor>.so to avoid mixing
+ and matching across Postgres versions, in case internal struct order
+ needs to change. (Future improvements may promote this "OAuth flow
+ plugin" to a first-class concept, at which point we would need a public
+ API to replace this anyway.)
Additionally, NLS support for error messages in b3f0be788a was
incomplete, because the new error macros weren't being scanned by
@@ src/interfaces/libpq-oauth/Makefile (new)
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
-+NAME = pq-oauth-$(MAJORVERSION)
++NAME = pq-oauth-$(MAJORVERSION)-$(MINORVERSION)
+
-+# Force the name "libpq-oauth" for both the static and shared libraries.
++# Force the name "libpq-oauth" for both the static and shared libraries. The
++# staticlib doesn't need version information in its name.
+override shlib := lib$(NAME)$(DLSUFFIX)
-+override stlib := lib$(NAME).a
++override stlib := libpq-oauth.a
+
+override CPPFLAGS := -I$(libpq_srcdir) -I$(top_builddir)/src/port $(LIBCURL_CPPFLAGS) $(CPPFLAGS)
+
+OBJS = \
-+ $(WIN32RES) \
-+ oauth-curl.o
++ $(WIN32RES)
++
++OBJS_STATIC = oauth-curl.o
+
+# The shared library needs additional glue symbols.
-+$(shlib): OBJS += oauth-utils.o
-+$(shlib): oauth-utils.o
++OBJS_SHLIB = \
++ oauth-curl_shlib.o \
++ oauth-utils.o \
++
++oauth-utils.o: override CPPFLAGS += -DUSE_DYNAMIC_OAUTH
++oauth-curl_shlib.o: override CPPFLAGS_SHLIB += -DUSE_DYNAMIC_OAUTH
++
++# Add shlib-/stlib-specific objects.
++$(shlib): override OBJS += $(OBJS_SHLIB)
++$(shlib): $(OBJS_SHLIB)
++
++$(stlib): override OBJS += $(OBJS_STATIC)
++$(stlib): $(OBJS_STATIC)
+
+SHLIB_LINK_INTERNAL = $(libpq_pgport_shlib)
+SHLIB_LINK = $(LIBCURL_LDFLAGS) $(LIBCURL_LDLIBS)
@@ src/interfaces/libpq-oauth/Makefile (new)
+# Shared library stuff
+include $(top_srcdir)/src/Makefile.shlib
+
++# Use src/common/Makefile's trick for tracking dependencies of shlib-specific
++# objects.
++%_shlib.o: %.c %.o
++ $(CC) $(CFLAGS) $(CFLAGS_SL) $(CPPFLAGS) $(CPPFLAGS_SHLIB) -c $< -o $@
++
+# Ignore the standard rules for SONAME-less installation; we want both the
+# static and shared libraries to go into libdir.
+install: all installdirs $(stlib) $(shlib)
@@ src/interfaces/libpq-oauth/Makefile (new)
+ rm -f '$(DESTDIR)$(libdir)/$(shlib)'
+
+clean distclean: clean-lib
-+ rm -f $(OBJS) oauth-utils.o
++ rm -f $(OBJS) $(OBJS_STATIC) $(OBJS_SHLIB)
## src/interfaces/libpq-oauth/README (new) ##
@@
@@ src/interfaces/libpq-oauth/README (new)
+= Load-Time ABI =
+
+This module ABI is an internal implementation detail, so it's subject to change
-+across major releases; the name of the module (libpq-oauth-MAJOR) reflects this.
++across releases; the name of the module (libpq-oauth-MAJOR-MINOR) reflects this.
+The module exports the following symbols:
+
+- PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
@@ src/interfaces/libpq-oauth/README (new)
+
+At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
+libpq_gettext(), which must be injected by libpq using this initialization
-+function before the flow is run. It also relies on libpq to expose
-+conn->errorMessage, via the errmsg_impl.
++function before the flow is run.
+
-+This dependency injection is done to ensure that the module ABI is decoupled
-+from the internals of `struct pg_conn`. This way we can safely search the
-+standard dlopen() paths (e.g. RPATH, LD_LIBRARY_PATH, the SO cache) for an
-+implementation module to use, even if that module wasn't compiled at the same
-+time as libpq.
++It also relies on libpq to expose conn->errorMessage, via the errmsg_impl. This
++is done to decouple the module ABI from the offset of errorMessage, which can
++change positions depending on configure-time options. This way we can safely
++search the standard dlopen() paths (e.g. RPATH, LD_LIBRARY_PATH, the SO cache)
++for an implementation module to use, even if that module wasn't compiled at the
++same time as libpq.
+
+= Static Build =
+
+The static library libpq.a does not perform any dynamic loading. If the builtin
-+flow is enabled, the application is expected to link against libpq-oauth-*.a
++flow is enabled, the application is expected to link against libpq-oauth.a
+directly to provide the necessary symbols.
## src/interfaces/libpq-oauth/exports.txt (new) ##
@@ src/interfaces/libpq-oauth/meson.build (new)
+libpq_oauth_so_sources = files(
+ 'oauth-utils.c',
+)
++libpq_oauth_so_c_args = ['-DUSE_DYNAMIC_OAUTH']
+
+export_file = custom_target('libpq-oauth.exports',
+ kwargs: gen_export_kwargs,
@@ src/interfaces/libpq-oauth/meson.build (new)
+# port needs to be in include path due to pthread-win32.h
+libpq_oauth_inc = include_directories('.', '../libpq', '../../port')
+
-+# This is an internal module; we don't want an SONAME and therefore do not set
-+# SO_MAJOR_VERSION.
-+libpq_oauth_name = 'libpq-oauth-@0@'.format(pg_version_major)
-+
-+libpq_oauth_st = static_library(libpq_oauth_name,
++libpq_oauth_st = static_library('libpq-oauth',
+ libpq_oauth_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_pch: pch_postgres_fe_h,
@@ src/interfaces/libpq-oauth/meson.build (new)
+ kwargs: default_lib_args,
+)
+
++# This is an internal module; we don't want an SONAME and therefore do not set
++# SO_MAJOR_VERSION.
++libpq_oauth_name = 'libpq-oauth-@0@-@1@'.format(pg_version_major, pg_version_minor)
++
+libpq_oauth_so = shared_module(libpq_oauth_name,
+ libpq_oauth_sources + libpq_oauth_so_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
++ c_args: libpq_so_c_args,
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_shlib_code, libpq, libpq_oauth_deps],
+ link_depends: export_file,
@@ src/interfaces/libpq/fe-auth-oauth-curl.c => src/interfaces/libpq-oauth/oauth-cu
-#include "libpq-int.h"
#include "mb/pg_wchar.h"
+#include "oauth-curl.h"
++#ifdef USE_DYNAMIC_OAUTH
+#include "oauth-utils.h"
++#endif
/*
* It's generally prudent to set a maximum response size to buffer in memory,
@@ src/interfaces/libpq-oauth/oauth-curl.c: prompt_user(struct async_ctx *actx, PGc
if (!res)
{
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ {
+ fe_oauth_state *state = conn->sasl_state;
+ struct async_ctx *actx;
++ PQExpBuffer errbuf;
+
+ if (!initialize_curl(conn))
+ return PGRES_POLLING_FAILED;
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+
+ error_return:
+
++ /*
++ * For the dynamic module build, we can't safely rely on the offset of
++ * conn->errorMessage, since it depends on build options like USE_SSL et
++ * al. libpq gives us a translator function instead.
++ */
++#ifdef USE_DYNAMIC_OAUTH
++ errbuf = conn_errorMessage(conn);
++#else
++ errbuf = &conn->errorMessage;
++#endif
++
+ /*
+ * Assemble the three parts of our error: context, body, and detail. See
+ * also the documentation for struct async_ctx.
+ */
+ if (actx->errctx)
+ {
+- appendPQExpBufferStr(&conn->errorMessage,
+- libpq_gettext(actx->errctx));
+- appendPQExpBufferStr(&conn->errorMessage, ": ");
++ appendPQExpBufferStr(errbuf, libpq_gettext(actx->errctx));
++ appendPQExpBufferStr(errbuf, ": ");
+ }
+
+ if (PQExpBufferDataBroken(actx->errbuf))
+- appendPQExpBufferStr(&conn->errorMessage,
+- libpq_gettext("out of memory"));
++ appendPQExpBufferStr(errbuf, libpq_gettext("out of memory"));
+ else
+- appendPQExpBufferStr(&conn->errorMessage, actx->errbuf.data);
++ appendPQExpBufferStr(errbuf, actx->errbuf.data);
+
+ if (actx->curl_err[0])
+ {
+ size_t len;
+
+- appendPQExpBuffer(&conn->errorMessage,
+- " (libcurl: %s)", actx->curl_err);
++ appendPQExpBuffer(errbuf, " (libcurl: %s)", actx->curl_err);
+
+ /* Sometimes libcurl adds a newline to the error buffer. :( */
+- len = conn->errorMessage.len;
+- if (len >= 2 && conn->errorMessage.data[len - 2] == '\n')
++ len = errbuf->len;
++ if (len >= 2 && errbuf->data[len - 2] == '\n')
+ {
+- conn->errorMessage.data[len - 2] = ')';
+- conn->errorMessage.data[len - 1] = '\0';
+- conn->errorMessage.len--;
++ errbuf->data[len - 2] = ')';
++ errbuf->data[len - 1] = '\0';
++ errbuf->len--;
+ }
+ }
+
+- appendPQExpBufferChar(&conn->errorMessage, '\n');
++ appendPQExpBufferChar(errbuf, '\n');
+
+ return PGRES_POLLING_FAILED;
+ }
## src/interfaces/libpq-oauth/oauth-curl.h (new) ##
@@
@@ src/interfaces/libpq-oauth/oauth-utils.c (new)
+#include "libpq-int.h"
+#include "oauth-utils.h"
+
++#ifndef USE_DYNAMIC_OAUTH
++#error oauth-utils.c is not supported in static builds
++#endif
++
+static libpq_gettext_func libpq_gettext_impl;
-+static conn_errorMessage_func conn_errorMessage;
+
+pgthreadlock_t pg_g_threadlock;
++conn_errorMessage_func conn_errorMessage;
+
+/*-
+ * Initializes libpq-oauth by setting necessary callbacks.
@@ src/interfaces/libpq-oauth/oauth-utils.h (new)
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+
++/* Callback to safely obtain conn->errorMessage from a PGconn. */
++extern conn_errorMessage_func conn_errorMessage;
++
+/* Duplicated APIs, copied from libpq. */
+extern void libpq_append_conn_error(PGconn *conn, const char *fmt,...) pg_attribute_printf(2, 3);
+extern bool oauth_unsafe_debugging_enabled(void);
@@ src/interfaces/libpq/Makefile: ifeq ($(with_ssl),openssl)
+# libpq.so doesn't link against libcurl, but libpq.a needs libpq-oauth, and
+# libpq-oauth needs libcurl. Put both into *.private.
+PKG_CONFIG_REQUIRES_PRIVATE += libcurl
-+%.pc: override SHLIB_LINK_INTERNAL += -lpq-oauth-$(MAJORVERSION)
++%.pc: override SHLIB_LINK_INTERNAL += -lpq-oauth
+endif
+
all: all-lib libpq-refs-stamp
@@ src/interfaces/libpq/fe-auth-oauth.c: cleanup_user_oauth_flow(PGconn *conn)
+ */
+ const char *const module_name =
+#if defined(__darwin__)
-+ LIBDIR "/libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
++ LIBDIR "/libpq-oauth-" PG_MAJORVERSION "-" PG_MINORVERSION DLSUFFIX;
+#else
-+ "libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
++ "libpq-oauth-" PG_MAJORVERSION "-" PG_MINORVERSION DLSUFFIX;
+#endif
+
+ state->builtin_flow = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
@@ src/interfaces/libpq/fe-auth-oauth.c: setup_token_request(PGconn *conn, fe_oauth
-
-#else
- libpq_append_conn_error(conn, "no custom OAuth flows are available, and libpq was not built with libcurl support");
-+ libpq_append_conn_error(conn, "no custom OAuth flows are available, and the builtin flow is not installed");
++ libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
goto fail;
-
-#endif
@@ src/interfaces/libpq/meson.build: libpq = declare_dependency(
+ # libpq-oauth needs libcurl. Put both into *.private.
+ private_deps += [
+ libpq_oauth_deps,
-+ '-lpq-oauth-@0@'.format(pg_version_major),
++ '-lpq-oauth',
+ ]
+endif
+
@@ src/test/modules/oauth_validator/t/002_client.pl: if ($ENV{with_libcurl} ne 'yes
flags => ["--no-hook"],
expected_stderr =>
- qr/no custom OAuth flows are available, and libpq was not built with libcurl support/
-+ qr/no custom OAuth flows are available, and the builtin flow is not installed/
++ qr/no OAuth flows are available \(try installing the libpq-oauth package\)/
);
}
[application/x-patch] v8-0001-Add-minor-version-counterpart-to-PG_-MAJORVERSION.patch (3.5K, ../../CAOYmi+k9uN2OQ_sXjztZYPSpPbfGmkkv4yhtPhyjNQ6vy7SSfA@mail.gmail.com/3-v8-0001-Add-minor-version-counterpart-to-PG_-MAJORVERSION.patch)
download | inline diff:
From 5f87f11b18ea83615c342c832caace49bf7e3897 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Mon, 21 Apr 2025 13:43:08 -0700
Subject: [PATCH v8 1/2] Add minor-version counterpart to (PG_)MAJORVERSION
An upcoming commit will name a library, libpq-oauth, using the major and
minor versions. Make the minor version accessible from the Makefiles and
as a string constant in the code.
---
configure | 7 +++++++
configure.ac | 2 ++
meson.build | 1 +
src/Makefile.global.in | 1 +
src/include/pg_config.h.in | 3 +++
src/makefiles/meson.build | 1 +
6 files changed, 15 insertions(+)
diff --git a/configure b/configure
index 0936010718d..3d783793dfa 100755
--- a/configure
+++ b/configure
@@ -792,6 +792,7 @@ build_os
build_vendor
build_cpu
build
+PG_MINORVERSION
PG_MAJORVERSION
target_alias
host_alias
@@ -2877,6 +2878,12 @@ cat >>confdefs.h <<_ACEOF
_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PG_MINORVERSION "$PG_MINORVERSION"
+_ACEOF
+
+
cat >>confdefs.h <<_ACEOF
#define PG_MINORVERSION_NUM $PG_MINORVERSION
_ACEOF
diff --git a/configure.ac b/configure.ac
index 2a78cddd825..1cb3a0ff042 100644
--- a/configure.ac
+++ b/configure.ac
@@ -35,6 +35,8 @@ test -n "$PG_MINORVERSION" || PG_MINORVERSION=0
AC_SUBST(PG_MAJORVERSION)
AC_DEFINE_UNQUOTED(PG_MAJORVERSION, "$PG_MAJORVERSION", [PostgreSQL major version as a string])
AC_DEFINE_UNQUOTED(PG_MAJORVERSION_NUM, $PG_MAJORVERSION, [PostgreSQL major version number])
+AC_SUBST(PG_MINORVERSION)
+AC_DEFINE_UNQUOTED(PG_MINORVERSION, "$PG_MINORVERSION", [PostgreSQL minor version as a string])
AC_DEFINE_UNQUOTED(PG_MINORVERSION_NUM, $PG_MINORVERSION, [PostgreSQL minor version number])
PGAC_ARG_REQ(with, extra-version, [STRING], [append STRING to version],
diff --git a/meson.build b/meson.build
index a1516e54529..18423a7c13e 100644
--- a/meson.build
+++ b/meson.build
@@ -148,6 +148,7 @@ pg_version += get_option('extra_version')
cdata.set_quoted('PG_VERSION', pg_version)
cdata.set_quoted('PG_MAJORVERSION', pg_version_major.to_string())
cdata.set('PG_MAJORVERSION_NUM', pg_version_major)
+cdata.set_quoted('PG_MINORVERSION', pg_version_minor.to_string())
cdata.set('PG_MINORVERSION_NUM', pg_version_minor)
cdata.set('PG_VERSION_NUM', pg_version_num)
# PG_VERSION_STR is built later, it depends on compiler test results
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 6722fbdf365..54b4a07712e 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -40,6 +40,7 @@ maintainer-clean: distclean
# PostgreSQL version number
VERSION = @PACKAGE_VERSION@
MAJORVERSION = @PG_MAJORVERSION@
+MINORVERSION = @PG_MINORVERSION@
VERSION_NUM = @PG_VERSION_NUM@
PACKAGE_URL = @PACKAGE_URL@
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index c3cc9fa856d..4fe37d228c5 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -602,6 +602,9 @@
/* PostgreSQL major version number */
#undef PG_MAJORVERSION_NUM
+/* PostgreSQL minor version as a string */
+#undef PG_MINORVERSION
+
/* PostgreSQL minor version number */
#undef PG_MINORVERSION_NUM
diff --git a/src/makefiles/meson.build b/src/makefiles/meson.build
index 55da678ec27..e3adb5d8dc4 100644
--- a/src/makefiles/meson.build
+++ b/src/makefiles/meson.build
@@ -36,6 +36,7 @@ pgxs_kv = {
'PACKAGE_URL': pg_url,
'PACKAGE_VERSION': pg_version,
'PG_MAJORVERSION': pg_version_major,
+ 'PG_MINORVERSION': pg_version_minor,
'PG_VERSION_NUM': pg_version_num,
'configure_input': 'meson',
--
2.34.1
[application/x-patch] v8-0002-oauth-Move-the-builtin-flow-into-a-separate-modul.patch (56.7K, ../../CAOYmi+k9uN2OQ_sXjztZYPSpPbfGmkkv4yhtPhyjNQ6vy7SSfA@mail.gmail.com/4-v8-0002-oauth-Move-the-builtin-flow-into-a-separate-modul.patch)
download | inline diff:
From 4c9cc7f69afd8f59f98b38900e53fc199d4b4009 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Wed, 26 Mar 2025 10:55:28 -0700
Subject: [PATCH v8 2/2] oauth: Move the builtin flow into a separate module
The additional packaging footprint of the OAuth Curl dependency, as well
as the existence of libcurl in the address space even if OAuth isn't
ever used by a client, has raised some concerns. Split off this
dependency into a separate loadable module called libpq-oauth.
When configured using --with-libcurl, libpq.so searches for this new
module via dlopen(). End users may choose not to install the libpq-oauth
module, in which case the default flow is disabled.
For static applications using libpq.a, the libpq-oauth staticlib is a
mandatory link-time dependency for --with-libcurl builds. libpq.pc has
been updated accordingly.
The default flow relies on some libpq internals. Some of these can be
safely duplicated (such as the SIGPIPE handlers), but others need to be
shared between libpq and libpq-oauth for thread-safety. To avoid exporting
these internals to all libpq clients forever, these dependencies are
instead injected from the libpq side via an initialization function.
This also lets libpq communicate the offset of conn->errorMessage to
libpq-oauth, so that we can function without crashing if the module on
the search path came from a different build of Postgres.
This ABI is considered "private". The module has no SONAME or version
symlinks, and it's named libpq-oauth-<major>-<minor>.so to avoid mixing
and matching across Postgres versions, in case internal struct order
needs to change. (Future improvements may promote this "OAuth flow
plugin" to a first-class concept, at which point we would need a public
API to replace this anyway.)
Additionally, NLS support for error messages in b3f0be788a was
incomplete, because the new error macros weren't being scanned by
xgettext. Fix that now.
Per request from Tom Lane and Bruce Momjian. Based on an initial patch
by Daniel Gustafsson, who also contributed docs changes. The "bare"
dlopen() concept came from Thomas Munro. Many many people reviewed the
design and implementation; thank you!
Co-authored-by: Daniel Gustafsson <[email protected]>
Reviewed-by: Andres Freund <[email protected]>
Reviewed-by: Christoph Berg <[email protected]>
Reviewed-by: Jelte Fennema-Nio <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Wolfgang Walther <[email protected]>
Discussion: https://postgr.es/m/641687.1742360249%40sss.pgh.pa.us
---
config/programs.m4 | 17 +-
configure | 50 ++++-
configure.ac | 26 ++-
doc/src/sgml/installation.sgml | 8 +
doc/src/sgml/libpq.sgml | 30 ++-
meson.build | 32 ++-
src/Makefile.global.in | 3 +
src/interfaces/Makefile | 12 ++
src/interfaces/libpq-oauth/Makefile | 83 +++++++
src/interfaces/libpq-oauth/README | 43 ++++
src/interfaces/libpq-oauth/exports.txt | 4 +
src/interfaces/libpq-oauth/meson.build | 45 ++++
.../oauth-curl.c} | 99 +++++----
src/interfaces/libpq-oauth/oauth-curl.h | 24 +++
src/interfaces/libpq-oauth/oauth-utils.c | 202 ++++++++++++++++++
src/interfaces/libpq-oauth/oauth-utils.h | 38 ++++
src/interfaces/libpq/Makefile | 36 +++-
src/interfaces/libpq/exports.txt | 1 +
src/interfaces/libpq/fe-auth-oauth.c | 197 ++++++++++++++++-
src/interfaces/libpq/fe-auth-oauth.h | 5 +-
src/interfaces/libpq/meson.build | 25 ++-
src/interfaces/libpq/nls.mk | 12 +-
src/makefiles/meson.build | 2 +
src/test/modules/oauth_validator/meson.build | 2 +-
.../modules/oauth_validator/t/002_client.pl | 2 +-
25 files changed, 886 insertions(+), 112 deletions(-)
create mode 100644 src/interfaces/libpq-oauth/Makefile
create mode 100644 src/interfaces/libpq-oauth/README
create mode 100644 src/interfaces/libpq-oauth/exports.txt
create mode 100644 src/interfaces/libpq-oauth/meson.build
rename src/interfaces/{libpq/fe-auth-oauth-curl.c => libpq-oauth/oauth-curl.c} (97%)
create mode 100644 src/interfaces/libpq-oauth/oauth-curl.h
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.c
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.h
diff --git a/config/programs.m4 b/config/programs.m4
index 0a07feb37cc..0ad1e58b48d 100644
--- a/config/programs.m4
+++ b/config/programs.m4
@@ -286,9 +286,20 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
[
AC_CHECK_HEADER(curl/curl.h, [],
[AC_MSG_ERROR([header file <curl/curl.h> is required for --with-libcurl])])
- AC_CHECK_LIB(curl, curl_multi_init, [],
+ AC_CHECK_LIB(curl, curl_multi_init, [
+ AC_DEFINE([HAVE_LIBCURL], [1], [Define to 1 if you have the `curl' library (-lcurl).])
+ AC_SUBST(LIBCURL_LDLIBS, -lcurl)
+ ],
[AC_MSG_ERROR([library 'curl' does not provide curl_multi_init])])
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+ LIBS="$LIBCURL_LDLIBS $LIBS"
+
# Check to see whether the current platform supports threadsafe Curl
# initialization.
AC_CACHE_CHECK([for curl_global_init thread safety], [pgac_cv__libcurl_threadsafe_init],
@@ -338,4 +349,8 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
*** lookups. Rebuild libcurl with the AsynchDNS feature enabled in order
*** to use it with libpq.])
fi
+
+ CPPFLAGS=$pgac_save_CPPFLAGS
+ LDFLAGS=$pgac_save_LDFLAGS
+ LIBS=$pgac_save_LIBS
])# PGAC_CHECK_LIBCURL
diff --git a/configure b/configure
index 3d783793dfa..eedd18e6d9a 100755
--- a/configure
+++ b/configure
@@ -655,6 +655,7 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+LIBCURL_LDLIBS
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -711,6 +712,8 @@ with_libxml
LIBNUMA_LIBS
LIBNUMA_CFLAGS
with_libnuma
+LIBCURL_LDFLAGS
+LIBCURL_CPPFLAGS
LIBCURL_LIBS
LIBCURL_CFLAGS
with_libcurl
@@ -9060,19 +9063,27 @@ $as_echo "yes" >&6; }
fi
- # We only care about -I, -D, and -L switches;
- # note that -lcurl will be added by PGAC_CHECK_LIBCURL below.
+ # Curl's flags are kept separate from the standard CPPFLAGS/LDFLAGS. We use
+ # them only for libpq-oauth.
+ LIBCURL_CPPFLAGS=
+ LIBCURL_LDFLAGS=
+
+ # We only care about -I, -D, and -L switches. Note that -lcurl will be added
+ # to LIBCURL_LDLIBS by PGAC_CHECK_LIBCURL, below.
for pgac_option in $LIBCURL_CFLAGS; do
case $pgac_option in
- -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+ -I*|-D*) LIBCURL_CPPFLAGS="$LIBCURL_CPPFLAGS $pgac_option";;
esac
done
for pgac_option in $LIBCURL_LIBS; do
case $pgac_option in
- -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+ -L*) LIBCURL_LDFLAGS="$LIBCURL_LDFLAGS $pgac_option";;
esac
done
+
+
+
# OAuth requires python for testing
if test "$with_python" != yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: *** OAuth support tests require --with-python to run" >&5
@@ -12711,9 +12722,6 @@ fi
fi
-# XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
-# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
-# dependency on that platform?
if test "$with_libcurl" = yes ; then
ac_fn_c_check_header_mongrel "$LINENO" "curl/curl.h" "ac_cv_header_curl_curl_h" "$ac_includes_default"
@@ -12761,17 +12769,26 @@ fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_curl_curl_multi_init" >&5
$as_echo "$ac_cv_lib_curl_curl_multi_init" >&6; }
if test "x$ac_cv_lib_curl_curl_multi_init" = xyes; then :
- cat >>confdefs.h <<_ACEOF
-#define HAVE_LIBCURL 1
-_ACEOF
- LIBS="-lcurl $LIBS"
+
+$as_echo "#define HAVE_LIBCURL 1" >>confdefs.h
+
+ LIBCURL_LDLIBS=-lcurl
+
else
as_fn_error $? "library 'curl' does not provide curl_multi_init" "$LINENO" 5
fi
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+ LIBS="$LIBCURL_LDLIBS $LIBS"
+
# Check to see whether the current platform supports threadsafe Curl
# initialization.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_global_init thread safety" >&5
@@ -12875,6 +12892,10 @@ $as_echo "$pgac_cv__libcurl_async_dns" >&6; }
*** to use it with libpq." "$LINENO" 5
fi
+ CPPFLAGS=$pgac_save_CPPFLAGS
+ LDFLAGS=$pgac_save_LDFLAGS
+ LIBS=$pgac_save_LIBS
+
fi
if test "$with_gssapi" = yes ; then
@@ -14523,6 +14544,13 @@ done
fi
+if test "$with_libcurl" = yes ; then
+ # Error out early if this platform can't support libpq-oauth.
+ if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
+ as_fn_error $? "client OAuth is not supported on this platform" "$LINENO" 5
+ fi
+fi
+
##
## Types, structures, compiler characteristics
##
diff --git a/configure.ac b/configure.ac
index 1cb3a0ff042..7329b23d309 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1035,19 +1035,27 @@ if test "$with_libcurl" = yes ; then
# to explicitly set TLS 1.3 ciphersuites).
PKG_CHECK_MODULES(LIBCURL, [libcurl >= 7.61.0])
- # We only care about -I, -D, and -L switches;
- # note that -lcurl will be added by PGAC_CHECK_LIBCURL below.
+ # Curl's flags are kept separate from the standard CPPFLAGS/LDFLAGS. We use
+ # them only for libpq-oauth.
+ LIBCURL_CPPFLAGS=
+ LIBCURL_LDFLAGS=
+
+ # We only care about -I, -D, and -L switches. Note that -lcurl will be added
+ # to LIBCURL_LDLIBS by PGAC_CHECK_LIBCURL, below.
for pgac_option in $LIBCURL_CFLAGS; do
case $pgac_option in
- -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+ -I*|-D*) LIBCURL_CPPFLAGS="$LIBCURL_CPPFLAGS $pgac_option";;
esac
done
for pgac_option in $LIBCURL_LIBS; do
case $pgac_option in
- -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+ -L*) LIBCURL_LDFLAGS="$LIBCURL_LDFLAGS $pgac_option";;
esac
done
+ AC_SUBST(LIBCURL_CPPFLAGS)
+ AC_SUBST(LIBCURL_LDFLAGS)
+
# OAuth requires python for testing
if test "$with_python" != yes; then
AC_MSG_WARN([*** OAuth support tests require --with-python to run])
@@ -1356,9 +1364,6 @@ failure. It is possible the compiler isn't looking in the proper directory.
Use --without-zlib to disable zlib support.])])
fi
-# XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
-# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
-# dependency on that platform?
if test "$with_libcurl" = yes ; then
PGAC_CHECK_LIBCURL
fi
@@ -1656,6 +1661,13 @@ if test "$PORTNAME" = "win32" ; then
AC_CHECK_HEADERS(crtdefs.h)
fi
+if test "$with_libcurl" = yes ; then
+ # Error out early if this platform can't support libpq-oauth.
+ if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
+ AC_MSG_ERROR([client OAuth is not supported on this platform])
+ fi
+fi
+
##
## Types, structures, compiler characteristics
##
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 077bcc20759..d928b103d22 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -313,6 +313,14 @@
</para>
</listitem>
+ <listitem>
+ <para>
+ You need <productname>Curl</productname> to build an optional module
+ which implements the <link linkend="libpq-oauth">OAuth Device
+ Authorization flow</link> for client applications.
+ </para>
+ </listitem>
+
<listitem>
<para>
You need <productname>LZ4</productname>, if you want to support
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 3be66789ba7..cd748902f4d 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -10226,15 +10226,20 @@ void PQinitSSL(int do_ssl);
<title>OAuth Support</title>
<para>
- libpq implements support for the OAuth v2 Device Authorization client flow,
+ <application>libpq</application> implements support for the OAuth v2 Device Authorization client flow,
documented in
<ulink url="https://datatracker.ietf.org/doc/html/rfc8628">RFC 8628</ulink>,
- which it will attempt to use by default if the server
+ as an optional module. See the <link linkend="configure-option-with-libcurl">
+ installation documentation</link> for information on how to enable support
+ for Device Authorization as a builtin flow.
+ </para>
+ <para>
+ When support is enabled and the optional module installed, <application>libpq</application>
+ will use the builtin flow by default if the server
<link linkend="auth-oauth">requests a bearer token</link> during
authentication. This flow can be utilized even if the system running the
client application does not have a usable web browser, for example when
- running a client via <application>SSH</application>. Client applications may implement their own flows
- instead; see <xref linkend="libpq-oauth-authdata-hooks"/>.
+ running a client via <acronym>SSH</acronym>.
</para>
<para>
The builtin flow will, by default, print a URL to visit and a user code to
@@ -10251,6 +10256,11 @@ Visit https://example.com/device and enter the code: ABCD-EFGH
they match expectations, before continuing. Permissions should not be given
to untrusted third parties.
</para>
+ <para>
+ Client applications may implement their own flows to customize interaction
+ and integration with applications. See <xref linkend="libpq-oauth-authdata-hooks"/>
+ for more information on how add a custom flow to <application>libpq</application>.
+ </para>
<para>
For an OAuth client flow to be usable, the connection string must at minimum
contain <xref linkend="libpq-connect-oauth-issuer"/> and
@@ -10366,7 +10376,9 @@ typedef struct _PGpromptOAuthDevice
</synopsis>
</para>
<para>
- The OAuth Device Authorization flow included in <application>libpq</application>
+ The OAuth Device Authorization flow which
+ <link linkend="configure-option-with-libcurl">can be included</link>
+ in <application>libpq</application>
requires the end user to visit a URL with a browser, then enter a code
which permits <application>libpq</application> to connect to the server
on their behalf. The default prompt simply prints the
@@ -10378,7 +10390,8 @@ typedef struct _PGpromptOAuthDevice
This callback is only invoked during the builtin device
authorization flow. If the application installs a
<link linkend="libpq-oauth-authdata-oauth-bearer-token">custom OAuth
- flow</link>, this authdata type will not be used.
+ flow</link>, or <application>libpq</application> was not built with
+ support for the builtin flow, this authdata type will not be used.
</para>
<para>
If a non-NULL <structfield>verification_uri_complete</structfield> is
@@ -10400,8 +10413,9 @@ typedef struct _PGpromptOAuthDevice
</term>
<listitem>
<para>
- Replaces the entire OAuth flow with a custom implementation. The hook
- should either directly return a Bearer token for the current
+ Adds a custom implementation of a flow, replacing the builtin flow if
+ it is <link linkend="configure-option-with-libcurl">installed</link>.
+ The hook should either directly return a Bearer token for the current
user/issuer/scope combination, if one is available without blocking, or
else set up an asynchronous callback to retrieve one.
</para>
diff --git a/meson.build b/meson.build
index 18423a7c13e..6787683ca27 100644
--- a/meson.build
+++ b/meson.build
@@ -107,6 +107,7 @@ os_deps = []
backend_both_deps = []
backend_deps = []
libpq_deps = []
+libpq_oauth_deps = []
pg_sysroot = ''
@@ -861,13 +862,13 @@ endif
###############################################################
libcurlopt = get_option('libcurl')
+oauth_flow_supported = false
+
if not libcurlopt.disabled()
# Check for libcurl 7.61.0 or higher (corresponding to RHEL8 and the ability
# to explicitly set TLS 1.3 ciphersuites).
libcurl = dependency('libcurl', version: '>= 7.61.0', required: libcurlopt)
if libcurl.found()
- cdata.set('USE_LIBCURL', 1)
-
# Check to see whether the current platform supports thread-safe Curl
# initialization.
libcurl_threadsafe_init = false
@@ -939,6 +940,22 @@ if not libcurlopt.disabled()
endif
endif
+ # Check that the current platform supports our builtin flow. This requires
+ # libcurl and one of either epoll or kqueue.
+ oauth_flow_supported = (
+ libcurl.found()
+ and (cc.check_header('sys/event.h', required: false,
+ args: test_c_args, include_directories: postgres_inc)
+ or cc.check_header('sys/epoll.h', required: false,
+ args: test_c_args, include_directories: postgres_inc))
+ )
+
+ if oauth_flow_supported
+ cdata.set('USE_LIBCURL', 1)
+ elif libcurlopt.enabled()
+ error('client OAuth is not supported on this platform')
+ endif
+
else
libcurl = not_found_dep
endif
@@ -3273,17 +3290,18 @@ libpq_deps += [
gssapi,
ldap_r,
- # XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
- # during gss_acquire_cred(). This is possibly related to Curl's Heimdal
- # dependency on that platform?
- libcurl,
libintl,
ssl,
]
+libpq_oauth_deps += [
+ libcurl,
+]
+
subdir('src/interfaces/libpq')
-# fe_utils depends on libpq
+# fe_utils and libpq-oauth depends on libpq
subdir('src/fe_utils')
+subdir('src/interfaces/libpq-oauth')
# for frontend binaries
frontend_code = declare_dependency(
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 54b4a07712e..f4caece04df 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -348,6 +348,9 @@ perl_embed_ldflags = @perl_embed_ldflags@
AWK = @AWK@
LN_S = @LN_S@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBCURL_LDFLAGS = @LIBCURL_LDFLAGS@
+LIBCURL_LDLIBS = @LIBCURL_LDLIBS@
MSGFMT = @MSGFMT@
MSGFMT_FLAGS = @MSGFMT_FLAGS@
MSGMERGE = @MSGMERGE@
diff --git a/src/interfaces/Makefile b/src/interfaces/Makefile
index 7d56b29d28f..e6822caa206 100644
--- a/src/interfaces/Makefile
+++ b/src/interfaces/Makefile
@@ -14,7 +14,19 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = libpq ecpg
+ifeq ($(with_libcurl), yes)
+SUBDIRS += libpq-oauth
+else
+ALWAYS_SUBDIRS += libpq-oauth
+endif
+
$(recurse)
+$(recurse_always)
all-ecpg-recurse: all-libpq-recurse
install-ecpg-recurse: install-libpq-recurse
+
+ifeq ($(with_libcurl), yes)
+all-libpq-oauth-recurse: all-libpq-recurse
+install-libpq-oauth-recurse: install-libpq-recurse
+endif
diff --git a/src/interfaces/libpq-oauth/Makefile b/src/interfaces/libpq-oauth/Makefile
new file mode 100644
index 00000000000..98acaff1a3b
--- /dev/null
+++ b/src/interfaces/libpq-oauth/Makefile
@@ -0,0 +1,83 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for libpq-oauth
+#
+# Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/interfaces/libpq-oauth/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/interfaces/libpq-oauth
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+PGFILEDESC = "libpq-oauth - device authorization OAuth support"
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
+NAME = pq-oauth-$(MAJORVERSION)-$(MINORVERSION)
+
+# Force the name "libpq-oauth" for both the static and shared libraries. The
+# staticlib doesn't need version information in its name.
+override shlib := lib$(NAME)$(DLSUFFIX)
+override stlib := libpq-oauth.a
+
+override CPPFLAGS := -I$(libpq_srcdir) -I$(top_builddir)/src/port $(LIBCURL_CPPFLAGS) $(CPPFLAGS)
+
+OBJS = \
+ $(WIN32RES)
+
+OBJS_STATIC = oauth-curl.o
+
+# The shared library needs additional glue symbols.
+OBJS_SHLIB = \
+ oauth-curl_shlib.o \
+ oauth-utils.o \
+
+oauth-utils.o: override CPPFLAGS += -DUSE_DYNAMIC_OAUTH
+oauth-curl_shlib.o: override CPPFLAGS_SHLIB += -DUSE_DYNAMIC_OAUTH
+
+# Add shlib-/stlib-specific objects.
+$(shlib): override OBJS += $(OBJS_SHLIB)
+$(shlib): $(OBJS_SHLIB)
+
+$(stlib): override OBJS += $(OBJS_STATIC)
+$(stlib): $(OBJS_STATIC)
+
+SHLIB_LINK_INTERNAL = $(libpq_pgport_shlib)
+SHLIB_LINK = $(LIBCURL_LDFLAGS) $(LIBCURL_LDLIBS)
+SHLIB_PREREQS = submake-libpq
+SHLIB_EXPORTS = exports.txt
+
+# Disable -bundle_loader on macOS.
+BE_DLLLIBS =
+
+# By default, a library without an SONAME doesn't get a static library, so we
+# add it to the build explicitly.
+all: all-lib all-static-lib
+
+# Shared library stuff
+include $(top_srcdir)/src/Makefile.shlib
+
+# Use src/common/Makefile's trick for tracking dependencies of shlib-specific
+# objects.
+%_shlib.o: %.c %.o
+ $(CC) $(CFLAGS) $(CFLAGS_SL) $(CPPFLAGS) $(CPPFLAGS_SHLIB) -c $< -o $@
+
+# Ignore the standard rules for SONAME-less installation; we want both the
+# static and shared libraries to go into libdir.
+install: all installdirs $(stlib) $(shlib)
+ $(INSTALL_SHLIB) $(shlib) '$(DESTDIR)$(libdir)/$(shlib)'
+ $(INSTALL_STLIB) $(stlib) '$(DESTDIR)$(libdir)/$(stlib)'
+
+installdirs:
+ $(MKDIR_P) '$(DESTDIR)$(libdir)'
+
+uninstall:
+ rm -f '$(DESTDIR)$(libdir)/$(stlib)'
+ rm -f '$(DESTDIR)$(libdir)/$(shlib)'
+
+clean distclean: clean-lib
+ rm -f $(OBJS) $(OBJS_STATIC) $(OBJS_SHLIB)
diff --git a/src/interfaces/libpq-oauth/README b/src/interfaces/libpq-oauth/README
new file mode 100644
index 00000000000..fdc1320d152
--- /dev/null
+++ b/src/interfaces/libpq-oauth/README
@@ -0,0 +1,43 @@
+libpq-oauth is an optional module implementing the Device Authorization flow for
+OAuth clients (RFC 8628). It was originally developed as part of libpq core and
+later split out as its own shared library in order to isolate its dependency on
+libcurl. (End users who don't want the Curl dependency can simply choose not to
+install this module.)
+
+If a connection string allows the use of OAuth, and the server asks for it, and
+a libpq client has not installed its own custom OAuth flow, libpq will attempt
+to delay-load this module using dlopen() and the following ABI. Failure to load
+results in a failed connection.
+
+= Load-Time ABI =
+
+This module ABI is an internal implementation detail, so it's subject to change
+across releases; the name of the module (libpq-oauth-MAJOR-MINOR) reflects this.
+The module exports the following symbols:
+
+- PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+- void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+pg_fe_run_oauth_flow and pg_fe_cleanup_oauth_flow are implementations of
+conn->async_auth and conn->cleanup_async_auth, respectively.
+
+- void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+
+At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
+libpq_gettext(), which must be injected by libpq using this initialization
+function before the flow is run.
+
+It also relies on libpq to expose conn->errorMessage, via the errmsg_impl. This
+is done to decouple the module ABI from the offset of errorMessage, which can
+change positions depending on configure-time options. This way we can safely
+search the standard dlopen() paths (e.g. RPATH, LD_LIBRARY_PATH, the SO cache)
+for an implementation module to use, even if that module wasn't compiled at the
+same time as libpq.
+
+= Static Build =
+
+The static library libpq.a does not perform any dynamic loading. If the builtin
+flow is enabled, the application is expected to link against libpq-oauth.a
+directly to provide the necessary symbols.
diff --git a/src/interfaces/libpq-oauth/exports.txt b/src/interfaces/libpq-oauth/exports.txt
new file mode 100644
index 00000000000..6891a83dbf9
--- /dev/null
+++ b/src/interfaces/libpq-oauth/exports.txt
@@ -0,0 +1,4 @@
+# src/interfaces/libpq-oauth/exports.txt
+libpq_oauth_init 1
+pg_fe_run_oauth_flow 2
+pg_fe_cleanup_oauth_flow 3
diff --git a/src/interfaces/libpq-oauth/meson.build b/src/interfaces/libpq-oauth/meson.build
new file mode 100644
index 00000000000..d97f893178a
--- /dev/null
+++ b/src/interfaces/libpq-oauth/meson.build
@@ -0,0 +1,45 @@
+# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+
+if not oauth_flow_supported
+ subdir_done()
+endif
+
+libpq_oauth_sources = files(
+ 'oauth-curl.c',
+)
+
+# The shared library needs additional glue symbols.
+libpq_oauth_so_sources = files(
+ 'oauth-utils.c',
+)
+libpq_oauth_so_c_args = ['-DUSE_DYNAMIC_OAUTH']
+
+export_file = custom_target('libpq-oauth.exports',
+ kwargs: gen_export_kwargs,
+)
+
+# port needs to be in include path due to pthread-win32.h
+libpq_oauth_inc = include_directories('.', '../libpq', '../../port')
+
+libpq_oauth_st = static_library('libpq-oauth',
+ libpq_oauth_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_stlib_code, libpq_oauth_deps],
+ kwargs: default_lib_args,
+)
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
+libpq_oauth_name = 'libpq-oauth-@0@-@1@'.format(pg_version_major, pg_version_minor)
+
+libpq_oauth_so = shared_module(libpq_oauth_name,
+ libpq_oauth_sources + libpq_oauth_so_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_args: libpq_so_c_args,
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_shlib_code, libpq, libpq_oauth_deps],
+ link_depends: export_file,
+ link_args: export_fmt.format(export_file.full_path()),
+ kwargs: default_lib_args,
+)
diff --git a/src/interfaces/libpq/fe-auth-oauth-curl.c b/src/interfaces/libpq-oauth/oauth-curl.c
similarity index 97%
rename from src/interfaces/libpq/fe-auth-oauth-curl.c
rename to src/interfaces/libpq-oauth/oauth-curl.c
index c195e00cd28..3239315d952 100644
--- a/src/interfaces/libpq/fe-auth-oauth-curl.c
+++ b/src/interfaces/libpq-oauth/oauth-curl.c
@@ -1,6 +1,6 @@
/*-------------------------------------------------------------------------
*
- * fe-auth-oauth-curl.c
+ * oauth-curl.c
* The libcurl implementation of OAuth/OIDC authentication, using the
* OAuth Device Authorization Grant (RFC 8628).
*
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * src/interfaces/libpq/fe-auth-oauth-curl.c
+ * src/interfaces/libpq-oauth/oauth-curl.c
*
*-------------------------------------------------------------------------
*/
@@ -17,20 +17,25 @@
#include <curl/curl.h>
#include <math.h>
-#ifdef HAVE_SYS_EPOLL_H
+#include <unistd.h>
+
+#if defined(HAVE_SYS_EPOLL_H)
#include <sys/epoll.h>
#include <sys/timerfd.h>
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
#include <sys/event.h>
+#else
+#error libpq-oauth is not supported on this platform
#endif
-#include <unistd.h>
#include "common/jsonapi.h"
#include "fe-auth.h"
#include "fe-auth-oauth.h"
-#include "libpq-int.h"
#include "mb/pg_wchar.h"
+#include "oauth-curl.h"
+#ifdef USE_DYNAMIC_OAUTH
+#include "oauth-utils.h"
+#endif
/*
* It's generally prudent to set a maximum response size to buffer in memory,
@@ -1110,7 +1115,7 @@ parse_access_token(struct async_ctx *actx, struct token *tok)
static bool
setup_multiplexer(struct async_ctx *actx)
{
-#ifdef HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct epoll_event ev = {.events = EPOLLIN};
actx->mux = epoll_create1(EPOLL_CLOEXEC);
@@ -1134,8 +1139,7 @@ setup_multiplexer(struct async_ctx *actx)
}
return true;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
actx->mux = kqueue();
if (actx->mux < 0)
{
@@ -1158,10 +1162,9 @@ setup_multiplexer(struct async_ctx *actx)
}
return true;
+#else
+#error setup_multiplexer is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support the Device Authorization flow on this platform");
- return false;
}
/*
@@ -1174,7 +1177,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
{
struct async_ctx *actx = ctx;
-#ifdef HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct epoll_event ev = {0};
int res;
int op = EPOLL_CTL_ADD;
@@ -1230,8 +1233,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
}
return 0;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
struct kevent ev[2] = {0};
struct kevent ev_out[2];
struct timespec timeout = {0};
@@ -1312,10 +1314,9 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
}
return 0;
+#else
+#error register_socket is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support multiplexer sockets on this platform");
- return -1;
}
/*
@@ -1334,7 +1335,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
static bool
set_timer(struct async_ctx *actx, long timeout)
{
-#if HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct itimerspec spec = {0};
if (timeout < 0)
@@ -1363,8 +1364,7 @@ set_timer(struct async_ctx *actx, long timeout)
}
return true;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
struct kevent ev;
#ifdef __NetBSD__
@@ -1419,10 +1419,9 @@ set_timer(struct async_ctx *actx, long timeout)
}
return true;
+#else
+#error set_timer is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support timers on this platform");
- return false;
}
/*
@@ -1433,7 +1432,7 @@ set_timer(struct async_ctx *actx, long timeout)
static int
timer_expired(struct async_ctx *actx)
{
-#if HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct itimerspec spec = {0};
if (timerfd_gettime(actx->timerfd, &spec) < 0)
@@ -1453,8 +1452,7 @@ timer_expired(struct async_ctx *actx)
/* If the remaining time to expiration is zero, we're done. */
return (spec.it_value.tv_sec == 0
&& spec.it_value.tv_nsec == 0);
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
int res;
/* Is the timer queue ready? */
@@ -1466,10 +1464,9 @@ timer_expired(struct async_ctx *actx)
}
return (res > 0);
+#else
+#error timer_expired is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support timers on this platform");
- return -1;
}
/*
@@ -2487,8 +2484,9 @@ prompt_user(struct async_ctx *actx, PGconn *conn)
.verification_uri_complete = actx->authz.verification_uri_complete,
.expires_in = actx->authz.expires_in,
};
+ PQauthDataHook_type hook = PQgetAuthDataHook();
- res = PQauthDataHook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
+ res = hook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
if (!res)
{
@@ -2635,6 +2633,7 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
{
fe_oauth_state *state = conn->sasl_state;
struct async_ctx *actx;
+ PQExpBuffer errbuf;
if (!initialize_curl(conn))
return PGRES_POLLING_FAILED;
@@ -2825,41 +2824,49 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
error_return:
+ /*
+ * For the dynamic module build, we can't safely rely on the offset of
+ * conn->errorMessage, since it depends on build options like USE_SSL et
+ * al. libpq gives us a translator function instead.
+ */
+#ifdef USE_DYNAMIC_OAUTH
+ errbuf = conn_errorMessage(conn);
+#else
+ errbuf = &conn->errorMessage;
+#endif
+
/*
* Assemble the three parts of our error: context, body, and detail. See
* also the documentation for struct async_ctx.
*/
if (actx->errctx)
{
- appendPQExpBufferStr(&conn->errorMessage,
- libpq_gettext(actx->errctx));
- appendPQExpBufferStr(&conn->errorMessage, ": ");
+ appendPQExpBufferStr(errbuf, libpq_gettext(actx->errctx));
+ appendPQExpBufferStr(errbuf, ": ");
}
if (PQExpBufferDataBroken(actx->errbuf))
- appendPQExpBufferStr(&conn->errorMessage,
- libpq_gettext("out of memory"));
+ appendPQExpBufferStr(errbuf, libpq_gettext("out of memory"));
else
- appendPQExpBufferStr(&conn->errorMessage, actx->errbuf.data);
+ appendPQExpBufferStr(errbuf, actx->errbuf.data);
if (actx->curl_err[0])
{
size_t len;
- appendPQExpBuffer(&conn->errorMessage,
- " (libcurl: %s)", actx->curl_err);
+ appendPQExpBuffer(errbuf, " (libcurl: %s)", actx->curl_err);
/* Sometimes libcurl adds a newline to the error buffer. :( */
- len = conn->errorMessage.len;
- if (len >= 2 && conn->errorMessage.data[len - 2] == '\n')
+ len = errbuf->len;
+ if (len >= 2 && errbuf->data[len - 2] == '\n')
{
- conn->errorMessage.data[len - 2] = ')';
- conn->errorMessage.data[len - 1] = '\0';
- conn->errorMessage.len--;
+ errbuf->data[len - 2] = ')';
+ errbuf->data[len - 1] = '\0';
+ errbuf->len--;
}
}
- appendPQExpBufferChar(&conn->errorMessage, '\n');
+ appendPQExpBufferChar(errbuf, '\n');
return PGRES_POLLING_FAILED;
}
diff --git a/src/interfaces/libpq-oauth/oauth-curl.h b/src/interfaces/libpq-oauth/oauth-curl.h
new file mode 100644
index 00000000000..248d0424ad0
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-curl.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-curl.h
+ *
+ * Definitions for OAuth Device Authorization module
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-curl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_CURL_H
+#define OAUTH_CURL_H
+
+#include "libpq-fe.h"
+
+/* Exported async-auth callbacks. */
+extern PGDLLEXPORT PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+extern PGDLLEXPORT void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+#endif /* OAUTH_CURL_H */
diff --git a/src/interfaces/libpq-oauth/oauth-utils.c b/src/interfaces/libpq-oauth/oauth-utils.c
new file mode 100644
index 00000000000..1f85a6b0479
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.c
@@ -0,0 +1,202 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.c
+ *
+ * "Glue" helpers providing a copy of some internal APIs from libpq. At
+ * some point in the future, we might be able to deduplicate.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq-oauth/oauth-utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+
+#include "libpq-int.h"
+#include "oauth-utils.h"
+
+#ifndef USE_DYNAMIC_OAUTH
+#error oauth-utils.c is not supported in static builds
+#endif
+
+static libpq_gettext_func libpq_gettext_impl;
+
+pgthreadlock_t pg_g_threadlock;
+conn_errorMessage_func conn_errorMessage;
+
+/*-
+ * Initializes libpq-oauth by setting necessary callbacks.
+ *
+ * The current implementation relies on the following private implementation
+ * details of libpq:
+ *
+ * - pg_g_threadlock: protects libcurl initialization if the underlying Curl
+ * installation is not threadsafe
+ *
+ * - libpq_gettext: translates error messages using libpq's message domain
+ *
+ * - conn->errorMessage: holds translated errors for the connection. This is
+ * handled through a translation shim, which avoids either depending on the
+ * offset of the errorMessage in PGconn, or needing to export the variadic
+ * libpq_append_conn_error().
+ */
+void
+libpq_oauth_init(pgthreadlock_t threadlock_impl,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl)
+{
+ pg_g_threadlock = threadlock_impl;
+ libpq_gettext_impl = gettext_impl;
+ conn_errorMessage = errmsg_impl;
+}
+
+/*
+ * Append a formatted string to the error message buffer of the given
+ * connection, after translating it. This is a copy of libpq's internal API.
+ */
+void
+libpq_append_conn_error(PGconn *conn, const char *fmt,...)
+{
+ int save_errno = errno;
+ bool done;
+ va_list args;
+ PQExpBuffer errorMessage = conn_errorMessage(conn);
+
+ Assert(fmt[strlen(fmt) - 1] != '\n');
+
+ if (PQExpBufferBroken(errorMessage))
+ return; /* already failed */
+
+ /* Loop in case we have to retry after enlarging the buffer. */
+ do
+ {
+ errno = save_errno;
+ va_start(args, fmt);
+ done = appendPQExpBufferVA(errorMessage, libpq_gettext(fmt), args);
+ va_end(args);
+ } while (!done);
+
+ appendPQExpBufferChar(errorMessage, '\n');
+}
+
+#ifdef ENABLE_NLS
+
+/*
+ * A shim that defers to the actual libpq_gettext().
+ */
+char *
+libpq_gettext(const char *msgid)
+{
+ if (!libpq_gettext_impl)
+ {
+ /*
+ * Possible if the libpq build didn't enable NLS but the libpq-oauth
+ * build did. That's an odd mismatch, but we can handle it.
+ *
+ * Note that callers of libpq_gettext() have to treat the return value
+ * as if it were const, because builds without NLS simply pass through
+ * their argument.
+ */
+ return unconstify(char *, msgid);
+ }
+
+ return libpq_gettext_impl(msgid);
+}
+
+#endif /* ENABLE_NLS */
+
+/*
+ * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
+ */
+bool
+oauth_unsafe_debugging_enabled(void)
+{
+ const char *env = getenv("PGOAUTHDEBUG");
+
+ return (env && strcmp(env, "UNSAFE") == 0);
+}
+
+/*
+ * Duplicate SOCK_ERRNO* definitions from libpq-int.h, for use by
+ * pq_block/reset_sigpipe().
+ */
+#ifdef WIN32
+#define SOCK_ERRNO (WSAGetLastError())
+#define SOCK_ERRNO_SET(e) WSASetLastError(e)
+#else
+#define SOCK_ERRNO errno
+#define SOCK_ERRNO_SET(e) (errno = (e))
+#endif
+
+/*
+ * Block SIGPIPE for this thread. This is a copy of libpq's internal API.
+ */
+int
+pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
+{
+ sigset_t sigpipe_sigset;
+ sigset_t sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ /* Block SIGPIPE and save previous mask for later reset */
+ SOCK_ERRNO_SET(pthread_sigmask(SIG_BLOCK, &sigpipe_sigset, osigset));
+ if (SOCK_ERRNO)
+ return -1;
+
+ /* We can have a pending SIGPIPE only if it was blocked before */
+ if (sigismember(osigset, SIGPIPE))
+ {
+ /* Is there a pending SIGPIPE? */
+ if (sigpending(&sigset) != 0)
+ return -1;
+
+ if (sigismember(&sigset, SIGPIPE))
+ *sigpipe_pending = true;
+ else
+ *sigpipe_pending = false;
+ }
+ else
+ *sigpipe_pending = false;
+
+ return 0;
+}
+
+/*
+ * Discard any pending SIGPIPE and reset the signal mask. This is a copy of
+ * libpq's internal API.
+ */
+void
+pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
+{
+ int save_errno = SOCK_ERRNO;
+ int signo;
+ sigset_t sigset;
+
+ /* Clear SIGPIPE only if none was pending */
+ if (got_epipe && !sigpipe_pending)
+ {
+ if (sigpending(&sigset) == 0 &&
+ sigismember(&sigset, SIGPIPE))
+ {
+ sigset_t sigpipe_sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ sigwait(&sigpipe_sigset, &signo);
+ }
+ }
+
+ /* Restore saved block mask */
+ pthread_sigmask(SIG_SETMASK, osigset, NULL);
+
+ SOCK_ERRNO_SET(save_errno);
+}
diff --git a/src/interfaces/libpq-oauth/oauth-utils.h b/src/interfaces/libpq-oauth/oauth-utils.h
new file mode 100644
index 00000000000..e2a9d01237d
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.h
@@ -0,0 +1,38 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.h
+ *
+ * Definitions providing missing libpq internal APIs
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_UTILS_H
+#define OAUTH_UTILS_H
+
+#include "libpq-fe.h"
+#include "pqexpbuffer.h"
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/* Initializes libpq-oauth. */
+extern PGDLLEXPORT void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+
+/* Callback to safely obtain conn->errorMessage from a PGconn. */
+extern conn_errorMessage_func conn_errorMessage;
+
+/* Duplicated APIs, copied from libpq. */
+extern void libpq_append_conn_error(PGconn *conn, const char *fmt,...) pg_attribute_printf(2, 3);
+extern bool oauth_unsafe_debugging_enabled(void);
+extern int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
+extern void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe);
+
+#endif /* OAUTH_UTILS_H */
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 90b0b65db6f..c6fe5fec7f6 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -31,7 +31,6 @@ endif
OBJS = \
$(WIN32RES) \
- fe-auth-oauth.o \
fe-auth-scram.o \
fe-cancel.o \
fe-connect.o \
@@ -64,9 +63,11 @@ OBJS += \
fe-secure-gssapi.o
endif
-ifeq ($(with_libcurl),yes)
-OBJS += fe-auth-oauth-curl.o
-endif
+# The OAuth implementation differs depending on the type of library being built.
+OBJS_STATIC = fe-auth-oauth.o
+
+fe-auth-oauth_shlib.o: override CPPFLAGS_SHLIB += -DUSE_DYNAMIC_OAUTH
+OBJS_SHLIB = fe-auth-oauth_shlib.o
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
@@ -86,7 +87,7 @@ endif
# that are built correctly for use in a shlib.
SHLIB_LINK_INTERNAL = -lpgcommon_shlib -lpgport_shlib
ifneq ($(PORTNAME), win32)
-SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lcurl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
+SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
else
SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi32 -lssl -lsocket -lnsl -lresolv -lintl -lm $(PTHREAD_LIBS), $(LIBS)) $(LDAP_LIBS_FE)
endif
@@ -101,12 +102,26 @@ ifeq ($(with_ssl),openssl)
PKG_CONFIG_REQUIRES_PRIVATE = libssl, libcrypto
endif
+ifeq ($(with_libcurl),yes)
+# libpq.so doesn't link against libcurl, but libpq.a needs libpq-oauth, and
+# libpq-oauth needs libcurl. Put both into *.private.
+PKG_CONFIG_REQUIRES_PRIVATE += libcurl
+%.pc: override SHLIB_LINK_INTERNAL += -lpq-oauth
+endif
+
all: all-lib libpq-refs-stamp
# Shared library stuff
include $(top_srcdir)/src/Makefile.shlib
backend_src = $(top_srcdir)/src/backend
+# Add shlib-/stlib-specific objects.
+$(shlib): override OBJS += $(OBJS_SHLIB)
+$(shlib): $(OBJS_SHLIB)
+
+$(stlib): override OBJS += $(OBJS_STATIC)
+$(stlib): $(OBJS_STATIC)
+
# Check for functions that libpq must not call, currently just exit().
# (Ideally we'd reject abort() too, but there are various scenarios where
# build toolchains insert abort() calls, e.g. to implement assert().)
@@ -115,8 +130,6 @@ backend_src = $(top_srcdir)/src/backend
# which seems to insert references to that even in pure C code. Excluding
# __tsan_func_exit is necessary when using ThreadSanitizer data race detector
# which use this function for instrumentation of function exit.
-# libcurl registers an exit handler in the memory debugging code when running
-# with LeakSanitizer.
# Skip the test when profiling, as gcc may insert exit() calls for that.
# Also skip the test on platforms where libpq infrastructure may be provided
# by statically-linked libraries, as we can't expect them to honor this
@@ -124,7 +137,7 @@ backend_src = $(top_srcdir)/src/backend
libpq-refs-stamp: $(shlib)
ifneq ($(enable_coverage), yes)
ifeq (,$(filter solaris,$(PORTNAME)))
- @if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit -e _atexit | grep exit; then \
+ @if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit | grep exit; then \
echo 'libpq must not be calling any function which invokes exit'; exit 1; \
fi
endif
@@ -138,6 +151,11 @@ fe-misc.o: fe-misc.c $(top_builddir)/src/port/pg_config_paths.h
$(top_builddir)/src/port/pg_config_paths.h:
$(MAKE) -C $(top_builddir)/src/port pg_config_paths.h
+# Use src/common/Makefile's trick for tracking dependencies of shlib-specific
+# objects.
+%_shlib.o: %.c %.o
+ $(CC) $(CFLAGS) $(CFLAGS_SL) $(CPPFLAGS) $(CPPFLAGS_SHLIB) -c $< -o $@
+
install: all installdirs install-lib
$(INSTALL_DATA) $(srcdir)/libpq-fe.h '$(DESTDIR)$(includedir)'
$(INSTALL_DATA) $(srcdir)/libpq-events.h '$(DESTDIR)$(includedir)'
@@ -171,6 +189,6 @@ uninstall: uninstall-lib
clean distclean: clean-lib
$(MAKE) -C test $@
rm -rf tmp_check
- rm -f $(OBJS) pthread.h libpq-refs-stamp
+ rm -f $(OBJS) $(OBJS_SHLIB) $(OBJS_STATIC) pthread.h libpq-refs-stamp
# Might be left over from a Win32 client-only build
rm -f pg_config_paths.h
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index d5143766858..0625cf39e9a 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -210,3 +210,4 @@ PQsetAuthDataHook 207
PQgetAuthDataHook 208
PQdefaultAuthDataHook 209
PQfullProtocolVersion 210
+appendPQExpBufferVA 211
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
index cf1a25e2ccc..ccdd9139cf1 100644
--- a/src/interfaces/libpq/fe-auth-oauth.c
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -15,6 +15,10 @@
#include "postgres_fe.h"
+#ifdef USE_DYNAMIC_OAUTH
+#include <dlfcn.h>
+#endif
+
#include "common/base64.h"
#include "common/hmac.h"
#include "common/jsonapi.h"
@@ -22,6 +26,7 @@
#include "fe-auth.h"
#include "fe-auth-oauth.h"
#include "mb/pg_wchar.h"
+#include "pg_config_paths.h"
/* The exported OAuth callback mechanism. */
static void *oauth_init(PGconn *conn, const char *password,
@@ -721,6 +726,186 @@ cleanup_user_oauth_flow(PGconn *conn)
state->async_ctx = NULL;
}
+/*-------------
+ * Builtin Flow
+ *
+ * There are three potential implementations of use_builtin_flow:
+ *
+ * 1) If the OAuth client is disabled at configuration time, return false.
+ * Dependent clients must provide their own flow.
+ * 2) If the OAuth client is enabled and USE_DYNAMIC_OAUTH is defined, dlopen()
+ * the libpq-oauth plugin and use its implementation.
+ * 3) Otherwise, use flow callbacks that are statically linked into the
+ * executable.
+ */
+
+#if !defined(USE_LIBCURL)
+
+/*
+ * This configuration doesn't support the builtin flow.
+ */
+
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ return false;
+}
+
+#elif defined(USE_DYNAMIC_OAUTH)
+
+/*
+ * Use the builtin flow in the libpq-oauth plugin, which is loaded at runtime.
+ */
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/*
+ * This shim is injected into libpq-oauth so that it doesn't depend on the
+ * offset of conn->errorMessage.
+ *
+ * TODO: look into exporting libpq_append_conn_error or a comparable API from
+ * libpq, instead.
+ */
+static PQExpBuffer
+conn_errorMessage(PGconn *conn)
+{
+ return &conn->errorMessage;
+}
+
+/*
+ * Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
+ * callbacks into the connection's async auth handlers.
+ *
+ * Failure to load here results in a relatively quiet connection error, to
+ * handle the use case where the build supports loading a flow but a user does
+ * not want to install it. Troubleshooting of linker/loader failures can be done
+ * via PGOAUTHDEBUG.
+ */
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ static bool initialized = false;
+ static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
+ int lockerr;
+
+ void (*init) (pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+ PostgresPollingStatusType (*flow) (PGconn *conn);
+ void (*cleanup) (PGconn *conn);
+
+ /*
+ * On macOS only, load the module using its absolute install path; the
+ * standard search behavior is not very helpful for this use case. Unlike
+ * on other platforms, DYLD_LIBRARY_PATH is used as a fallback even with
+ * absolute paths (modulo SIP effects), so tests can continue to work.
+ *
+ * On the other platforms, load the module using only the basename, to
+ * rely on the runtime linker's standard search behavior.
+ */
+ const char *const module_name =
+#if defined(__darwin__)
+ LIBDIR "/libpq-oauth-" PG_MAJORVERSION "-" PG_MINORVERSION DLSUFFIX;
+#else
+ "libpq-oauth-" PG_MAJORVERSION "-" PG_MINORVERSION DLSUFFIX;
+#endif
+
+ state->builtin_flow = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
+ if (!state->builtin_flow)
+ {
+ /*
+ * For end users, this probably isn't an error condition, it just
+ * means the flow isn't installed. Developers and package maintainers
+ * may want to debug this via the PGOAUTHDEBUG envvar, though.
+ *
+ * Note that POSIX dlerror() isn't guaranteed to be threadsafe.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
+
+ return false;
+ }
+
+ if ((init = dlsym(state->builtin_flow, "libpq_oauth_init")) == NULL
+ || (flow = dlsym(state->builtin_flow, "pg_fe_run_oauth_flow")) == NULL
+ || (cleanup = dlsym(state->builtin_flow, "pg_fe_cleanup_oauth_flow")) == NULL)
+ {
+ /*
+ * This is more of an error condition than the one above, but due to
+ * the dlerror() threadsafety issue, lock it behind PGOAUTHDEBUG too.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
+
+ dlclose(state->builtin_flow);
+ return false;
+ }
+
+ /*
+ * Past this point, we do not unload the module. It stays in the process
+ * permanently.
+ */
+
+ /*
+ * We need to inject necessary function pointers into the module. This
+ * only needs to be done once -- even if the pointers are constant,
+ * assigning them while another thread is executing the flows feels like
+ * tempting fate.
+ */
+ if ((lockerr = pthread_mutex_lock(&init_mutex)) != 0)
+ {
+ /* Should not happen... but don't continue if it does. */
+ Assert(false);
+
+ libpq_append_conn_error(conn, "failed to lock mutex (%d)", lockerr);
+ return false;
+ }
+
+ if (!initialized)
+ {
+ init(pg_g_threadlock,
+#ifdef ENABLE_NLS
+ libpq_gettext,
+#else
+ NULL,
+#endif
+ conn_errorMessage);
+
+ initialized = true;
+ }
+
+ pthread_mutex_unlock(&init_mutex);
+
+ /* Set our asynchronous callbacks. */
+ conn->async_auth = flow;
+ conn->cleanup_async_auth = cleanup;
+
+ return true;
+}
+
+#else
+
+/*
+ * Use the builtin flow in libpq-oauth.a (see libpq-oauth/oauth-curl.h).
+ */
+
+extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ /* Set our asynchronous callbacks. */
+ conn->async_auth = pg_fe_run_oauth_flow;
+ conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
+
+ return true;
+}
+
+#endif /* USE_LIBCURL */
+
+
/*
* Chooses an OAuth client flow for the connection, which will retrieve a Bearer
* token for presentation to the server.
@@ -792,18 +977,10 @@ setup_token_request(PGconn *conn, fe_oauth_state *state)
libpq_append_conn_error(conn, "user-defined OAuth flow failed");
goto fail;
}
- else
+ else if (!use_builtin_flow(conn, state))
{
-#if USE_LIBCURL
- /* Hand off to our built-in OAuth flow. */
- conn->async_auth = pg_fe_run_oauth_flow;
- conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
-
-#else
- libpq_append_conn_error(conn, "no custom OAuth flows are available, and libpq was not built with libcurl support");
+ libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
goto fail;
-
-#endif
}
return true;
diff --git a/src/interfaces/libpq/fe-auth-oauth.h b/src/interfaces/libpq/fe-auth-oauth.h
index 3f1a7503a01..687e664475f 100644
--- a/src/interfaces/libpq/fe-auth-oauth.h
+++ b/src/interfaces/libpq/fe-auth-oauth.h
@@ -33,12 +33,13 @@ typedef struct
PGconn *conn;
void *async_ctx;
+
+ void *builtin_flow;
} fe_oauth_state;
-extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
-extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
extern void pqClearOAuthToken(PGconn *conn);
extern bool oauth_unsafe_debugging_enabled(void);
+extern bool use_builtin_flow(PGconn *conn, fe_oauth_state *state);
/* Mechanisms in fe-auth-oauth.c */
extern const pg_fe_sasl_mech pg_oauth_mech;
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index 292fecf3320..a74e885b169 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -38,10 +38,6 @@ if gssapi.found()
)
endif
-if libcurl.found()
- libpq_sources += files('fe-auth-oauth-curl.c')
-endif
-
export_file = custom_target('libpq.exports',
kwargs: gen_export_kwargs,
)
@@ -50,6 +46,9 @@ export_file = custom_target('libpq.exports',
libpq_inc = include_directories('.', '../../port')
libpq_c_args = ['-DSO_MAJOR_VERSION=5']
+# The OAuth implementation differs depending on the type of library being built.
+libpq_so_c_args = ['-DUSE_DYNAMIC_OAUTH']
+
# Not using both_libraries() here as
# 1) resource files should only be in the shared library
# 2) we want the .pc file to include a dependency to {pgport,common}_static for
@@ -70,7 +69,7 @@ libpq_st = static_library('libpq',
libpq_so = shared_library('libpq',
libpq_sources + libpq_so_sources,
include_directories: [libpq_inc, postgres_inc],
- c_args: libpq_c_args,
+ c_args: libpq_c_args + libpq_so_c_args,
c_pch: pch_postgres_fe_h,
version: '5.' + pg_version_major.to_string(),
soversion: host_system != 'windows' ? '5' : '',
@@ -86,12 +85,26 @@ libpq = declare_dependency(
include_directories: [include_directories('.')]
)
+private_deps = [
+ frontend_stlib_code,
+ libpq_deps,
+]
+
+if oauth_flow_supported
+ # libpq.so doesn't link against libcurl, but libpq.a needs libpq-oauth, and
+ # libpq-oauth needs libcurl. Put both into *.private.
+ private_deps += [
+ libpq_oauth_deps,
+ '-lpq-oauth',
+ ]
+endif
+
pkgconfig.generate(
name: 'libpq',
description: 'PostgreSQL libpq library',
url: pg_url,
libraries: libpq,
- libraries_private: [frontend_stlib_code, libpq_deps],
+ libraries_private: private_deps,
)
install_headers(
diff --git a/src/interfaces/libpq/nls.mk b/src/interfaces/libpq/nls.mk
index ae761265852..b87df277d93 100644
--- a/src/interfaces/libpq/nls.mk
+++ b/src/interfaces/libpq/nls.mk
@@ -13,15 +13,21 @@ GETTEXT_FILES = fe-auth.c \
fe-secure-common.c \
fe-secure-gssapi.c \
fe-secure-openssl.c \
- win32.c
-GETTEXT_TRIGGERS = libpq_append_conn_error:2 \
+ win32.c \
+ ../libpq-oauth/oauth-curl.c \
+ ../libpq-oauth/oauth-utils.c
+GETTEXT_TRIGGERS = actx_error:2 \
+ libpq_append_conn_error:2 \
libpq_append_error:2 \
libpq_gettext \
libpq_ngettext:1,2 \
+ oauth_parse_set_error:2 \
pqInternalNotice:2
-GETTEXT_FLAGS = libpq_append_conn_error:2:c-format \
+GETTEXT_FLAGS = actx_error:2:c-format \
+ libpq_append_conn_error:2:c-format \
libpq_append_error:2:c-format \
libpq_gettext:1:pass-c-format \
libpq_ngettext:1:pass-c-format \
libpq_ngettext:2:pass-c-format \
+ oauth_parse_set_error:2:c-format \
pqInternalNotice:2:c-format
diff --git a/src/makefiles/meson.build b/src/makefiles/meson.build
index e3adb5d8dc4..48d01a54dc6 100644
--- a/src/makefiles/meson.build
+++ b/src/makefiles/meson.build
@@ -204,6 +204,8 @@ pgxs_empty = [
'LIBNUMA_CFLAGS', 'LIBNUMA_LIBS',
'LIBURING_CFLAGS', 'LIBURING_LIBS',
+
+ 'LIBCURL_CPPFLAGS', 'LIBCURL_LDFLAGS', 'LIBCURL_LDLIBS',
]
if host_system == 'windows' and cc.get_argument_syntax() != 'msvc'
diff --git a/src/test/modules/oauth_validator/meson.build b/src/test/modules/oauth_validator/meson.build
index 36d1b26369f..e190f9cf15a 100644
--- a/src/test/modules/oauth_validator/meson.build
+++ b/src/test/modules/oauth_validator/meson.build
@@ -78,7 +78,7 @@ tests += {
],
'env': {
'PYTHON': python.path(),
- 'with_libcurl': libcurl.found() ? 'yes' : 'no',
+ 'with_libcurl': oauth_flow_supported ? 'yes' : 'no',
'with_python': 'yes',
},
},
diff --git a/src/test/modules/oauth_validator/t/002_client.pl b/src/test/modules/oauth_validator/t/002_client.pl
index 8dd502f41e1..21d4acc1926 100644
--- a/src/test/modules/oauth_validator/t/002_client.pl
+++ b/src/test/modules/oauth_validator/t/002_client.pl
@@ -110,7 +110,7 @@ if ($ENV{with_libcurl} ne 'yes')
"fails without custom hook installed",
flags => ["--no-hook"],
expected_stderr =>
- qr/no custom OAuth flows are available, and libpq was not built with libcurl support/
+ qr/no OAuth flows are available \(try installing the libpq-oauth package\)/
);
}
--
2.34.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-22 23:41 ` Jacob Champion <[email protected]>
1 sibling, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-22 23:41 UTC (permalink / raw)
To: Daniel Gustafsson <[email protected]>; +Cc: Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Tue, Apr 22, 2025 at 3:02 AM Daniel Gustafsson <[email protected]> wrote:
> + if oauth_flow_supported
> + cdata.set('USE_LIBCURL', 1)
> + elif libcurlopt.enabled()
> + error('client OAuth is not supported on this platform')
> + endif
> We already know that libcurlopt.enabled() is true here so maybe just doing
> if-else-endif would make it more readable and save readers thinking it might
> have changed?
Features are tri-state, so libcurlopt.disabled() and
libcurlopt.enabled() can both be false. :( My intent is to fall
through nicely in the case where -Dlibcurl=auto.
(Our minimum version of Meson is too old to switch to syntax that
makes this more readable, like .allowed(), .require(), .disable_if(),
etc...)
> Also, "client OAuth" reads a bit strange, how about "client-side
> OAuth" or "OAuth flow module"?
> ...
> I think we should take this opportunity to turn this into a appendPQExpBuffer()
> with a format string instead of two calls.
> ...
> Now that the actual variable, errbuf->len, is short and very descriptive I
> wonder if we shouldn't just use this as it makes the code even clearer IMO.
All three done in v9, attached.
Thanks!
--Jacob
1: 5f87f11b18e = 1: 5f87f11b18e Add minor-version counterpart to (PG_)MAJORVERSION
2: 4c9cc7f69af ! 2: 9e37fd7c217 oauth: Move the builtin flow into a separate module
@@ configure.ac: if test "$PORTNAME" = "win32" ; then
+if test "$with_libcurl" = yes ; then
+ # Error out early if this platform can't support libpq-oauth.
+ if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
-+ AC_MSG_ERROR([client OAuth is not supported on this platform])
++ AC_MSG_ERROR([client-side OAuth is not supported on this platform])
+ fi
+fi
+
@@ meson.build: if not libcurlopt.disabled()
+ if oauth_flow_supported
+ cdata.set('USE_LIBCURL', 1)
+ elif libcurlopt.enabled()
-+ error('client OAuth is not supported on this platform')
++ error('client-side OAuth is not supported on this platform')
+ endif
+
else
@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
* also the documentation for struct async_ctx.
*/
if (actx->errctx)
- {
+- {
- appendPQExpBufferStr(&conn->errorMessage,
- libpq_gettext(actx->errctx));
- appendPQExpBufferStr(&conn->errorMessage, ": ");
-+ appendPQExpBufferStr(errbuf, libpq_gettext(actx->errctx));
-+ appendPQExpBufferStr(errbuf, ": ");
- }
+- }
++ appendPQExpBuffer(errbuf, "%s: ", libpq_gettext(actx->errctx));
if (PQExpBufferDataBroken(actx->errbuf))
- appendPQExpBufferStr(&conn->errorMessage,
@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
if (actx->curl_err[0])
{
- size_t len;
-
+- size_t len;
+-
- appendPQExpBuffer(&conn->errorMessage,
- " (libcurl: %s)", actx->curl_err);
+ appendPQExpBuffer(errbuf, " (libcurl: %s)", actx->curl_err);
@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
/* Sometimes libcurl adds a newline to the error buffer. :( */
- len = conn->errorMessage.len;
- if (len >= 2 && conn->errorMessage.data[len - 2] == '\n')
-+ len = errbuf->len;
-+ if (len >= 2 && errbuf->data[len - 2] == '\n')
++ if (errbuf->len >= 2 && errbuf->data[errbuf->len - 2] == '\n')
{
- conn->errorMessage.data[len - 2] = ')';
- conn->errorMessage.data[len - 1] = '\0';
- conn->errorMessage.len--;
-+ errbuf->data[len - 2] = ')';
-+ errbuf->data[len - 1] = '\0';
++ errbuf->data[errbuf->len - 2] = ')';
++ errbuf->data[errbuf->len - 1] = '\0';
+ errbuf->len--;
}
}
Attachments:
[text/plain] since-v8.diff.txt (2.7K, ../../CAOYmi+=ka9dTDtFhHjnL7jLd-rA1Q+VuU6=vjMM=jjm6_yCrpg@mail.gmail.com/2-since-v8.diff.txt)
download | inline:
1: 5f87f11b18e = 1: 5f87f11b18e Add minor-version counterpart to (PG_)MAJORVERSION
2: 4c9cc7f69af ! 2: 9e37fd7c217 oauth: Move the builtin flow into a separate module
@@ configure.ac: if test "$PORTNAME" = "win32" ; then
+if test "$with_libcurl" = yes ; then
+ # Error out early if this platform can't support libpq-oauth.
+ if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
-+ AC_MSG_ERROR([client OAuth is not supported on this platform])
++ AC_MSG_ERROR([client-side OAuth is not supported on this platform])
+ fi
+fi
+
@@ meson.build: if not libcurlopt.disabled()
+ if oauth_flow_supported
+ cdata.set('USE_LIBCURL', 1)
+ elif libcurlopt.enabled()
-+ error('client OAuth is not supported on this platform')
++ error('client-side OAuth is not supported on this platform')
+ endif
+
else
@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
* also the documentation for struct async_ctx.
*/
if (actx->errctx)
- {
+- {
- appendPQExpBufferStr(&conn->errorMessage,
- libpq_gettext(actx->errctx));
- appendPQExpBufferStr(&conn->errorMessage, ": ");
-+ appendPQExpBufferStr(errbuf, libpq_gettext(actx->errctx));
-+ appendPQExpBufferStr(errbuf, ": ");
- }
+- }
++ appendPQExpBuffer(errbuf, "%s: ", libpq_gettext(actx->errctx));
if (PQExpBufferDataBroken(actx->errbuf))
- appendPQExpBufferStr(&conn->errorMessage,
@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
if (actx->curl_err[0])
{
- size_t len;
-
+- size_t len;
+-
- appendPQExpBuffer(&conn->errorMessage,
- " (libcurl: %s)", actx->curl_err);
+ appendPQExpBuffer(errbuf, " (libcurl: %s)", actx->curl_err);
@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
/* Sometimes libcurl adds a newline to the error buffer. :( */
- len = conn->errorMessage.len;
- if (len >= 2 && conn->errorMessage.data[len - 2] == '\n')
-+ len = errbuf->len;
-+ if (len >= 2 && errbuf->data[len - 2] == '\n')
++ if (errbuf->len >= 2 && errbuf->data[errbuf->len - 2] == '\n')
{
- conn->errorMessage.data[len - 2] = ')';
- conn->errorMessage.data[len - 1] = '\0';
- conn->errorMessage.len--;
-+ errbuf->data[len - 2] = ')';
-+ errbuf->data[len - 1] = '\0';
++ errbuf->data[errbuf->len - 2] = ')';
++ errbuf->data[errbuf->len - 1] = '\0';
+ errbuf->len--;
}
}
[application/octet-stream] v9-0001-Add-minor-version-counterpart-to-PG_-MAJORVERSION.patch (3.5K, ../../CAOYmi+=ka9dTDtFhHjnL7jLd-rA1Q+VuU6=vjMM=jjm6_yCrpg@mail.gmail.com/3-v9-0001-Add-minor-version-counterpart-to-PG_-MAJORVERSION.patch)
download | inline diff:
From 5f87f11b18ea83615c342c832caace49bf7e3897 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Mon, 21 Apr 2025 13:43:08 -0700
Subject: [PATCH v9 1/2] Add minor-version counterpart to (PG_)MAJORVERSION
An upcoming commit will name a library, libpq-oauth, using the major and
minor versions. Make the minor version accessible from the Makefiles and
as a string constant in the code.
---
configure | 7 +++++++
configure.ac | 2 ++
meson.build | 1 +
src/Makefile.global.in | 1 +
src/include/pg_config.h.in | 3 +++
src/makefiles/meson.build | 1 +
6 files changed, 15 insertions(+)
diff --git a/configure b/configure
index 0936010718d..3d783793dfa 100755
--- a/configure
+++ b/configure
@@ -792,6 +792,7 @@ build_os
build_vendor
build_cpu
build
+PG_MINORVERSION
PG_MAJORVERSION
target_alias
host_alias
@@ -2877,6 +2878,12 @@ cat >>confdefs.h <<_ACEOF
_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PG_MINORVERSION "$PG_MINORVERSION"
+_ACEOF
+
+
cat >>confdefs.h <<_ACEOF
#define PG_MINORVERSION_NUM $PG_MINORVERSION
_ACEOF
diff --git a/configure.ac b/configure.ac
index 2a78cddd825..1cb3a0ff042 100644
--- a/configure.ac
+++ b/configure.ac
@@ -35,6 +35,8 @@ test -n "$PG_MINORVERSION" || PG_MINORVERSION=0
AC_SUBST(PG_MAJORVERSION)
AC_DEFINE_UNQUOTED(PG_MAJORVERSION, "$PG_MAJORVERSION", [PostgreSQL major version as a string])
AC_DEFINE_UNQUOTED(PG_MAJORVERSION_NUM, $PG_MAJORVERSION, [PostgreSQL major version number])
+AC_SUBST(PG_MINORVERSION)
+AC_DEFINE_UNQUOTED(PG_MINORVERSION, "$PG_MINORVERSION", [PostgreSQL minor version as a string])
AC_DEFINE_UNQUOTED(PG_MINORVERSION_NUM, $PG_MINORVERSION, [PostgreSQL minor version number])
PGAC_ARG_REQ(with, extra-version, [STRING], [append STRING to version],
diff --git a/meson.build b/meson.build
index a1516e54529..18423a7c13e 100644
--- a/meson.build
+++ b/meson.build
@@ -148,6 +148,7 @@ pg_version += get_option('extra_version')
cdata.set_quoted('PG_VERSION', pg_version)
cdata.set_quoted('PG_MAJORVERSION', pg_version_major.to_string())
cdata.set('PG_MAJORVERSION_NUM', pg_version_major)
+cdata.set_quoted('PG_MINORVERSION', pg_version_minor.to_string())
cdata.set('PG_MINORVERSION_NUM', pg_version_minor)
cdata.set('PG_VERSION_NUM', pg_version_num)
# PG_VERSION_STR is built later, it depends on compiler test results
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 6722fbdf365..54b4a07712e 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -40,6 +40,7 @@ maintainer-clean: distclean
# PostgreSQL version number
VERSION = @PACKAGE_VERSION@
MAJORVERSION = @PG_MAJORVERSION@
+MINORVERSION = @PG_MINORVERSION@
VERSION_NUM = @PG_VERSION_NUM@
PACKAGE_URL = @PACKAGE_URL@
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index c3cc9fa856d..4fe37d228c5 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -602,6 +602,9 @@
/* PostgreSQL major version number */
#undef PG_MAJORVERSION_NUM
+/* PostgreSQL minor version as a string */
+#undef PG_MINORVERSION
+
/* PostgreSQL minor version number */
#undef PG_MINORVERSION_NUM
diff --git a/src/makefiles/meson.build b/src/makefiles/meson.build
index 55da678ec27..e3adb5d8dc4 100644
--- a/src/makefiles/meson.build
+++ b/src/makefiles/meson.build
@@ -36,6 +36,7 @@ pgxs_kv = {
'PACKAGE_URL': pg_url,
'PACKAGE_VERSION': pg_version,
'PG_MAJORVERSION': pg_version_major,
+ 'PG_MINORVERSION': pg_version_minor,
'PG_VERSION_NUM': pg_version_num,
'configure_input': 'meson',
--
2.34.1
[application/octet-stream] v9-0002-oauth-Move-the-builtin-flow-into-a-separate-modul.patch (56.7K, ../../CAOYmi+=ka9dTDtFhHjnL7jLd-rA1Q+VuU6=vjMM=jjm6_yCrpg@mail.gmail.com/4-v9-0002-oauth-Move-the-builtin-flow-into-a-separate-modul.patch)
download | inline diff:
From 9e37fd7c2171d8fe1880c0b00625b7ca6a833062 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Wed, 26 Mar 2025 10:55:28 -0700
Subject: [PATCH v9 2/2] oauth: Move the builtin flow into a separate module
The additional packaging footprint of the OAuth Curl dependency, as well
as the existence of libcurl in the address space even if OAuth isn't
ever used by a client, has raised some concerns. Split off this
dependency into a separate loadable module called libpq-oauth.
When configured using --with-libcurl, libpq.so searches for this new
module via dlopen(). End users may choose not to install the libpq-oauth
module, in which case the default flow is disabled.
For static applications using libpq.a, the libpq-oauth staticlib is a
mandatory link-time dependency for --with-libcurl builds. libpq.pc has
been updated accordingly.
The default flow relies on some libpq internals. Some of these can be
safely duplicated (such as the SIGPIPE handlers), but others need to be
shared between libpq and libpq-oauth for thread-safety. To avoid exporting
these internals to all libpq clients forever, these dependencies are
instead injected from the libpq side via an initialization function.
This also lets libpq communicate the offset of conn->errorMessage to
libpq-oauth, so that we can function without crashing if the module on
the search path came from a different build of Postgres.
This ABI is considered "private". The module has no SONAME or version
symlinks, and it's named libpq-oauth-<major>-<minor>.so to avoid mixing
and matching across Postgres versions, in case internal struct order
needs to change. (Future improvements may promote this "OAuth flow
plugin" to a first-class concept, at which point we would need a public
API to replace this anyway.)
Additionally, NLS support for error messages in b3f0be788a was
incomplete, because the new error macros weren't being scanned by
xgettext. Fix that now.
Per request from Tom Lane and Bruce Momjian. Based on an initial patch
by Daniel Gustafsson, who also contributed docs changes. The "bare"
dlopen() concept came from Thomas Munro. Many many people reviewed the
design and implementation; thank you!
Co-authored-by: Daniel Gustafsson <[email protected]>
Reviewed-by: Andres Freund <[email protected]>
Reviewed-by: Christoph Berg <[email protected]>
Reviewed-by: Jelte Fennema-Nio <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Wolfgang Walther <[email protected]>
Discussion: https://postgr.es/m/641687.1742360249%40sss.pgh.pa.us
---
config/programs.m4 | 17 +-
configure | 50 ++++-
configure.ac | 26 ++-
doc/src/sgml/installation.sgml | 8 +
doc/src/sgml/libpq.sgml | 30 ++-
meson.build | 32 ++-
src/Makefile.global.in | 3 +
src/interfaces/Makefile | 12 ++
src/interfaces/libpq-oauth/Makefile | 83 +++++++
src/interfaces/libpq-oauth/README | 43 ++++
src/interfaces/libpq-oauth/exports.txt | 4 +
src/interfaces/libpq-oauth/meson.build | 45 ++++
.../oauth-curl.c} | 101 ++++-----
src/interfaces/libpq-oauth/oauth-curl.h | 24 +++
src/interfaces/libpq-oauth/oauth-utils.c | 202 ++++++++++++++++++
src/interfaces/libpq-oauth/oauth-utils.h | 38 ++++
src/interfaces/libpq/Makefile | 36 +++-
src/interfaces/libpq/exports.txt | 1 +
src/interfaces/libpq/fe-auth-oauth.c | 197 ++++++++++++++++-
src/interfaces/libpq/fe-auth-oauth.h | 5 +-
src/interfaces/libpq/meson.build | 25 ++-
src/interfaces/libpq/nls.mk | 12 +-
src/makefiles/meson.build | 2 +
src/test/modules/oauth_validator/meson.build | 2 +-
.../modules/oauth_validator/t/002_client.pl | 2 +-
25 files changed, 884 insertions(+), 116 deletions(-)
create mode 100644 src/interfaces/libpq-oauth/Makefile
create mode 100644 src/interfaces/libpq-oauth/README
create mode 100644 src/interfaces/libpq-oauth/exports.txt
create mode 100644 src/interfaces/libpq-oauth/meson.build
rename src/interfaces/{libpq/fe-auth-oauth-curl.c => libpq-oauth/oauth-curl.c} (97%)
create mode 100644 src/interfaces/libpq-oauth/oauth-curl.h
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.c
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.h
diff --git a/config/programs.m4 b/config/programs.m4
index 0a07feb37cc..0ad1e58b48d 100644
--- a/config/programs.m4
+++ b/config/programs.m4
@@ -286,9 +286,20 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
[
AC_CHECK_HEADER(curl/curl.h, [],
[AC_MSG_ERROR([header file <curl/curl.h> is required for --with-libcurl])])
- AC_CHECK_LIB(curl, curl_multi_init, [],
+ AC_CHECK_LIB(curl, curl_multi_init, [
+ AC_DEFINE([HAVE_LIBCURL], [1], [Define to 1 if you have the `curl' library (-lcurl).])
+ AC_SUBST(LIBCURL_LDLIBS, -lcurl)
+ ],
[AC_MSG_ERROR([library 'curl' does not provide curl_multi_init])])
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+ LIBS="$LIBCURL_LDLIBS $LIBS"
+
# Check to see whether the current platform supports threadsafe Curl
# initialization.
AC_CACHE_CHECK([for curl_global_init thread safety], [pgac_cv__libcurl_threadsafe_init],
@@ -338,4 +349,8 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
*** lookups. Rebuild libcurl with the AsynchDNS feature enabled in order
*** to use it with libpq.])
fi
+
+ CPPFLAGS=$pgac_save_CPPFLAGS
+ LDFLAGS=$pgac_save_LDFLAGS
+ LIBS=$pgac_save_LIBS
])# PGAC_CHECK_LIBCURL
diff --git a/configure b/configure
index 3d783793dfa..eedd18e6d9a 100755
--- a/configure
+++ b/configure
@@ -655,6 +655,7 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+LIBCURL_LDLIBS
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -711,6 +712,8 @@ with_libxml
LIBNUMA_LIBS
LIBNUMA_CFLAGS
with_libnuma
+LIBCURL_LDFLAGS
+LIBCURL_CPPFLAGS
LIBCURL_LIBS
LIBCURL_CFLAGS
with_libcurl
@@ -9060,19 +9063,27 @@ $as_echo "yes" >&6; }
fi
- # We only care about -I, -D, and -L switches;
- # note that -lcurl will be added by PGAC_CHECK_LIBCURL below.
+ # Curl's flags are kept separate from the standard CPPFLAGS/LDFLAGS. We use
+ # them only for libpq-oauth.
+ LIBCURL_CPPFLAGS=
+ LIBCURL_LDFLAGS=
+
+ # We only care about -I, -D, and -L switches. Note that -lcurl will be added
+ # to LIBCURL_LDLIBS by PGAC_CHECK_LIBCURL, below.
for pgac_option in $LIBCURL_CFLAGS; do
case $pgac_option in
- -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+ -I*|-D*) LIBCURL_CPPFLAGS="$LIBCURL_CPPFLAGS $pgac_option";;
esac
done
for pgac_option in $LIBCURL_LIBS; do
case $pgac_option in
- -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+ -L*) LIBCURL_LDFLAGS="$LIBCURL_LDFLAGS $pgac_option";;
esac
done
+
+
+
# OAuth requires python for testing
if test "$with_python" != yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: *** OAuth support tests require --with-python to run" >&5
@@ -12711,9 +12722,6 @@ fi
fi
-# XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
-# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
-# dependency on that platform?
if test "$with_libcurl" = yes ; then
ac_fn_c_check_header_mongrel "$LINENO" "curl/curl.h" "ac_cv_header_curl_curl_h" "$ac_includes_default"
@@ -12761,17 +12769,26 @@ fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_curl_curl_multi_init" >&5
$as_echo "$ac_cv_lib_curl_curl_multi_init" >&6; }
if test "x$ac_cv_lib_curl_curl_multi_init" = xyes; then :
- cat >>confdefs.h <<_ACEOF
-#define HAVE_LIBCURL 1
-_ACEOF
- LIBS="-lcurl $LIBS"
+
+$as_echo "#define HAVE_LIBCURL 1" >>confdefs.h
+
+ LIBCURL_LDLIBS=-lcurl
+
else
as_fn_error $? "library 'curl' does not provide curl_multi_init" "$LINENO" 5
fi
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+ LIBS="$LIBCURL_LDLIBS $LIBS"
+
# Check to see whether the current platform supports threadsafe Curl
# initialization.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_global_init thread safety" >&5
@@ -12875,6 +12892,10 @@ $as_echo "$pgac_cv__libcurl_async_dns" >&6; }
*** to use it with libpq." "$LINENO" 5
fi
+ CPPFLAGS=$pgac_save_CPPFLAGS
+ LDFLAGS=$pgac_save_LDFLAGS
+ LIBS=$pgac_save_LIBS
+
fi
if test "$with_gssapi" = yes ; then
@@ -14523,6 +14544,13 @@ done
fi
+if test "$with_libcurl" = yes ; then
+ # Error out early if this platform can't support libpq-oauth.
+ if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
+ as_fn_error $? "client OAuth is not supported on this platform" "$LINENO" 5
+ fi
+fi
+
##
## Types, structures, compiler characteristics
##
diff --git a/configure.ac b/configure.ac
index 1cb3a0ff042..d1c8dd536cd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1035,19 +1035,27 @@ if test "$with_libcurl" = yes ; then
# to explicitly set TLS 1.3 ciphersuites).
PKG_CHECK_MODULES(LIBCURL, [libcurl >= 7.61.0])
- # We only care about -I, -D, and -L switches;
- # note that -lcurl will be added by PGAC_CHECK_LIBCURL below.
+ # Curl's flags are kept separate from the standard CPPFLAGS/LDFLAGS. We use
+ # them only for libpq-oauth.
+ LIBCURL_CPPFLAGS=
+ LIBCURL_LDFLAGS=
+
+ # We only care about -I, -D, and -L switches. Note that -lcurl will be added
+ # to LIBCURL_LDLIBS by PGAC_CHECK_LIBCURL, below.
for pgac_option in $LIBCURL_CFLAGS; do
case $pgac_option in
- -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+ -I*|-D*) LIBCURL_CPPFLAGS="$LIBCURL_CPPFLAGS $pgac_option";;
esac
done
for pgac_option in $LIBCURL_LIBS; do
case $pgac_option in
- -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+ -L*) LIBCURL_LDFLAGS="$LIBCURL_LDFLAGS $pgac_option";;
esac
done
+ AC_SUBST(LIBCURL_CPPFLAGS)
+ AC_SUBST(LIBCURL_LDFLAGS)
+
# OAuth requires python for testing
if test "$with_python" != yes; then
AC_MSG_WARN([*** OAuth support tests require --with-python to run])
@@ -1356,9 +1364,6 @@ failure. It is possible the compiler isn't looking in the proper directory.
Use --without-zlib to disable zlib support.])])
fi
-# XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
-# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
-# dependency on that platform?
if test "$with_libcurl" = yes ; then
PGAC_CHECK_LIBCURL
fi
@@ -1656,6 +1661,13 @@ if test "$PORTNAME" = "win32" ; then
AC_CHECK_HEADERS(crtdefs.h)
fi
+if test "$with_libcurl" = yes ; then
+ # Error out early if this platform can't support libpq-oauth.
+ if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
+ AC_MSG_ERROR([client-side OAuth is not supported on this platform])
+ fi
+fi
+
##
## Types, structures, compiler characteristics
##
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 077bcc20759..d928b103d22 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -313,6 +313,14 @@
</para>
</listitem>
+ <listitem>
+ <para>
+ You need <productname>Curl</productname> to build an optional module
+ which implements the <link linkend="libpq-oauth">OAuth Device
+ Authorization flow</link> for client applications.
+ </para>
+ </listitem>
+
<listitem>
<para>
You need <productname>LZ4</productname>, if you want to support
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 3be66789ba7..cd748902f4d 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -10226,15 +10226,20 @@ void PQinitSSL(int do_ssl);
<title>OAuth Support</title>
<para>
- libpq implements support for the OAuth v2 Device Authorization client flow,
+ <application>libpq</application> implements support for the OAuth v2 Device Authorization client flow,
documented in
<ulink url="https://datatracker.ietf.org/doc/html/rfc8628">RFC 8628</ulink>,
- which it will attempt to use by default if the server
+ as an optional module. See the <link linkend="configure-option-with-libcurl">
+ installation documentation</link> for information on how to enable support
+ for Device Authorization as a builtin flow.
+ </para>
+ <para>
+ When support is enabled and the optional module installed, <application>libpq</application>
+ will use the builtin flow by default if the server
<link linkend="auth-oauth">requests a bearer token</link> during
authentication. This flow can be utilized even if the system running the
client application does not have a usable web browser, for example when
- running a client via <application>SSH</application>. Client applications may implement their own flows
- instead; see <xref linkend="libpq-oauth-authdata-hooks"/>.
+ running a client via <acronym>SSH</acronym>.
</para>
<para>
The builtin flow will, by default, print a URL to visit and a user code to
@@ -10251,6 +10256,11 @@ Visit https://example.com/device and enter the code: ABCD-EFGH
they match expectations, before continuing. Permissions should not be given
to untrusted third parties.
</para>
+ <para>
+ Client applications may implement their own flows to customize interaction
+ and integration with applications. See <xref linkend="libpq-oauth-authdata-hooks"/>
+ for more information on how add a custom flow to <application>libpq</application>.
+ </para>
<para>
For an OAuth client flow to be usable, the connection string must at minimum
contain <xref linkend="libpq-connect-oauth-issuer"/> and
@@ -10366,7 +10376,9 @@ typedef struct _PGpromptOAuthDevice
</synopsis>
</para>
<para>
- The OAuth Device Authorization flow included in <application>libpq</application>
+ The OAuth Device Authorization flow which
+ <link linkend="configure-option-with-libcurl">can be included</link>
+ in <application>libpq</application>
requires the end user to visit a URL with a browser, then enter a code
which permits <application>libpq</application> to connect to the server
on their behalf. The default prompt simply prints the
@@ -10378,7 +10390,8 @@ typedef struct _PGpromptOAuthDevice
This callback is only invoked during the builtin device
authorization flow. If the application installs a
<link linkend="libpq-oauth-authdata-oauth-bearer-token">custom OAuth
- flow</link>, this authdata type will not be used.
+ flow</link>, or <application>libpq</application> was not built with
+ support for the builtin flow, this authdata type will not be used.
</para>
<para>
If a non-NULL <structfield>verification_uri_complete</structfield> is
@@ -10400,8 +10413,9 @@ typedef struct _PGpromptOAuthDevice
</term>
<listitem>
<para>
- Replaces the entire OAuth flow with a custom implementation. The hook
- should either directly return a Bearer token for the current
+ Adds a custom implementation of a flow, replacing the builtin flow if
+ it is <link linkend="configure-option-with-libcurl">installed</link>.
+ The hook should either directly return a Bearer token for the current
user/issuer/scope combination, if one is available without blocking, or
else set up an asynchronous callback to retrieve one.
</para>
diff --git a/meson.build b/meson.build
index 18423a7c13e..2798922c6f0 100644
--- a/meson.build
+++ b/meson.build
@@ -107,6 +107,7 @@ os_deps = []
backend_both_deps = []
backend_deps = []
libpq_deps = []
+libpq_oauth_deps = []
pg_sysroot = ''
@@ -861,13 +862,13 @@ endif
###############################################################
libcurlopt = get_option('libcurl')
+oauth_flow_supported = false
+
if not libcurlopt.disabled()
# Check for libcurl 7.61.0 or higher (corresponding to RHEL8 and the ability
# to explicitly set TLS 1.3 ciphersuites).
libcurl = dependency('libcurl', version: '>= 7.61.0', required: libcurlopt)
if libcurl.found()
- cdata.set('USE_LIBCURL', 1)
-
# Check to see whether the current platform supports thread-safe Curl
# initialization.
libcurl_threadsafe_init = false
@@ -939,6 +940,22 @@ if not libcurlopt.disabled()
endif
endif
+ # Check that the current platform supports our builtin flow. This requires
+ # libcurl and one of either epoll or kqueue.
+ oauth_flow_supported = (
+ libcurl.found()
+ and (cc.check_header('sys/event.h', required: false,
+ args: test_c_args, include_directories: postgres_inc)
+ or cc.check_header('sys/epoll.h', required: false,
+ args: test_c_args, include_directories: postgres_inc))
+ )
+
+ if oauth_flow_supported
+ cdata.set('USE_LIBCURL', 1)
+ elif libcurlopt.enabled()
+ error('client-side OAuth is not supported on this platform')
+ endif
+
else
libcurl = not_found_dep
endif
@@ -3273,17 +3290,18 @@ libpq_deps += [
gssapi,
ldap_r,
- # XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
- # during gss_acquire_cred(). This is possibly related to Curl's Heimdal
- # dependency on that platform?
- libcurl,
libintl,
ssl,
]
+libpq_oauth_deps += [
+ libcurl,
+]
+
subdir('src/interfaces/libpq')
-# fe_utils depends on libpq
+# fe_utils and libpq-oauth depends on libpq
subdir('src/fe_utils')
+subdir('src/interfaces/libpq-oauth')
# for frontend binaries
frontend_code = declare_dependency(
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 54b4a07712e..f4caece04df 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -348,6 +348,9 @@ perl_embed_ldflags = @perl_embed_ldflags@
AWK = @AWK@
LN_S = @LN_S@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBCURL_LDFLAGS = @LIBCURL_LDFLAGS@
+LIBCURL_LDLIBS = @LIBCURL_LDLIBS@
MSGFMT = @MSGFMT@
MSGFMT_FLAGS = @MSGFMT_FLAGS@
MSGMERGE = @MSGMERGE@
diff --git a/src/interfaces/Makefile b/src/interfaces/Makefile
index 7d56b29d28f..e6822caa206 100644
--- a/src/interfaces/Makefile
+++ b/src/interfaces/Makefile
@@ -14,7 +14,19 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = libpq ecpg
+ifeq ($(with_libcurl), yes)
+SUBDIRS += libpq-oauth
+else
+ALWAYS_SUBDIRS += libpq-oauth
+endif
+
$(recurse)
+$(recurse_always)
all-ecpg-recurse: all-libpq-recurse
install-ecpg-recurse: install-libpq-recurse
+
+ifeq ($(with_libcurl), yes)
+all-libpq-oauth-recurse: all-libpq-recurse
+install-libpq-oauth-recurse: install-libpq-recurse
+endif
diff --git a/src/interfaces/libpq-oauth/Makefile b/src/interfaces/libpq-oauth/Makefile
new file mode 100644
index 00000000000..98acaff1a3b
--- /dev/null
+++ b/src/interfaces/libpq-oauth/Makefile
@@ -0,0 +1,83 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for libpq-oauth
+#
+# Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/interfaces/libpq-oauth/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/interfaces/libpq-oauth
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+PGFILEDESC = "libpq-oauth - device authorization OAuth support"
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
+NAME = pq-oauth-$(MAJORVERSION)-$(MINORVERSION)
+
+# Force the name "libpq-oauth" for both the static and shared libraries. The
+# staticlib doesn't need version information in its name.
+override shlib := lib$(NAME)$(DLSUFFIX)
+override stlib := libpq-oauth.a
+
+override CPPFLAGS := -I$(libpq_srcdir) -I$(top_builddir)/src/port $(LIBCURL_CPPFLAGS) $(CPPFLAGS)
+
+OBJS = \
+ $(WIN32RES)
+
+OBJS_STATIC = oauth-curl.o
+
+# The shared library needs additional glue symbols.
+OBJS_SHLIB = \
+ oauth-curl_shlib.o \
+ oauth-utils.o \
+
+oauth-utils.o: override CPPFLAGS += -DUSE_DYNAMIC_OAUTH
+oauth-curl_shlib.o: override CPPFLAGS_SHLIB += -DUSE_DYNAMIC_OAUTH
+
+# Add shlib-/stlib-specific objects.
+$(shlib): override OBJS += $(OBJS_SHLIB)
+$(shlib): $(OBJS_SHLIB)
+
+$(stlib): override OBJS += $(OBJS_STATIC)
+$(stlib): $(OBJS_STATIC)
+
+SHLIB_LINK_INTERNAL = $(libpq_pgport_shlib)
+SHLIB_LINK = $(LIBCURL_LDFLAGS) $(LIBCURL_LDLIBS)
+SHLIB_PREREQS = submake-libpq
+SHLIB_EXPORTS = exports.txt
+
+# Disable -bundle_loader on macOS.
+BE_DLLLIBS =
+
+# By default, a library without an SONAME doesn't get a static library, so we
+# add it to the build explicitly.
+all: all-lib all-static-lib
+
+# Shared library stuff
+include $(top_srcdir)/src/Makefile.shlib
+
+# Use src/common/Makefile's trick for tracking dependencies of shlib-specific
+# objects.
+%_shlib.o: %.c %.o
+ $(CC) $(CFLAGS) $(CFLAGS_SL) $(CPPFLAGS) $(CPPFLAGS_SHLIB) -c $< -o $@
+
+# Ignore the standard rules for SONAME-less installation; we want both the
+# static and shared libraries to go into libdir.
+install: all installdirs $(stlib) $(shlib)
+ $(INSTALL_SHLIB) $(shlib) '$(DESTDIR)$(libdir)/$(shlib)'
+ $(INSTALL_STLIB) $(stlib) '$(DESTDIR)$(libdir)/$(stlib)'
+
+installdirs:
+ $(MKDIR_P) '$(DESTDIR)$(libdir)'
+
+uninstall:
+ rm -f '$(DESTDIR)$(libdir)/$(stlib)'
+ rm -f '$(DESTDIR)$(libdir)/$(shlib)'
+
+clean distclean: clean-lib
+ rm -f $(OBJS) $(OBJS_STATIC) $(OBJS_SHLIB)
diff --git a/src/interfaces/libpq-oauth/README b/src/interfaces/libpq-oauth/README
new file mode 100644
index 00000000000..fdc1320d152
--- /dev/null
+++ b/src/interfaces/libpq-oauth/README
@@ -0,0 +1,43 @@
+libpq-oauth is an optional module implementing the Device Authorization flow for
+OAuth clients (RFC 8628). It was originally developed as part of libpq core and
+later split out as its own shared library in order to isolate its dependency on
+libcurl. (End users who don't want the Curl dependency can simply choose not to
+install this module.)
+
+If a connection string allows the use of OAuth, and the server asks for it, and
+a libpq client has not installed its own custom OAuth flow, libpq will attempt
+to delay-load this module using dlopen() and the following ABI. Failure to load
+results in a failed connection.
+
+= Load-Time ABI =
+
+This module ABI is an internal implementation detail, so it's subject to change
+across releases; the name of the module (libpq-oauth-MAJOR-MINOR) reflects this.
+The module exports the following symbols:
+
+- PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+- void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+pg_fe_run_oauth_flow and pg_fe_cleanup_oauth_flow are implementations of
+conn->async_auth and conn->cleanup_async_auth, respectively.
+
+- void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+
+At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
+libpq_gettext(), which must be injected by libpq using this initialization
+function before the flow is run.
+
+It also relies on libpq to expose conn->errorMessage, via the errmsg_impl. This
+is done to decouple the module ABI from the offset of errorMessage, which can
+change positions depending on configure-time options. This way we can safely
+search the standard dlopen() paths (e.g. RPATH, LD_LIBRARY_PATH, the SO cache)
+for an implementation module to use, even if that module wasn't compiled at the
+same time as libpq.
+
+= Static Build =
+
+The static library libpq.a does not perform any dynamic loading. If the builtin
+flow is enabled, the application is expected to link against libpq-oauth.a
+directly to provide the necessary symbols.
diff --git a/src/interfaces/libpq-oauth/exports.txt b/src/interfaces/libpq-oauth/exports.txt
new file mode 100644
index 00000000000..6891a83dbf9
--- /dev/null
+++ b/src/interfaces/libpq-oauth/exports.txt
@@ -0,0 +1,4 @@
+# src/interfaces/libpq-oauth/exports.txt
+libpq_oauth_init 1
+pg_fe_run_oauth_flow 2
+pg_fe_cleanup_oauth_flow 3
diff --git a/src/interfaces/libpq-oauth/meson.build b/src/interfaces/libpq-oauth/meson.build
new file mode 100644
index 00000000000..d97f893178a
--- /dev/null
+++ b/src/interfaces/libpq-oauth/meson.build
@@ -0,0 +1,45 @@
+# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+
+if not oauth_flow_supported
+ subdir_done()
+endif
+
+libpq_oauth_sources = files(
+ 'oauth-curl.c',
+)
+
+# The shared library needs additional glue symbols.
+libpq_oauth_so_sources = files(
+ 'oauth-utils.c',
+)
+libpq_oauth_so_c_args = ['-DUSE_DYNAMIC_OAUTH']
+
+export_file = custom_target('libpq-oauth.exports',
+ kwargs: gen_export_kwargs,
+)
+
+# port needs to be in include path due to pthread-win32.h
+libpq_oauth_inc = include_directories('.', '../libpq', '../../port')
+
+libpq_oauth_st = static_library('libpq-oauth',
+ libpq_oauth_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_stlib_code, libpq_oauth_deps],
+ kwargs: default_lib_args,
+)
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
+libpq_oauth_name = 'libpq-oauth-@0@-@1@'.format(pg_version_major, pg_version_minor)
+
+libpq_oauth_so = shared_module(libpq_oauth_name,
+ libpq_oauth_sources + libpq_oauth_so_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_args: libpq_so_c_args,
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_shlib_code, libpq, libpq_oauth_deps],
+ link_depends: export_file,
+ link_args: export_fmt.format(export_file.full_path()),
+ kwargs: default_lib_args,
+)
diff --git a/src/interfaces/libpq/fe-auth-oauth-curl.c b/src/interfaces/libpq-oauth/oauth-curl.c
similarity index 97%
rename from src/interfaces/libpq/fe-auth-oauth-curl.c
rename to src/interfaces/libpq-oauth/oauth-curl.c
index c195e00cd28..7b38395ec5f 100644
--- a/src/interfaces/libpq/fe-auth-oauth-curl.c
+++ b/src/interfaces/libpq-oauth/oauth-curl.c
@@ -1,6 +1,6 @@
/*-------------------------------------------------------------------------
*
- * fe-auth-oauth-curl.c
+ * oauth-curl.c
* The libcurl implementation of OAuth/OIDC authentication, using the
* OAuth Device Authorization Grant (RFC 8628).
*
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * src/interfaces/libpq/fe-auth-oauth-curl.c
+ * src/interfaces/libpq-oauth/oauth-curl.c
*
*-------------------------------------------------------------------------
*/
@@ -17,20 +17,25 @@
#include <curl/curl.h>
#include <math.h>
-#ifdef HAVE_SYS_EPOLL_H
+#include <unistd.h>
+
+#if defined(HAVE_SYS_EPOLL_H)
#include <sys/epoll.h>
#include <sys/timerfd.h>
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
#include <sys/event.h>
+#else
+#error libpq-oauth is not supported on this platform
#endif
-#include <unistd.h>
#include "common/jsonapi.h"
#include "fe-auth.h"
#include "fe-auth-oauth.h"
-#include "libpq-int.h"
#include "mb/pg_wchar.h"
+#include "oauth-curl.h"
+#ifdef USE_DYNAMIC_OAUTH
+#include "oauth-utils.h"
+#endif
/*
* It's generally prudent to set a maximum response size to buffer in memory,
@@ -1110,7 +1115,7 @@ parse_access_token(struct async_ctx *actx, struct token *tok)
static bool
setup_multiplexer(struct async_ctx *actx)
{
-#ifdef HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct epoll_event ev = {.events = EPOLLIN};
actx->mux = epoll_create1(EPOLL_CLOEXEC);
@@ -1134,8 +1139,7 @@ setup_multiplexer(struct async_ctx *actx)
}
return true;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
actx->mux = kqueue();
if (actx->mux < 0)
{
@@ -1158,10 +1162,9 @@ setup_multiplexer(struct async_ctx *actx)
}
return true;
+#else
+#error setup_multiplexer is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support the Device Authorization flow on this platform");
- return false;
}
/*
@@ -1174,7 +1177,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
{
struct async_ctx *actx = ctx;
-#ifdef HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct epoll_event ev = {0};
int res;
int op = EPOLL_CTL_ADD;
@@ -1230,8 +1233,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
}
return 0;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
struct kevent ev[2] = {0};
struct kevent ev_out[2];
struct timespec timeout = {0};
@@ -1312,10 +1314,9 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
}
return 0;
+#else
+#error register_socket is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support multiplexer sockets on this platform");
- return -1;
}
/*
@@ -1334,7 +1335,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
static bool
set_timer(struct async_ctx *actx, long timeout)
{
-#if HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct itimerspec spec = {0};
if (timeout < 0)
@@ -1363,8 +1364,7 @@ set_timer(struct async_ctx *actx, long timeout)
}
return true;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
struct kevent ev;
#ifdef __NetBSD__
@@ -1419,10 +1419,9 @@ set_timer(struct async_ctx *actx, long timeout)
}
return true;
+#else
+#error set_timer is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support timers on this platform");
- return false;
}
/*
@@ -1433,7 +1432,7 @@ set_timer(struct async_ctx *actx, long timeout)
static int
timer_expired(struct async_ctx *actx)
{
-#if HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct itimerspec spec = {0};
if (timerfd_gettime(actx->timerfd, &spec) < 0)
@@ -1453,8 +1452,7 @@ timer_expired(struct async_ctx *actx)
/* If the remaining time to expiration is zero, we're done. */
return (spec.it_value.tv_sec == 0
&& spec.it_value.tv_nsec == 0);
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
int res;
/* Is the timer queue ready? */
@@ -1466,10 +1464,9 @@ timer_expired(struct async_ctx *actx)
}
return (res > 0);
+#else
+#error timer_expired is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support timers on this platform");
- return -1;
}
/*
@@ -2487,8 +2484,9 @@ prompt_user(struct async_ctx *actx, PGconn *conn)
.verification_uri_complete = actx->authz.verification_uri_complete,
.expires_in = actx->authz.expires_in,
};
+ PQauthDataHook_type hook = PQgetAuthDataHook();
- res = PQauthDataHook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
+ res = hook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
if (!res)
{
@@ -2635,6 +2633,7 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
{
fe_oauth_state *state = conn->sasl_state;
struct async_ctx *actx;
+ PQExpBuffer errbuf;
if (!initialize_curl(conn))
return PGRES_POLLING_FAILED;
@@ -2825,41 +2824,43 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
error_return:
+ /*
+ * For the dynamic module build, we can't safely rely on the offset of
+ * conn->errorMessage, since it depends on build options like USE_SSL et
+ * al. libpq gives us a translator function instead.
+ */
+#ifdef USE_DYNAMIC_OAUTH
+ errbuf = conn_errorMessage(conn);
+#else
+ errbuf = &conn->errorMessage;
+#endif
+
/*
* Assemble the three parts of our error: context, body, and detail. See
* also the documentation for struct async_ctx.
*/
if (actx->errctx)
- {
- appendPQExpBufferStr(&conn->errorMessage,
- libpq_gettext(actx->errctx));
- appendPQExpBufferStr(&conn->errorMessage, ": ");
- }
+ appendPQExpBuffer(errbuf, "%s: ", libpq_gettext(actx->errctx));
if (PQExpBufferDataBroken(actx->errbuf))
- appendPQExpBufferStr(&conn->errorMessage,
- libpq_gettext("out of memory"));
+ appendPQExpBufferStr(errbuf, libpq_gettext("out of memory"));
else
- appendPQExpBufferStr(&conn->errorMessage, actx->errbuf.data);
+ appendPQExpBufferStr(errbuf, actx->errbuf.data);
if (actx->curl_err[0])
{
- size_t len;
-
- appendPQExpBuffer(&conn->errorMessage,
- " (libcurl: %s)", actx->curl_err);
+ appendPQExpBuffer(errbuf, " (libcurl: %s)", actx->curl_err);
/* Sometimes libcurl adds a newline to the error buffer. :( */
- len = conn->errorMessage.len;
- if (len >= 2 && conn->errorMessage.data[len - 2] == '\n')
+ if (errbuf->len >= 2 && errbuf->data[errbuf->len - 2] == '\n')
{
- conn->errorMessage.data[len - 2] = ')';
- conn->errorMessage.data[len - 1] = '\0';
- conn->errorMessage.len--;
+ errbuf->data[errbuf->len - 2] = ')';
+ errbuf->data[errbuf->len - 1] = '\0';
+ errbuf->len--;
}
}
- appendPQExpBufferChar(&conn->errorMessage, '\n');
+ appendPQExpBufferChar(errbuf, '\n');
return PGRES_POLLING_FAILED;
}
diff --git a/src/interfaces/libpq-oauth/oauth-curl.h b/src/interfaces/libpq-oauth/oauth-curl.h
new file mode 100644
index 00000000000..248d0424ad0
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-curl.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-curl.h
+ *
+ * Definitions for OAuth Device Authorization module
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-curl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_CURL_H
+#define OAUTH_CURL_H
+
+#include "libpq-fe.h"
+
+/* Exported async-auth callbacks. */
+extern PGDLLEXPORT PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+extern PGDLLEXPORT void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+#endif /* OAUTH_CURL_H */
diff --git a/src/interfaces/libpq-oauth/oauth-utils.c b/src/interfaces/libpq-oauth/oauth-utils.c
new file mode 100644
index 00000000000..1f85a6b0479
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.c
@@ -0,0 +1,202 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.c
+ *
+ * "Glue" helpers providing a copy of some internal APIs from libpq. At
+ * some point in the future, we might be able to deduplicate.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq-oauth/oauth-utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+
+#include "libpq-int.h"
+#include "oauth-utils.h"
+
+#ifndef USE_DYNAMIC_OAUTH
+#error oauth-utils.c is not supported in static builds
+#endif
+
+static libpq_gettext_func libpq_gettext_impl;
+
+pgthreadlock_t pg_g_threadlock;
+conn_errorMessage_func conn_errorMessage;
+
+/*-
+ * Initializes libpq-oauth by setting necessary callbacks.
+ *
+ * The current implementation relies on the following private implementation
+ * details of libpq:
+ *
+ * - pg_g_threadlock: protects libcurl initialization if the underlying Curl
+ * installation is not threadsafe
+ *
+ * - libpq_gettext: translates error messages using libpq's message domain
+ *
+ * - conn->errorMessage: holds translated errors for the connection. This is
+ * handled through a translation shim, which avoids either depending on the
+ * offset of the errorMessage in PGconn, or needing to export the variadic
+ * libpq_append_conn_error().
+ */
+void
+libpq_oauth_init(pgthreadlock_t threadlock_impl,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl)
+{
+ pg_g_threadlock = threadlock_impl;
+ libpq_gettext_impl = gettext_impl;
+ conn_errorMessage = errmsg_impl;
+}
+
+/*
+ * Append a formatted string to the error message buffer of the given
+ * connection, after translating it. This is a copy of libpq's internal API.
+ */
+void
+libpq_append_conn_error(PGconn *conn, const char *fmt,...)
+{
+ int save_errno = errno;
+ bool done;
+ va_list args;
+ PQExpBuffer errorMessage = conn_errorMessage(conn);
+
+ Assert(fmt[strlen(fmt) - 1] != '\n');
+
+ if (PQExpBufferBroken(errorMessage))
+ return; /* already failed */
+
+ /* Loop in case we have to retry after enlarging the buffer. */
+ do
+ {
+ errno = save_errno;
+ va_start(args, fmt);
+ done = appendPQExpBufferVA(errorMessage, libpq_gettext(fmt), args);
+ va_end(args);
+ } while (!done);
+
+ appendPQExpBufferChar(errorMessage, '\n');
+}
+
+#ifdef ENABLE_NLS
+
+/*
+ * A shim that defers to the actual libpq_gettext().
+ */
+char *
+libpq_gettext(const char *msgid)
+{
+ if (!libpq_gettext_impl)
+ {
+ /*
+ * Possible if the libpq build didn't enable NLS but the libpq-oauth
+ * build did. That's an odd mismatch, but we can handle it.
+ *
+ * Note that callers of libpq_gettext() have to treat the return value
+ * as if it were const, because builds without NLS simply pass through
+ * their argument.
+ */
+ return unconstify(char *, msgid);
+ }
+
+ return libpq_gettext_impl(msgid);
+}
+
+#endif /* ENABLE_NLS */
+
+/*
+ * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
+ */
+bool
+oauth_unsafe_debugging_enabled(void)
+{
+ const char *env = getenv("PGOAUTHDEBUG");
+
+ return (env && strcmp(env, "UNSAFE") == 0);
+}
+
+/*
+ * Duplicate SOCK_ERRNO* definitions from libpq-int.h, for use by
+ * pq_block/reset_sigpipe().
+ */
+#ifdef WIN32
+#define SOCK_ERRNO (WSAGetLastError())
+#define SOCK_ERRNO_SET(e) WSASetLastError(e)
+#else
+#define SOCK_ERRNO errno
+#define SOCK_ERRNO_SET(e) (errno = (e))
+#endif
+
+/*
+ * Block SIGPIPE for this thread. This is a copy of libpq's internal API.
+ */
+int
+pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
+{
+ sigset_t sigpipe_sigset;
+ sigset_t sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ /* Block SIGPIPE and save previous mask for later reset */
+ SOCK_ERRNO_SET(pthread_sigmask(SIG_BLOCK, &sigpipe_sigset, osigset));
+ if (SOCK_ERRNO)
+ return -1;
+
+ /* We can have a pending SIGPIPE only if it was blocked before */
+ if (sigismember(osigset, SIGPIPE))
+ {
+ /* Is there a pending SIGPIPE? */
+ if (sigpending(&sigset) != 0)
+ return -1;
+
+ if (sigismember(&sigset, SIGPIPE))
+ *sigpipe_pending = true;
+ else
+ *sigpipe_pending = false;
+ }
+ else
+ *sigpipe_pending = false;
+
+ return 0;
+}
+
+/*
+ * Discard any pending SIGPIPE and reset the signal mask. This is a copy of
+ * libpq's internal API.
+ */
+void
+pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
+{
+ int save_errno = SOCK_ERRNO;
+ int signo;
+ sigset_t sigset;
+
+ /* Clear SIGPIPE only if none was pending */
+ if (got_epipe && !sigpipe_pending)
+ {
+ if (sigpending(&sigset) == 0 &&
+ sigismember(&sigset, SIGPIPE))
+ {
+ sigset_t sigpipe_sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ sigwait(&sigpipe_sigset, &signo);
+ }
+ }
+
+ /* Restore saved block mask */
+ pthread_sigmask(SIG_SETMASK, osigset, NULL);
+
+ SOCK_ERRNO_SET(save_errno);
+}
diff --git a/src/interfaces/libpq-oauth/oauth-utils.h b/src/interfaces/libpq-oauth/oauth-utils.h
new file mode 100644
index 00000000000..e2a9d01237d
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.h
@@ -0,0 +1,38 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.h
+ *
+ * Definitions providing missing libpq internal APIs
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_UTILS_H
+#define OAUTH_UTILS_H
+
+#include "libpq-fe.h"
+#include "pqexpbuffer.h"
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/* Initializes libpq-oauth. */
+extern PGDLLEXPORT void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+
+/* Callback to safely obtain conn->errorMessage from a PGconn. */
+extern conn_errorMessage_func conn_errorMessage;
+
+/* Duplicated APIs, copied from libpq. */
+extern void libpq_append_conn_error(PGconn *conn, const char *fmt,...) pg_attribute_printf(2, 3);
+extern bool oauth_unsafe_debugging_enabled(void);
+extern int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
+extern void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe);
+
+#endif /* OAUTH_UTILS_H */
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 90b0b65db6f..c6fe5fec7f6 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -31,7 +31,6 @@ endif
OBJS = \
$(WIN32RES) \
- fe-auth-oauth.o \
fe-auth-scram.o \
fe-cancel.o \
fe-connect.o \
@@ -64,9 +63,11 @@ OBJS += \
fe-secure-gssapi.o
endif
-ifeq ($(with_libcurl),yes)
-OBJS += fe-auth-oauth-curl.o
-endif
+# The OAuth implementation differs depending on the type of library being built.
+OBJS_STATIC = fe-auth-oauth.o
+
+fe-auth-oauth_shlib.o: override CPPFLAGS_SHLIB += -DUSE_DYNAMIC_OAUTH
+OBJS_SHLIB = fe-auth-oauth_shlib.o
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
@@ -86,7 +87,7 @@ endif
# that are built correctly for use in a shlib.
SHLIB_LINK_INTERNAL = -lpgcommon_shlib -lpgport_shlib
ifneq ($(PORTNAME), win32)
-SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lcurl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
+SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
else
SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi32 -lssl -lsocket -lnsl -lresolv -lintl -lm $(PTHREAD_LIBS), $(LIBS)) $(LDAP_LIBS_FE)
endif
@@ -101,12 +102,26 @@ ifeq ($(with_ssl),openssl)
PKG_CONFIG_REQUIRES_PRIVATE = libssl, libcrypto
endif
+ifeq ($(with_libcurl),yes)
+# libpq.so doesn't link against libcurl, but libpq.a needs libpq-oauth, and
+# libpq-oauth needs libcurl. Put both into *.private.
+PKG_CONFIG_REQUIRES_PRIVATE += libcurl
+%.pc: override SHLIB_LINK_INTERNAL += -lpq-oauth
+endif
+
all: all-lib libpq-refs-stamp
# Shared library stuff
include $(top_srcdir)/src/Makefile.shlib
backend_src = $(top_srcdir)/src/backend
+# Add shlib-/stlib-specific objects.
+$(shlib): override OBJS += $(OBJS_SHLIB)
+$(shlib): $(OBJS_SHLIB)
+
+$(stlib): override OBJS += $(OBJS_STATIC)
+$(stlib): $(OBJS_STATIC)
+
# Check for functions that libpq must not call, currently just exit().
# (Ideally we'd reject abort() too, but there are various scenarios where
# build toolchains insert abort() calls, e.g. to implement assert().)
@@ -115,8 +130,6 @@ backend_src = $(top_srcdir)/src/backend
# which seems to insert references to that even in pure C code. Excluding
# __tsan_func_exit is necessary when using ThreadSanitizer data race detector
# which use this function for instrumentation of function exit.
-# libcurl registers an exit handler in the memory debugging code when running
-# with LeakSanitizer.
# Skip the test when profiling, as gcc may insert exit() calls for that.
# Also skip the test on platforms where libpq infrastructure may be provided
# by statically-linked libraries, as we can't expect them to honor this
@@ -124,7 +137,7 @@ backend_src = $(top_srcdir)/src/backend
libpq-refs-stamp: $(shlib)
ifneq ($(enable_coverage), yes)
ifeq (,$(filter solaris,$(PORTNAME)))
- @if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit -e _atexit | grep exit; then \
+ @if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit | grep exit; then \
echo 'libpq must not be calling any function which invokes exit'; exit 1; \
fi
endif
@@ -138,6 +151,11 @@ fe-misc.o: fe-misc.c $(top_builddir)/src/port/pg_config_paths.h
$(top_builddir)/src/port/pg_config_paths.h:
$(MAKE) -C $(top_builddir)/src/port pg_config_paths.h
+# Use src/common/Makefile's trick for tracking dependencies of shlib-specific
+# objects.
+%_shlib.o: %.c %.o
+ $(CC) $(CFLAGS) $(CFLAGS_SL) $(CPPFLAGS) $(CPPFLAGS_SHLIB) -c $< -o $@
+
install: all installdirs install-lib
$(INSTALL_DATA) $(srcdir)/libpq-fe.h '$(DESTDIR)$(includedir)'
$(INSTALL_DATA) $(srcdir)/libpq-events.h '$(DESTDIR)$(includedir)'
@@ -171,6 +189,6 @@ uninstall: uninstall-lib
clean distclean: clean-lib
$(MAKE) -C test $@
rm -rf tmp_check
- rm -f $(OBJS) pthread.h libpq-refs-stamp
+ rm -f $(OBJS) $(OBJS_SHLIB) $(OBJS_STATIC) pthread.h libpq-refs-stamp
# Might be left over from a Win32 client-only build
rm -f pg_config_paths.h
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index d5143766858..0625cf39e9a 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -210,3 +210,4 @@ PQsetAuthDataHook 207
PQgetAuthDataHook 208
PQdefaultAuthDataHook 209
PQfullProtocolVersion 210
+appendPQExpBufferVA 211
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
index cf1a25e2ccc..ccdd9139cf1 100644
--- a/src/interfaces/libpq/fe-auth-oauth.c
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -15,6 +15,10 @@
#include "postgres_fe.h"
+#ifdef USE_DYNAMIC_OAUTH
+#include <dlfcn.h>
+#endif
+
#include "common/base64.h"
#include "common/hmac.h"
#include "common/jsonapi.h"
@@ -22,6 +26,7 @@
#include "fe-auth.h"
#include "fe-auth-oauth.h"
#include "mb/pg_wchar.h"
+#include "pg_config_paths.h"
/* The exported OAuth callback mechanism. */
static void *oauth_init(PGconn *conn, const char *password,
@@ -721,6 +726,186 @@ cleanup_user_oauth_flow(PGconn *conn)
state->async_ctx = NULL;
}
+/*-------------
+ * Builtin Flow
+ *
+ * There are three potential implementations of use_builtin_flow:
+ *
+ * 1) If the OAuth client is disabled at configuration time, return false.
+ * Dependent clients must provide their own flow.
+ * 2) If the OAuth client is enabled and USE_DYNAMIC_OAUTH is defined, dlopen()
+ * the libpq-oauth plugin and use its implementation.
+ * 3) Otherwise, use flow callbacks that are statically linked into the
+ * executable.
+ */
+
+#if !defined(USE_LIBCURL)
+
+/*
+ * This configuration doesn't support the builtin flow.
+ */
+
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ return false;
+}
+
+#elif defined(USE_DYNAMIC_OAUTH)
+
+/*
+ * Use the builtin flow in the libpq-oauth plugin, which is loaded at runtime.
+ */
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/*
+ * This shim is injected into libpq-oauth so that it doesn't depend on the
+ * offset of conn->errorMessage.
+ *
+ * TODO: look into exporting libpq_append_conn_error or a comparable API from
+ * libpq, instead.
+ */
+static PQExpBuffer
+conn_errorMessage(PGconn *conn)
+{
+ return &conn->errorMessage;
+}
+
+/*
+ * Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
+ * callbacks into the connection's async auth handlers.
+ *
+ * Failure to load here results in a relatively quiet connection error, to
+ * handle the use case where the build supports loading a flow but a user does
+ * not want to install it. Troubleshooting of linker/loader failures can be done
+ * via PGOAUTHDEBUG.
+ */
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ static bool initialized = false;
+ static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
+ int lockerr;
+
+ void (*init) (pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl);
+ PostgresPollingStatusType (*flow) (PGconn *conn);
+ void (*cleanup) (PGconn *conn);
+
+ /*
+ * On macOS only, load the module using its absolute install path; the
+ * standard search behavior is not very helpful for this use case. Unlike
+ * on other platforms, DYLD_LIBRARY_PATH is used as a fallback even with
+ * absolute paths (modulo SIP effects), so tests can continue to work.
+ *
+ * On the other platforms, load the module using only the basename, to
+ * rely on the runtime linker's standard search behavior.
+ */
+ const char *const module_name =
+#if defined(__darwin__)
+ LIBDIR "/libpq-oauth-" PG_MAJORVERSION "-" PG_MINORVERSION DLSUFFIX;
+#else
+ "libpq-oauth-" PG_MAJORVERSION "-" PG_MINORVERSION DLSUFFIX;
+#endif
+
+ state->builtin_flow = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
+ if (!state->builtin_flow)
+ {
+ /*
+ * For end users, this probably isn't an error condition, it just
+ * means the flow isn't installed. Developers and package maintainers
+ * may want to debug this via the PGOAUTHDEBUG envvar, though.
+ *
+ * Note that POSIX dlerror() isn't guaranteed to be threadsafe.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
+
+ return false;
+ }
+
+ if ((init = dlsym(state->builtin_flow, "libpq_oauth_init")) == NULL
+ || (flow = dlsym(state->builtin_flow, "pg_fe_run_oauth_flow")) == NULL
+ || (cleanup = dlsym(state->builtin_flow, "pg_fe_cleanup_oauth_flow")) == NULL)
+ {
+ /*
+ * This is more of an error condition than the one above, but due to
+ * the dlerror() threadsafety issue, lock it behind PGOAUTHDEBUG too.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
+
+ dlclose(state->builtin_flow);
+ return false;
+ }
+
+ /*
+ * Past this point, we do not unload the module. It stays in the process
+ * permanently.
+ */
+
+ /*
+ * We need to inject necessary function pointers into the module. This
+ * only needs to be done once -- even if the pointers are constant,
+ * assigning them while another thread is executing the flows feels like
+ * tempting fate.
+ */
+ if ((lockerr = pthread_mutex_lock(&init_mutex)) != 0)
+ {
+ /* Should not happen... but don't continue if it does. */
+ Assert(false);
+
+ libpq_append_conn_error(conn, "failed to lock mutex (%d)", lockerr);
+ return false;
+ }
+
+ if (!initialized)
+ {
+ init(pg_g_threadlock,
+#ifdef ENABLE_NLS
+ libpq_gettext,
+#else
+ NULL,
+#endif
+ conn_errorMessage);
+
+ initialized = true;
+ }
+
+ pthread_mutex_unlock(&init_mutex);
+
+ /* Set our asynchronous callbacks. */
+ conn->async_auth = flow;
+ conn->cleanup_async_auth = cleanup;
+
+ return true;
+}
+
+#else
+
+/*
+ * Use the builtin flow in libpq-oauth.a (see libpq-oauth/oauth-curl.h).
+ */
+
+extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ /* Set our asynchronous callbacks. */
+ conn->async_auth = pg_fe_run_oauth_flow;
+ conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
+
+ return true;
+}
+
+#endif /* USE_LIBCURL */
+
+
/*
* Chooses an OAuth client flow for the connection, which will retrieve a Bearer
* token for presentation to the server.
@@ -792,18 +977,10 @@ setup_token_request(PGconn *conn, fe_oauth_state *state)
libpq_append_conn_error(conn, "user-defined OAuth flow failed");
goto fail;
}
- else
+ else if (!use_builtin_flow(conn, state))
{
-#if USE_LIBCURL
- /* Hand off to our built-in OAuth flow. */
- conn->async_auth = pg_fe_run_oauth_flow;
- conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
-
-#else
- libpq_append_conn_error(conn, "no custom OAuth flows are available, and libpq was not built with libcurl support");
+ libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
goto fail;
-
-#endif
}
return true;
diff --git a/src/interfaces/libpq/fe-auth-oauth.h b/src/interfaces/libpq/fe-auth-oauth.h
index 3f1a7503a01..687e664475f 100644
--- a/src/interfaces/libpq/fe-auth-oauth.h
+++ b/src/interfaces/libpq/fe-auth-oauth.h
@@ -33,12 +33,13 @@ typedef struct
PGconn *conn;
void *async_ctx;
+
+ void *builtin_flow;
} fe_oauth_state;
-extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
-extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
extern void pqClearOAuthToken(PGconn *conn);
extern bool oauth_unsafe_debugging_enabled(void);
+extern bool use_builtin_flow(PGconn *conn, fe_oauth_state *state);
/* Mechanisms in fe-auth-oauth.c */
extern const pg_fe_sasl_mech pg_oauth_mech;
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index 292fecf3320..a74e885b169 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -38,10 +38,6 @@ if gssapi.found()
)
endif
-if libcurl.found()
- libpq_sources += files('fe-auth-oauth-curl.c')
-endif
-
export_file = custom_target('libpq.exports',
kwargs: gen_export_kwargs,
)
@@ -50,6 +46,9 @@ export_file = custom_target('libpq.exports',
libpq_inc = include_directories('.', '../../port')
libpq_c_args = ['-DSO_MAJOR_VERSION=5']
+# The OAuth implementation differs depending on the type of library being built.
+libpq_so_c_args = ['-DUSE_DYNAMIC_OAUTH']
+
# Not using both_libraries() here as
# 1) resource files should only be in the shared library
# 2) we want the .pc file to include a dependency to {pgport,common}_static for
@@ -70,7 +69,7 @@ libpq_st = static_library('libpq',
libpq_so = shared_library('libpq',
libpq_sources + libpq_so_sources,
include_directories: [libpq_inc, postgres_inc],
- c_args: libpq_c_args,
+ c_args: libpq_c_args + libpq_so_c_args,
c_pch: pch_postgres_fe_h,
version: '5.' + pg_version_major.to_string(),
soversion: host_system != 'windows' ? '5' : '',
@@ -86,12 +85,26 @@ libpq = declare_dependency(
include_directories: [include_directories('.')]
)
+private_deps = [
+ frontend_stlib_code,
+ libpq_deps,
+]
+
+if oauth_flow_supported
+ # libpq.so doesn't link against libcurl, but libpq.a needs libpq-oauth, and
+ # libpq-oauth needs libcurl. Put both into *.private.
+ private_deps += [
+ libpq_oauth_deps,
+ '-lpq-oauth',
+ ]
+endif
+
pkgconfig.generate(
name: 'libpq',
description: 'PostgreSQL libpq library',
url: pg_url,
libraries: libpq,
- libraries_private: [frontend_stlib_code, libpq_deps],
+ libraries_private: private_deps,
)
install_headers(
diff --git a/src/interfaces/libpq/nls.mk b/src/interfaces/libpq/nls.mk
index ae761265852..b87df277d93 100644
--- a/src/interfaces/libpq/nls.mk
+++ b/src/interfaces/libpq/nls.mk
@@ -13,15 +13,21 @@ GETTEXT_FILES = fe-auth.c \
fe-secure-common.c \
fe-secure-gssapi.c \
fe-secure-openssl.c \
- win32.c
-GETTEXT_TRIGGERS = libpq_append_conn_error:2 \
+ win32.c \
+ ../libpq-oauth/oauth-curl.c \
+ ../libpq-oauth/oauth-utils.c
+GETTEXT_TRIGGERS = actx_error:2 \
+ libpq_append_conn_error:2 \
libpq_append_error:2 \
libpq_gettext \
libpq_ngettext:1,2 \
+ oauth_parse_set_error:2 \
pqInternalNotice:2
-GETTEXT_FLAGS = libpq_append_conn_error:2:c-format \
+GETTEXT_FLAGS = actx_error:2:c-format \
+ libpq_append_conn_error:2:c-format \
libpq_append_error:2:c-format \
libpq_gettext:1:pass-c-format \
libpq_ngettext:1:pass-c-format \
libpq_ngettext:2:pass-c-format \
+ oauth_parse_set_error:2:c-format \
pqInternalNotice:2:c-format
diff --git a/src/makefiles/meson.build b/src/makefiles/meson.build
index e3adb5d8dc4..48d01a54dc6 100644
--- a/src/makefiles/meson.build
+++ b/src/makefiles/meson.build
@@ -204,6 +204,8 @@ pgxs_empty = [
'LIBNUMA_CFLAGS', 'LIBNUMA_LIBS',
'LIBURING_CFLAGS', 'LIBURING_LIBS',
+
+ 'LIBCURL_CPPFLAGS', 'LIBCURL_LDFLAGS', 'LIBCURL_LDLIBS',
]
if host_system == 'windows' and cc.get_argument_syntax() != 'msvc'
diff --git a/src/test/modules/oauth_validator/meson.build b/src/test/modules/oauth_validator/meson.build
index 36d1b26369f..e190f9cf15a 100644
--- a/src/test/modules/oauth_validator/meson.build
+++ b/src/test/modules/oauth_validator/meson.build
@@ -78,7 +78,7 @@ tests += {
],
'env': {
'PYTHON': python.path(),
- 'with_libcurl': libcurl.found() ? 'yes' : 'no',
+ 'with_libcurl': oauth_flow_supported ? 'yes' : 'no',
'with_python': 'yes',
},
},
diff --git a/src/test/modules/oauth_validator/t/002_client.pl b/src/test/modules/oauth_validator/t/002_client.pl
index 8dd502f41e1..21d4acc1926 100644
--- a/src/test/modules/oauth_validator/t/002_client.pl
+++ b/src/test/modules/oauth_validator/t/002_client.pl
@@ -110,7 +110,7 @@ if ($ENV{with_libcurl} ne 'yes')
"fails without custom hook installed",
flags => ["--no-hook"],
expected_stderr =>
- qr/no custom OAuth flows are available, and libpq was not built with libcurl support/
+ qr/no OAuth flows are available \(try installing the libpq-oauth package\)/
);
}
--
2.34.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-23 15:39 ` Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Christoph Berg @ 2025-04-23 15:39 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
Re: Jacob Champion
> - Per ABI comment upthread, we are back to major-minor versioning for
> the shared library (e.g. libpq-oauth-18-0.so). 0001 adds the macros
> and makefile variables to make this easy, and 0002 is the bulk of the
> change now.
This will cause problems when programs are running while packages are
updated on disk. That program then tries to dlopen 18-0.so when there
is already 18-1.so installed. Relevant when the first oauth connection
is made way after startup.
This is trading one problem for another, but within-a-major ABI
changes should be much rarer than normal minor updates with
applications restarting only later.
Alternatively, there could be a dedicated SONAME for the plugin that
only changes when necessary, but perhaps the simple "18.so" solution
is good enough.
Christoph
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
@ 2025-04-23 16:07 ` Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-23 16:07 UTC (permalink / raw)
To: Christoph Berg <[email protected]>; Jacob Champion <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Wed, Apr 23, 2025 at 8:39 AM Christoph Berg <[email protected]> wrote:
> This will cause problems when programs are running while packages are
> updated on disk. That program then tries to dlopen 18-0.so when there
> is already 18-1.so installed. Relevant when the first oauth connection
> is made way after startup.
Ugh, good point. This hazard applies to the previous suggestion of
pkglibdir, too, but in that case it would have been silent...
> This is trading one problem for another, but within-a-major ABI
> changes should be much rarer than normal minor updates with
> applications restarting only later.
But the consequences are much worse for a silent ABI mismatch. Imagine
if libpq-oauth examines the wrong pointer inside PGconn for a
security-critical check.
> Alternatively, there could be a dedicated SONAME for the plugin that
> only changes when necessary, but perhaps the simple "18.so" solution
> is good enough.
I don't think SONAME helps us, does it? We're not using it in dlopen().
We could all agree to bump the second number in the filename whenever
there's an internal ABI change. That works from a technical
perspective, but it's hard to test and enforce and... just not forget.
Or, I may still be able to thread the needle with a fuller lookup
table, and remove the dependency on libpq-int.h; it's just not going
to be incredibly pretty. Thinking...
Thanks so much for your continued review!
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-23 16:38 ` Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Christoph Berg @ 2025-04-23 16:38 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
Re: Jacob Champion
> But the consequences are much worse for a silent ABI mismatch. Imagine
> if libpq-oauth examines the wrong pointer inside PGconn for a
> security-critical check.
True.
> > Alternatively, there could be a dedicated SONAME for the plugin that
> > only changes when necessary, but perhaps the simple "18.so" solution
> > is good enough.
>
> I don't think SONAME helps us, does it? We're not using it in dlopen().
That was paraphrasing, with SONAME I meant "library file name that
changes when the ABI changes".
> We could all agree to bump the second number in the filename whenever
> there's an internal ABI change. That works from a technical
> perspective, but it's hard to test and enforce and... just not forget.
It's hopefully not harder than checking ABI compatibility of any other
libpq change, just a different number. If that number is in the
meson.build in the same directory, people should be able to connect
the dots.
Btw, if we have that number, we might as well drop the MAJOR part as
well... apt.pg.o is always shipping the latest libpq5, so major libpq
upgrades while apps are running are going to happen. (But this is just
once a year and much less problematic than minor upgrades and I'm not
going to complain if MAJOR is kept.)
> Or, I may still be able to thread the needle with a fuller lookup
> table, and remove the dependency on libpq-int.h; it's just not going
> to be incredibly pretty. Thinking...
Don't overdesign it...
Christoph
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
@ 2025-04-23 17:46 ` Jacob Champion <[email protected]>
2025-04-24 17:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 2 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-23 17:46 UTC (permalink / raw)
To: Christoph Berg <[email protected]>; Jacob Champion <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Wed, Apr 23, 2025 at 9:38 AM Christoph Berg <[email protected]> wrote:
> > We could all agree to bump the second number in the filename whenever
> > there's an internal ABI change. That works from a technical
> > perspective, but it's hard to test and enforce and... just not forget.
>
> It's hopefully not harder than checking ABI compatibility of any other
> libpq change, just a different number. If that number is in the
> meson.build in the same directory, people should be able to connect
> the dots.
I think it is harder, simply because no one has to do it today, and
that change would sign them up to do it, forever, adding to the
backport checklist. It's one thing if there's a bunch of committers
who pile into the thread right now saying "yes, that's okay", but I
don't really feel comfortable making that decision for them right this
instant.
If we had robust ABI compatibility checks as part of the farm [1], I
think we could do that. Doesn't feel like an 18 thing, though.
> Btw, if we have that number, we might as well drop the MAJOR part as
> well... apt.pg.o is always shipping the latest libpq5, so major libpq
> upgrades while apps are running are going to happen. (But this is just
> once a year and much less problematic than minor upgrades and I'm not
> going to complain if MAJOR is kept.)
I don't want to introduce another testing matrix dimension if I can
avoid it. ("I have this bug where libpq.so.5.18 is using
libpq-oauth.so from PG20 and I had no idea it was doing that and the
problem went away when I restarted and...")
And the intent is for this to be temporary until we have a user-facing
API. If this is the solution we go with, I think it'd wise to prepare
for a -19 version of libpq-oauth, but I'm going to try my best to get
custom modules in ASAP. People are going to be annoyed that v1 of the
feature doesn't let them swap the flow for our utilities. Ideally they
only have to deal with that for a single major release.
Also: since the libpq-oauth-18 and libpq-oauth-19 packages can be
installed side-by-side safely, isn't the upgrade hazard significantly
diminished? (If a user uninstalls the previous libpq-oauth version
while they're still running that version of libpq in memory, _and_
they've somehow never used OAuth until right that instant... it's easy
enough for them to undo their mistake while the application is still
running.)
> > Or, I may still be able to thread the needle with a fuller lookup
> > table, and remove the dependency on libpq-int.h; it's just not going
> > to be incredibly pretty. Thinking...
>
> Don't overdesign it...
Oh, I agree... but my "minimal" ABI designs have all had corner cases
so far. I may need to just bite the bullet.
Are there any readers who feel like an internal ABI version for
`struct pg_conn`, bumped during breaking backports, would be
acceptable? (More definitively: are there any readers who would veto
that?) You're still signing up for delayed errors in the long-lived
client case, so it's not a magic bullet, but the breakage is easy to
see and it's not a crash. The client application "just" has to restart
after a libpq upgrade.
Thanks,
--Jacob
[1] https://www.postgresql.org/message-id/B142EE8A-5D38-48B9-A4BB-82D69A854B55%40justatheory.com
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-24 17:02 ` Jacob Champion <[email protected]>
2025-04-25 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-24 17:02 UTC (permalink / raw)
To: Christoph Berg <[email protected]>; Jacob Champion <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Wed, Apr 23, 2025 at 1:13 PM Christoph Berg <[email protected]> wrote:
> Uhm, so far the plan was to have one "libpq-oauth" package, not several.
I think the system is overconstrained at that point. If you want to
support clients that delay-load the ABI they're compiled against,
_and_ have them continue to work seamlessly after the system has
upgraded the ABI underneath them, without restarting the client... is
there any option other than side-by-side installation?
> Since shipping a single libpq5.deb package for all PG majors has worked well
> for the past decades, I wouldn't want to complicate that now.
I'm not sure if it's possible to ship a client-side module system
without something getting more complicated, though... I'm trying hard
not to overcomplicate it for you, but I also don't think the
complexity is going to remain the same.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-24 17:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-25 20:31 ` Jacob Champion <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-25 20:31 UTC (permalink / raw)
To: Christoph Berg <[email protected]>; Jacob Champion <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Fri, Apr 25, 2025 at 2:03 AM Christoph Berg <[email protected]> wrote:
> My point is that we should be trying to change the ABI-as-coded-in-the-
> filename as rarely as possible.
I agree, but I'm also trying to say I can't unilaterally declare
pieces of our internal structs to be covered by an ABI guarantee.
Maybe the rest of the ABI will never change because it'll be perfect,
but I point to the immediately preceding thread as evidence against
the likelihood of perfection on the first try. I'm trying to build in
air bags so we don't have to regret a mistake.
> Then side-by-side should not be required.
It's still required _during_ an ABI bump, though, if you don't want
things to break. Right?
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-29 00:10 ` Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-29 00:10 UTC (permalink / raw)
To: Christoph Berg <[email protected]>; Jacob Champion <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Wed, Apr 23, 2025 at 10:46 AM Jacob Champion
<[email protected]> wrote:
> Are there any readers who feel like an internal ABI version for
> `struct pg_conn`, bumped during breaking backports, would be
> acceptable? (More definitively: are there any readers who would veto
> that?)
To keep things moving: I assume this is unacceptable. So v10 redirects
every access to a PGconn struct member through a shim, similarly to
how conn->errorMessage was translated in v9. This adds plenty of new
boilerplate, but not a whole lot of complexity. To try to keep us
honest, libpq-int.h has been removed from the libpq-oauth includes.
This will now handle in-place minor version upgrades that swap pg_conn
internals around, so I've gone back to -MAJOR versioning alone.
fe_oauth_state is still exported; it now has an ABI warning above it.
(I figure that's easier to draw a line around during backports,
compared to everything in PGconn. We can still break things there
during major version upgrades.)
Thanks,
--Jacob
1: 5f87f11b18e < -: ----------- Add minor-version counterpart to (PG_)MAJORVERSION
2: 9e37fd7c217 ! 1: e86e93f7ac8 oauth: Move the builtin flow into a separate module
@@ Commit message
The default flow relies on some libpq internals. Some of these can be
safely duplicated (such as the SIGPIPE handlers), but others need to be
- shared between libpq and libpq-oauth for thread-safety. To avoid exporting
- these internals to all libpq clients forever, these dependencies are
- instead injected from the libpq side via an initialization function.
- This also lets libpq communicate the offset of conn->errorMessage to
- libpq-oauth, so that we can function without crashing if the module on
- the search path came from a different build of Postgres.
+ shared between libpq and libpq-oauth for thread-safety. To avoid
+ exporting these internals to all libpq clients forever, these
+ dependencies are instead injected from the libpq side via an
+ initialization function. This also lets libpq communicate the offsets of
+ PGconn struct members to libpq-oauth, so that we can function without
+ crashing if the module on the search path came from a different build of
+ Postgres. (A minor-version upgrade could swap the libpq-oauth module out
+ from under a long-running libpq client before it does its first load of
+ the OAuth flow.)
This ABI is considered "private". The module has no SONAME or version
- symlinks, and it's named libpq-oauth-<major>-<minor>.so to avoid mixing
- and matching across Postgres versions, in case internal struct order
- needs to change. (Future improvements may promote this "OAuth flow
- plugin" to a first-class concept, at which point we would need a public
- API to replace this anyway.)
+ symlinks, and it's named libpq-oauth-<major>.so to avoid mixing and
+ matching across Postgres versions. (Future improvements may promote this
+ "OAuth flow plugin" to a first-class concept, at which point we would
+ need a public API to replace this anyway.)
Additionally, NLS support for error messages in b3f0be788a was
incomplete, because the new error macros weren't being scanned by
@@ src/interfaces/libpq-oauth/Makefile (new)
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
-+NAME = pq-oauth-$(MAJORVERSION)-$(MINORVERSION)
++NAME = pq-oauth-$(MAJORVERSION)
+
+# Force the name "libpq-oauth" for both the static and shared libraries. The
+# staticlib doesn't need version information in its name.
@@ src/interfaces/libpq-oauth/README (new)
+= Load-Time ABI =
+
+This module ABI is an internal implementation detail, so it's subject to change
-+across releases; the name of the module (libpq-oauth-MAJOR-MINOR) reflects this.
++across major releases; the name of the module (libpq-oauth-MAJOR) reflects this.
+The module exports the following symbols:
+
+- PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
@@ src/interfaces/libpq-oauth/README (new)
+
+- void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
-+ conn_errorMessage_func errmsg_impl);
++ conn_errorMessage_func errmsg_impl,
++ conn_oauth_client_id_func clientid_impl,
++ conn_oauth_client_secret_func clientsecret_impl,
++ conn_oauth_discovery_uri_func discoveryuri_impl,
++ conn_oauth_issuer_id_func issuerid_impl,
++ conn_oauth_scope_func scope_impl,
++ conn_sasl_state_func saslstate_impl,
++ set_conn_altsock_func setaltsock_impl,
++ set_conn_oauth_token_func settoken_impl);
+
+At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
+libpq_gettext(), which must be injected by libpq using this initialization
+function before the flow is run.
+
-+It also relies on libpq to expose conn->errorMessage, via the errmsg_impl. This
-+is done to decouple the module ABI from the offset of errorMessage, which can
-+change positions depending on configure-time options. This way we can safely
-+search the standard dlopen() paths (e.g. RPATH, LD_LIBRARY_PATH, the SO cache)
-+for an implementation module to use, even if that module wasn't compiled at the
-+same time as libpq.
++It also relies on access to several members of the PGconn struct. Not only can
++these change positions across minor versions, but the offsets aren't necessarily
++stable within a single minor release (conn->errorMessage, for instance, can
++change offsets depending on configure-time options). Therefore the necessary
++accessors (named conn_*) and mutators (set_conn_*) are injected here. With this
++approach, we can safely search the standard dlopen() paths (e.g. RPATH,
++LD_LIBRARY_PATH, the SO cache) for an implementation module to use, even if that
++module wasn't compiled at the same time as libpq -- which becomes especially
++important during "live upgrade" situations where a running libpq application has
++the libpq-oauth module updated out from under it before it's first loaded from
++disk.
+
+= Static Build =
+
+The static library libpq.a does not perform any dynamic loading. If the builtin
+flow is enabled, the application is expected to link against libpq-oauth.a
-+directly to provide the necessary symbols.
++directly to provide the necessary symbols. (libpq.a and libpq-oauth.a must be
++part of the same build. Unlike the dynamic module, there are no translation
++shims provided.)
## src/interfaces/libpq-oauth/exports.txt (new) ##
@@
@@ src/interfaces/libpq-oauth/meson.build (new)
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
-+libpq_oauth_name = 'libpq-oauth-@0@-@1@'.format(pg_version_major, pg_version_minor)
++libpq_oauth_name = 'libpq-oauth-@0@'.format(pg_version_major)
+
+libpq_oauth_so = shared_module(libpq_oauth_name,
+ libpq_oauth_sources + libpq_oauth_so_sources,
@@ src/interfaces/libpq/fe-auth-oauth-curl.c => src/interfaces/libpq-oauth/oauth-cu
-#include <unistd.h>
#include "common/jsonapi.h"
- #include "fe-auth.h"
+-#include "fe-auth.h"
#include "fe-auth-oauth.h"
-#include "libpq-int.h"
#include "mb/pg_wchar.h"
+#include "oauth-curl.h"
++
+#ifdef USE_DYNAMIC_OAUTH
++
++/*
++ * The module build is decoupled from libpq-int.h, to try to avoid inadvertent
++ * ABI breaks during minor version bumps. Replacements for the missing internals
++ * are provided by oauth-utils.
++ */
+#include "oauth-utils.h"
++
++#else /* !USE_DYNAMIC_OAUTH */
++
++/*
++ * Static builds may rely on PGconn offsets directly. Keep these aligned with
++ * the bank of callbacks in oauth-utils.h.
++ */
++#include "libpq-int.h"
++
++#define conn_errorMessage(CONN) (&CONN->errorMessage)
++#define conn_oauth_client_id(CONN) (CONN->oauth_client_id)
++#define conn_oauth_client_secret(CONN) (CONN->oauth_client_secret)
++#define conn_oauth_discovery_uri(CONN) (CONN->oauth_discovery_uri)
++#define conn_oauth_issuer_id(CONN) (CONN->oauth_issuer_id)
++#define conn_oauth_scope(CONN) (CONN->oauth_scope)
++#define conn_sasl_state(CONN) (CONN->sasl_state)
++
++#define set_conn_altsock(CONN, VAL) do { CONN->altsock = VAL; } while (0)
++#define set_conn_oauth_token(CONN, VAL) do { CONN->oauth_token = VAL; } while (0)
++
++#endif /* !USE_DYNAMIC_OAUTH */
++
++/* One final guardrail against accidental inclusion... */
++#if defined(USE_DYNAMIC_OAUTH) && defined(LIBPQ_INT_H)
++#error do not rely on libpq-int.h in libpq-oauth.so
+#endif
/*
* It's generally prudent to set a maximum response size to buffer in memory,
+@@ src/interfaces/libpq-oauth/oauth-curl.c: free_async_ctx(PGconn *conn, struct async_ctx *actx)
+ void
+ pg_fe_cleanup_oauth_flow(PGconn *conn)
+ {
+- fe_oauth_state *state = conn->sasl_state;
++ fe_oauth_state *state = conn_sasl_state(conn);
+
+ if (state->async_ctx)
+ {
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_cleanup_oauth_flow(PGconn *conn)
+ state->async_ctx = NULL;
+ }
+
+- conn->altsock = PGINVALID_SOCKET;
++ set_conn_altsock(conn, PGINVALID_SOCKET);
+ }
+
+ /*
@@ src/interfaces/libpq-oauth/oauth-curl.c: parse_access_token(struct async_ctx *actx, struct token *tok)
static bool
setup_multiplexer(struct async_ctx *actx)
@@ src/interfaces/libpq-oauth/oauth-curl.c: timer_expired(struct async_ctx *actx)
}
/*
+@@ src/interfaces/libpq-oauth/oauth-curl.c: static bool
+ check_issuer(struct async_ctx *actx, PGconn *conn)
+ {
+ const struct provider *provider = &actx->provider;
++ const char *oauth_issuer_id = conn_oauth_issuer_id(conn);
+
+- Assert(conn->oauth_issuer_id); /* ensured by setup_oauth_parameters() */
++ Assert(oauth_issuer_id); /* ensured by setup_oauth_parameters() */
+ Assert(provider->issuer); /* ensured by parse_provider() */
+
+ /*---
+@@ src/interfaces/libpq-oauth/oauth-curl.c: check_issuer(struct async_ctx *actx, PGconn *conn)
+ * sent to. This comparison MUST use simple string comparison as defined
+ * in Section 6.2.1 of [RFC3986].
+ */
+- if (strcmp(conn->oauth_issuer_id, provider->issuer) != 0)
++ if (strcmp(oauth_issuer_id, provider->issuer) != 0)
+ {
+ actx_error(actx,
+ "the issuer identifier (%s) does not match oauth_issuer (%s)",
+- provider->issuer, conn->oauth_issuer_id);
++ provider->issuer, oauth_issuer_id);
+ return false;
+ }
+
+@@ src/interfaces/libpq-oauth/oauth-curl.c: check_for_device_flow(struct async_ctx *actx)
+ static bool
+ add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *conn)
+ {
++ const char *oauth_client_id = conn_oauth_client_id(conn);
++ const char *oauth_client_secret = conn_oauth_client_secret(conn);
++
+ bool success = false;
+ char *username = NULL;
+ char *password = NULL;
+
+- if (conn->oauth_client_secret) /* Zero-length secrets are permitted! */
++ if (oauth_client_secret) /* Zero-length secrets are permitted! */
+ {
+ /*----
+ * Use HTTP Basic auth to send the client_id and secret. Per RFC 6749,
+@@ src/interfaces/libpq-oauth/oauth-curl.c: add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *c
+ * would it be redundant, but some providers in the wild (e.g. Okta)
+ * refuse to accept it.
+ */
+- username = urlencode(conn->oauth_client_id);
+- password = urlencode(conn->oauth_client_secret);
++ username = urlencode(oauth_client_id);
++ password = urlencode(oauth_client_secret);
+
+ if (!username || !password)
+ {
+@@ src/interfaces/libpq-oauth/oauth-curl.c: add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *c
+ * If we're not otherwise authenticating, client_id is REQUIRED in the
+ * request body.
+ */
+- build_urlencoded(reqbody, "client_id", conn->oauth_client_id);
++ build_urlencoded(reqbody, "client_id", oauth_client_id);
+
+ CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_NONE, goto cleanup);
+ actx->used_basic_auth = false;
+@@ src/interfaces/libpq-oauth/oauth-curl.c: cleanup:
+ static bool
+ start_device_authz(struct async_ctx *actx, PGconn *conn)
+ {
++ const char *oauth_scope = conn_oauth_scope(conn);
+ const char *device_authz_uri = actx->provider.device_authorization_endpoint;
+ PQExpBuffer work_buffer = &actx->work_data;
+
+- Assert(conn->oauth_client_id); /* ensured by setup_oauth_parameters() */
++ Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
+ Assert(device_authz_uri); /* ensured by check_for_device_flow() */
+
+ /* Construct our request body. */
+ resetPQExpBuffer(work_buffer);
+- if (conn->oauth_scope && conn->oauth_scope[0])
+- build_urlencoded(work_buffer, "scope", conn->oauth_scope);
++ if (oauth_scope && oauth_scope[0])
++ build_urlencoded(work_buffer, "scope", oauth_scope);
+
+ if (!add_client_identification(actx, work_buffer, conn))
+ return false;
+@@ src/interfaces/libpq-oauth/oauth-curl.c: start_token_request(struct async_ctx *actx, PGconn *conn)
+ const char *device_code = actx->authz.device_code;
+ PQExpBuffer work_buffer = &actx->work_data;
+
+- Assert(conn->oauth_client_id); /* ensured by setup_oauth_parameters() */
++ Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
+ Assert(token_uri); /* ensured by parse_provider() */
+ Assert(device_code); /* ensured by parse_device_authz() */
+
@@ src/interfaces/libpq-oauth/oauth-curl.c: prompt_user(struct async_ctx *actx, PGconn *conn)
.verification_uri_complete = actx->authz.verification_uri_complete,
.expires_in = actx->authz.expires_in,
@@ src/interfaces/libpq-oauth/oauth-curl.c: prompt_user(struct async_ctx *actx, PGc
if (!res)
{
-@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+@@ src/interfaces/libpq-oauth/oauth-curl.c: done:
+ static PostgresPollingStatusType
+ pg_fe_run_oauth_flow_impl(PGconn *conn)
{
- fe_oauth_state *state = conn->sasl_state;
+- fe_oauth_state *state = conn->sasl_state;
++ fe_oauth_state *state = conn_sasl_state(conn);
struct async_ctx *actx;
++ char *oauth_token = NULL;
+ PQExpBuffer errbuf;
if (!initialize_curl(conn))
return PGRES_POLLING_FAILED;
@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ do
+ {
+ /* By default, the multiplexer is the altsock. Reassign as desired. */
+- conn->altsock = actx->mux;
++ set_conn_altsock(conn, actx->mux);
+
+ switch (actx->step)
+ {
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ */
+ if (!timer_expired(actx))
+ {
+- conn->altsock = actx->timerfd;
++ set_conn_altsock(conn, actx->timerfd);
+ return PGRES_POLLING_READING;
+ }
- error_return:
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ {
+ case OAUTH_STEP_INIT:
+ actx->errctx = "failed to fetch OpenID discovery document";
+- if (!start_discovery(actx, conn->oauth_discovery_uri))
++ if (!start_discovery(actx, conn_oauth_discovery_uri(conn)))
+ goto error_return;
-+ /*
-+ * For the dynamic module build, we can't safely rely on the offset of
-+ * conn->errorMessage, since it depends on build options like USE_SSL et
-+ * al. libpq gives us a translator function instead.
-+ */
-+#ifdef USE_DYNAMIC_OAUTH
+ actx->step = OAUTH_STEP_DISCOVERY;
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ break;
+
+ case OAUTH_STEP_TOKEN_REQUEST:
+- if (!handle_token_response(actx, &conn->oauth_token))
++ if (!handle_token_response(actx, &oauth_token))
+ goto error_return;
+
++ /*
++ * Hook any oauth_token into the PGconn immediately so that
++ * the allocation isn't lost in case of an error.
++ */
++ set_conn_oauth_token(conn, oauth_token);
++
+ if (!actx->user_prompted)
+ {
+ /*
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ actx->user_prompted = true;
+ }
+
+- if (conn->oauth_token)
++ if (oauth_token)
+ break; /* done! */
+
+ /*
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ * the client wait directly on the timerfd rather than the
+ * multiplexer.
+ */
+- conn->altsock = actx->timerfd;
++ set_conn_altsock(conn, actx->timerfd);
+
+ actx->step = OAUTH_STEP_WAIT_INTERVAL;
+ actx->running = 1;
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ * point, actx->running will be set. But there are some corner cases
+ * where we can immediately loop back around; see start_request().
+ */
+- } while (!conn->oauth_token && !actx->running);
++ } while (!oauth_token && !actx->running);
+
+ /* If we've stored a token, we're done. Otherwise come back later. */
+- return conn->oauth_token ? PGRES_POLLING_OK : PGRES_POLLING_READING;
++ return oauth_token ? PGRES_POLLING_OK : PGRES_POLLING_READING;
+
+ error_return:
+ errbuf = conn_errorMessage(conn);
-+#else
-+ errbuf = &conn->errorMessage;
-+#endif
-+
+
/*
* Assemble the three parts of our error: context, body, and detail. See
* also the documentation for struct async_ctx.
@@ src/interfaces/libpq-oauth/oauth-utils.c (new)
+
+#include <signal.h>
+
-+#include "libpq-int.h"
+#include "oauth-utils.h"
+
+#ifndef USE_DYNAMIC_OAUTH
+#error oauth-utils.c is not supported in static builds
+#endif
+
-+static libpq_gettext_func libpq_gettext_impl;
++#ifdef LIBPQ_INT_H
++#error do not rely on libpq-int.h in libpq-oauth
++#endif
++
++/*
++ * Function pointers set by libpq_oauth_init().
++ */
+
+pgthreadlock_t pg_g_threadlock;
++static libpq_gettext_func libpq_gettext_impl;
++
+conn_errorMessage_func conn_errorMessage;
++conn_oauth_client_id_func conn_oauth_client_id;
++conn_oauth_client_secret_func conn_oauth_client_secret;
++conn_oauth_discovery_uri_func conn_oauth_discovery_uri;
++conn_oauth_issuer_id_func conn_oauth_issuer_id;
++conn_oauth_scope_func conn_oauth_scope;
++conn_sasl_state_func conn_sasl_state;
++
++set_conn_altsock_func set_conn_altsock;
++set_conn_oauth_token_func set_conn_oauth_token;
+
+/*-
+ * Initializes libpq-oauth by setting necessary callbacks.
@@ src/interfaces/libpq-oauth/oauth-utils.c (new)
+ *
+ * - libpq_gettext: translates error messages using libpq's message domain
+ *
-+ * - conn->errorMessage: holds translated errors for the connection. This is
-+ * handled through a translation shim, which avoids either depending on the
-+ * offset of the errorMessage in PGconn, or needing to export the variadic
-+ * libpq_append_conn_error().
++ * The implementation also needs access to several members of the PGconn struct,
++ * which are not guaranteed to stay in place across minor versions. Accessors
++ * (named conn_*) and mutators (named set_conn_*) are injected here.
+ */
+void
+libpq_oauth_init(pgthreadlock_t threadlock_impl,
+ libpq_gettext_func gettext_impl,
-+ conn_errorMessage_func errmsg_impl)
++ conn_errorMessage_func errmsg_impl,
++ conn_oauth_client_id_func clientid_impl,
++ conn_oauth_client_secret_func clientsecret_impl,
++ conn_oauth_discovery_uri_func discoveryuri_impl,
++ conn_oauth_issuer_id_func issuerid_impl,
++ conn_oauth_scope_func scope_impl,
++ conn_sasl_state_func saslstate_impl,
++ set_conn_altsock_func setaltsock_impl,
++ set_conn_oauth_token_func settoken_impl)
+{
+ pg_g_threadlock = threadlock_impl;
+ libpq_gettext_impl = gettext_impl;
+ conn_errorMessage = errmsg_impl;
++ conn_oauth_client_id = clientid_impl;
++ conn_oauth_client_secret = clientsecret_impl;
++ conn_oauth_discovery_uri = discoveryuri_impl;
++ conn_oauth_issuer_id = issuerid_impl;
++ conn_oauth_scope = scope_impl;
++ conn_sasl_state = saslstate_impl;
++ set_conn_altsock = setaltsock_impl;
++ set_conn_oauth_token = settoken_impl;
+}
+
+/*
@@ src/interfaces/libpq-oauth/oauth-utils.h (new)
+#ifndef OAUTH_UTILS_H
+#define OAUTH_UTILS_H
+
++#include "fe-auth-oauth.h"
+#include "libpq-fe.h"
+#include "pqexpbuffer.h"
+
++/*
++ * A bank of callbacks to safely access members of PGconn, which are all passed
++ * to libpq_oauth_init() by libpq.
++ *
++ * Keep these aligned with the definitions in fe-auth-oauth.c as well as the
++ * static declarations in oauth-curl.c.
++ */
++#define DECLARE_GETTER(TYPE, MEMBER) \
++ typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
++ extern conn_ ## MEMBER ## _func conn_ ## MEMBER;
++
++#define DECLARE_SETTER(TYPE, MEMBER) \
++ typedef void (*set_conn_ ## MEMBER ## _func) (PGconn *conn, TYPE val); \
++ extern set_conn_ ## MEMBER ## _func set_conn_ ## MEMBER;
++
++DECLARE_GETTER(PQExpBuffer, errorMessage);
++DECLARE_GETTER(char *, oauth_client_id);
++DECLARE_GETTER(char *, oauth_client_secret);
++DECLARE_GETTER(char *, oauth_discovery_uri);
++DECLARE_GETTER(char *, oauth_issuer_id);
++DECLARE_GETTER(char *, oauth_scope);
++DECLARE_GETTER(fe_oauth_state *, sasl_state);
++
++DECLARE_SETTER(pgsocket, altsock);
++DECLARE_SETTER(char *, oauth_token);
++
++#undef DECLARE_GETTER
++#undef DECLARE_SETTER
++
+typedef char *(*libpq_gettext_func) (const char *msgid);
-+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/* Initializes libpq-oauth. */
+extern PGDLLEXPORT void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
-+ conn_errorMessage_func errmsg_impl);
++ conn_errorMessage_func errmsg_impl,
++ conn_oauth_client_id_func clientid_impl,
++ conn_oauth_client_secret_func clientsecret_impl,
++ conn_oauth_discovery_uri_func discoveryuri_impl,
++ conn_oauth_issuer_id_func issuerid_impl,
++ conn_oauth_scope_func scope_impl,
++ conn_sasl_state_func saslstate_impl,
++ set_conn_altsock_func setaltsock_impl,
++ set_conn_oauth_token_func settoken_impl);
+
-+/* Callback to safely obtain conn->errorMessage from a PGconn. */
-+extern conn_errorMessage_func conn_errorMessage;
++/*
++ * Duplicated APIs, copied from libpq (primarily libpq-int.h, which we cannot
++ * depend on here).
++ */
++
++typedef enum
++{
++ PG_BOOL_UNKNOWN = 0, /* Currently unknown */
++ PG_BOOL_YES, /* Yes (true) */
++ PG_BOOL_NO /* No (false) */
++} PGTernaryBool;
+
-+/* Duplicated APIs, copied from libpq. */
+extern void libpq_append_conn_error(PGconn *conn, const char *fmt,...) pg_attribute_printf(2, 3);
+extern bool oauth_unsafe_debugging_enabled(void);
+extern int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
+extern void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe);
+
++#ifdef ENABLE_NLS
++extern char *libpq_gettext(const char *msgid) pg_attribute_format_arg(1);
++#else
++#define libpq_gettext(x) (x)
++#endif
++
++extern pgthreadlock_t pg_g_threadlock;
++
++#define pglock_thread() pg_g_threadlock(true)
++#define pgunlock_thread() pg_g_threadlock(false)
++
+#endif /* OAUTH_UTILS_H */
## src/interfaces/libpq/Makefile ##
@@ src/interfaces/libpq/fe-auth-oauth.c: cleanup_user_oauth_flow(PGconn *conn)
+ */
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
-+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/*
-+ * This shim is injected into libpq-oauth so that it doesn't depend on the
-+ * offset of conn->errorMessage.
-+ *
-+ * TODO: look into exporting libpq_append_conn_error or a comparable API from
-+ * libpq, instead.
++ * Define accessor/mutator shims to inject into libpq-oauth, so that it doesn't
++ * depend on the offsets within PGconn. (These have changed during minor version
++ * updates in the past.)
+ */
-+static PQExpBuffer
-+conn_errorMessage(PGconn *conn)
-+{
-+ return &conn->errorMessage;
-+}
++
++#define DEFINE_GETTER(TYPE, MEMBER) \
++ typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
++ static TYPE conn_ ## MEMBER(PGconn *conn) { return conn->MEMBER; }
++
++/* Like DEFINE_GETTER, but returns a pointer to the member. */
++#define DEFINE_GETTER_P(TYPE, MEMBER) \
++ typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
++ static TYPE conn_ ## MEMBER(PGconn *conn) { return &conn->MEMBER; }
++
++#define DEFINE_SETTER(TYPE, MEMBER) \
++ typedef void (*set_conn_ ## MEMBER ## _func) (PGconn *conn, TYPE val); \
++ static void set_conn_ ## MEMBER(PGconn *conn, TYPE val) { conn->MEMBER = val; }
++
++DEFINE_GETTER_P(PQExpBuffer, errorMessage);
++DEFINE_GETTER(char *, oauth_client_id);
++DEFINE_GETTER(char *, oauth_client_secret);
++DEFINE_GETTER(char *, oauth_discovery_uri);
++DEFINE_GETTER(char *, oauth_issuer_id);
++DEFINE_GETTER(char *, oauth_scope);
++DEFINE_GETTER(fe_oauth_state *, sasl_state);
++
++DEFINE_SETTER(pgsocket, altsock);
++DEFINE_SETTER(char *, oauth_token);
+
+/*
+ * Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
@@ src/interfaces/libpq/fe-auth-oauth.c: cleanup_user_oauth_flow(PGconn *conn)
+
+ void (*init) (pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
-+ conn_errorMessage_func errmsg_impl);
++ conn_errorMessage_func errmsg_impl,
++ conn_oauth_client_id_func clientid_impl,
++ conn_oauth_client_secret_func clientsecret_impl,
++ conn_oauth_discovery_uri_func discoveryuri_impl,
++ conn_oauth_issuer_id_func issuerid_impl,
++ conn_oauth_scope_func scope_impl,
++ conn_sasl_state_func saslstate_impl,
++ set_conn_altsock_func setaltsock_impl,
++ set_conn_oauth_token_func settoken_impl);
+ PostgresPollingStatusType (*flow) (PGconn *conn);
+ void (*cleanup) (PGconn *conn);
+
@@ src/interfaces/libpq/fe-auth-oauth.c: cleanup_user_oauth_flow(PGconn *conn)
+ */
+ const char *const module_name =
+#if defined(__darwin__)
-+ LIBDIR "/libpq-oauth-" PG_MAJORVERSION "-" PG_MINORVERSION DLSUFFIX;
++ LIBDIR "/libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
+#else
-+ "libpq-oauth-" PG_MAJORVERSION "-" PG_MINORVERSION DLSUFFIX;
++ "libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
+#endif
+
+ state->builtin_flow = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
@@ src/interfaces/libpq/fe-auth-oauth.c: cleanup_user_oauth_flow(PGconn *conn)
+#else
+ NULL,
+#endif
-+ conn_errorMessage);
++ conn_errorMessage,
++ conn_oauth_client_id,
++ conn_oauth_client_secret,
++ conn_oauth_discovery_uri,
++ conn_oauth_issuer_id,
++ conn_oauth_scope,
++ conn_sasl_state,
++ set_conn_altsock,
++ set_conn_oauth_token);
+
+ initialized = true;
+ }
@@ src/interfaces/libpq/fe-auth-oauth.c: setup_token_request(PGconn *conn, fe_oauth
return true;
## src/interfaces/libpq/fe-auth-oauth.h ##
-@@ src/interfaces/libpq/fe-auth-oauth.h: typedef struct
+@@
+ #ifndef FE_AUTH_OAUTH_H
+ #define FE_AUTH_OAUTH_H
+
++#include "fe-auth-sasl.h"
+ #include "libpq-fe.h"
+-#include "libpq-int.h"
+
+
+ enum fe_oauth_step
+@@ src/interfaces/libpq/fe-auth-oauth.h: enum fe_oauth_step
+ FE_OAUTH_SERVER_ERROR,
+ };
+
++/*
++ * This struct is exported to the libpq-oauth module. If changes are needed
++ * during backports to stable branches, please keep ABI compatibility (no
++ * changes to existing members, add new members at the end, etc.).
++ */
+ typedef struct
+ {
+ enum fe_oauth_step step;
PGconn *conn;
void *async_ctx;
Attachments:
[text/plain] since-v9.diff.txt (28.4K, ../../CAOYmi+=UsSnLM4K+hYkhrezpQROQ5jr=72feAkQ0Te8GJf3Fbg@mail.gmail.com/2-since-v9.diff.txt)
download | inline:
1: 5f87f11b18e < -: ----------- Add minor-version counterpart to (PG_)MAJORVERSION
2: 9e37fd7c217 ! 1: e86e93f7ac8 oauth: Move the builtin flow into a separate module
@@ Commit message
The default flow relies on some libpq internals. Some of these can be
safely duplicated (such as the SIGPIPE handlers), but others need to be
- shared between libpq and libpq-oauth for thread-safety. To avoid exporting
- these internals to all libpq clients forever, these dependencies are
- instead injected from the libpq side via an initialization function.
- This also lets libpq communicate the offset of conn->errorMessage to
- libpq-oauth, so that we can function without crashing if the module on
- the search path came from a different build of Postgres.
+ shared between libpq and libpq-oauth for thread-safety. To avoid
+ exporting these internals to all libpq clients forever, these
+ dependencies are instead injected from the libpq side via an
+ initialization function. This also lets libpq communicate the offsets of
+ PGconn struct members to libpq-oauth, so that we can function without
+ crashing if the module on the search path came from a different build of
+ Postgres. (A minor-version upgrade could swap the libpq-oauth module out
+ from under a long-running libpq client before it does its first load of
+ the OAuth flow.)
This ABI is considered "private". The module has no SONAME or version
- symlinks, and it's named libpq-oauth-<major>-<minor>.so to avoid mixing
- and matching across Postgres versions, in case internal struct order
- needs to change. (Future improvements may promote this "OAuth flow
- plugin" to a first-class concept, at which point we would need a public
- API to replace this anyway.)
+ symlinks, and it's named libpq-oauth-<major>.so to avoid mixing and
+ matching across Postgres versions. (Future improvements may promote this
+ "OAuth flow plugin" to a first-class concept, at which point we would
+ need a public API to replace this anyway.)
Additionally, NLS support for error messages in b3f0be788a was
incomplete, because the new error macros weren't being scanned by
@@ src/interfaces/libpq-oauth/Makefile (new)
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
-+NAME = pq-oauth-$(MAJORVERSION)-$(MINORVERSION)
++NAME = pq-oauth-$(MAJORVERSION)
+
+# Force the name "libpq-oauth" for both the static and shared libraries. The
+# staticlib doesn't need version information in its name.
@@ src/interfaces/libpq-oauth/README (new)
+= Load-Time ABI =
+
+This module ABI is an internal implementation detail, so it's subject to change
-+across releases; the name of the module (libpq-oauth-MAJOR-MINOR) reflects this.
++across major releases; the name of the module (libpq-oauth-MAJOR) reflects this.
+The module exports the following symbols:
+
+- PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
@@ src/interfaces/libpq-oauth/README (new)
+
+- void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
-+ conn_errorMessage_func errmsg_impl);
++ conn_errorMessage_func errmsg_impl,
++ conn_oauth_client_id_func clientid_impl,
++ conn_oauth_client_secret_func clientsecret_impl,
++ conn_oauth_discovery_uri_func discoveryuri_impl,
++ conn_oauth_issuer_id_func issuerid_impl,
++ conn_oauth_scope_func scope_impl,
++ conn_sasl_state_func saslstate_impl,
++ set_conn_altsock_func setaltsock_impl,
++ set_conn_oauth_token_func settoken_impl);
+
+At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
+libpq_gettext(), which must be injected by libpq using this initialization
+function before the flow is run.
+
-+It also relies on libpq to expose conn->errorMessage, via the errmsg_impl. This
-+is done to decouple the module ABI from the offset of errorMessage, which can
-+change positions depending on configure-time options. This way we can safely
-+search the standard dlopen() paths (e.g. RPATH, LD_LIBRARY_PATH, the SO cache)
-+for an implementation module to use, even if that module wasn't compiled at the
-+same time as libpq.
++It also relies on access to several members of the PGconn struct. Not only can
++these change positions across minor versions, but the offsets aren't necessarily
++stable within a single minor release (conn->errorMessage, for instance, can
++change offsets depending on configure-time options). Therefore the necessary
++accessors (named conn_*) and mutators (set_conn_*) are injected here. With this
++approach, we can safely search the standard dlopen() paths (e.g. RPATH,
++LD_LIBRARY_PATH, the SO cache) for an implementation module to use, even if that
++module wasn't compiled at the same time as libpq -- which becomes especially
++important during "live upgrade" situations where a running libpq application has
++the libpq-oauth module updated out from under it before it's first loaded from
++disk.
+
+= Static Build =
+
+The static library libpq.a does not perform any dynamic loading. If the builtin
+flow is enabled, the application is expected to link against libpq-oauth.a
-+directly to provide the necessary symbols.
++directly to provide the necessary symbols. (libpq.a and libpq-oauth.a must be
++part of the same build. Unlike the dynamic module, there are no translation
++shims provided.)
## src/interfaces/libpq-oauth/exports.txt (new) ##
@@
@@ src/interfaces/libpq-oauth/meson.build (new)
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
-+libpq_oauth_name = 'libpq-oauth-@0@-@1@'.format(pg_version_major, pg_version_minor)
++libpq_oauth_name = 'libpq-oauth-@0@'.format(pg_version_major)
+
+libpq_oauth_so = shared_module(libpq_oauth_name,
+ libpq_oauth_sources + libpq_oauth_so_sources,
@@ src/interfaces/libpq/fe-auth-oauth-curl.c => src/interfaces/libpq-oauth/oauth-cu
-#include <unistd.h>
#include "common/jsonapi.h"
- #include "fe-auth.h"
+-#include "fe-auth.h"
#include "fe-auth-oauth.h"
-#include "libpq-int.h"
#include "mb/pg_wchar.h"
+#include "oauth-curl.h"
++
+#ifdef USE_DYNAMIC_OAUTH
++
++/*
++ * The module build is decoupled from libpq-int.h, to try to avoid inadvertent
++ * ABI breaks during minor version bumps. Replacements for the missing internals
++ * are provided by oauth-utils.
++ */
+#include "oauth-utils.h"
++
++#else /* !USE_DYNAMIC_OAUTH */
++
++/*
++ * Static builds may rely on PGconn offsets directly. Keep these aligned with
++ * the bank of callbacks in oauth-utils.h.
++ */
++#include "libpq-int.h"
++
++#define conn_errorMessage(CONN) (&CONN->errorMessage)
++#define conn_oauth_client_id(CONN) (CONN->oauth_client_id)
++#define conn_oauth_client_secret(CONN) (CONN->oauth_client_secret)
++#define conn_oauth_discovery_uri(CONN) (CONN->oauth_discovery_uri)
++#define conn_oauth_issuer_id(CONN) (CONN->oauth_issuer_id)
++#define conn_oauth_scope(CONN) (CONN->oauth_scope)
++#define conn_sasl_state(CONN) (CONN->sasl_state)
++
++#define set_conn_altsock(CONN, VAL) do { CONN->altsock = VAL; } while (0)
++#define set_conn_oauth_token(CONN, VAL) do { CONN->oauth_token = VAL; } while (0)
++
++#endif /* !USE_DYNAMIC_OAUTH */
++
++/* One final guardrail against accidental inclusion... */
++#if defined(USE_DYNAMIC_OAUTH) && defined(LIBPQ_INT_H)
++#error do not rely on libpq-int.h in libpq-oauth.so
+#endif
/*
* It's generally prudent to set a maximum response size to buffer in memory,
+@@ src/interfaces/libpq-oauth/oauth-curl.c: free_async_ctx(PGconn *conn, struct async_ctx *actx)
+ void
+ pg_fe_cleanup_oauth_flow(PGconn *conn)
+ {
+- fe_oauth_state *state = conn->sasl_state;
++ fe_oauth_state *state = conn_sasl_state(conn);
+
+ if (state->async_ctx)
+ {
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_cleanup_oauth_flow(PGconn *conn)
+ state->async_ctx = NULL;
+ }
+
+- conn->altsock = PGINVALID_SOCKET;
++ set_conn_altsock(conn, PGINVALID_SOCKET);
+ }
+
+ /*
@@ src/interfaces/libpq-oauth/oauth-curl.c: parse_access_token(struct async_ctx *actx, struct token *tok)
static bool
setup_multiplexer(struct async_ctx *actx)
@@ src/interfaces/libpq-oauth/oauth-curl.c: timer_expired(struct async_ctx *actx)
}
/*
+@@ src/interfaces/libpq-oauth/oauth-curl.c: static bool
+ check_issuer(struct async_ctx *actx, PGconn *conn)
+ {
+ const struct provider *provider = &actx->provider;
++ const char *oauth_issuer_id = conn_oauth_issuer_id(conn);
+
+- Assert(conn->oauth_issuer_id); /* ensured by setup_oauth_parameters() */
++ Assert(oauth_issuer_id); /* ensured by setup_oauth_parameters() */
+ Assert(provider->issuer); /* ensured by parse_provider() */
+
+ /*---
+@@ src/interfaces/libpq-oauth/oauth-curl.c: check_issuer(struct async_ctx *actx, PGconn *conn)
+ * sent to. This comparison MUST use simple string comparison as defined
+ * in Section 6.2.1 of [RFC3986].
+ */
+- if (strcmp(conn->oauth_issuer_id, provider->issuer) != 0)
++ if (strcmp(oauth_issuer_id, provider->issuer) != 0)
+ {
+ actx_error(actx,
+ "the issuer identifier (%s) does not match oauth_issuer (%s)",
+- provider->issuer, conn->oauth_issuer_id);
++ provider->issuer, oauth_issuer_id);
+ return false;
+ }
+
+@@ src/interfaces/libpq-oauth/oauth-curl.c: check_for_device_flow(struct async_ctx *actx)
+ static bool
+ add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *conn)
+ {
++ const char *oauth_client_id = conn_oauth_client_id(conn);
++ const char *oauth_client_secret = conn_oauth_client_secret(conn);
++
+ bool success = false;
+ char *username = NULL;
+ char *password = NULL;
+
+- if (conn->oauth_client_secret) /* Zero-length secrets are permitted! */
++ if (oauth_client_secret) /* Zero-length secrets are permitted! */
+ {
+ /*----
+ * Use HTTP Basic auth to send the client_id and secret. Per RFC 6749,
+@@ src/interfaces/libpq-oauth/oauth-curl.c: add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *c
+ * would it be redundant, but some providers in the wild (e.g. Okta)
+ * refuse to accept it.
+ */
+- username = urlencode(conn->oauth_client_id);
+- password = urlencode(conn->oauth_client_secret);
++ username = urlencode(oauth_client_id);
++ password = urlencode(oauth_client_secret);
+
+ if (!username || !password)
+ {
+@@ src/interfaces/libpq-oauth/oauth-curl.c: add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *c
+ * If we're not otherwise authenticating, client_id is REQUIRED in the
+ * request body.
+ */
+- build_urlencoded(reqbody, "client_id", conn->oauth_client_id);
++ build_urlencoded(reqbody, "client_id", oauth_client_id);
+
+ CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_NONE, goto cleanup);
+ actx->used_basic_auth = false;
+@@ src/interfaces/libpq-oauth/oauth-curl.c: cleanup:
+ static bool
+ start_device_authz(struct async_ctx *actx, PGconn *conn)
+ {
++ const char *oauth_scope = conn_oauth_scope(conn);
+ const char *device_authz_uri = actx->provider.device_authorization_endpoint;
+ PQExpBuffer work_buffer = &actx->work_data;
+
+- Assert(conn->oauth_client_id); /* ensured by setup_oauth_parameters() */
++ Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
+ Assert(device_authz_uri); /* ensured by check_for_device_flow() */
+
+ /* Construct our request body. */
+ resetPQExpBuffer(work_buffer);
+- if (conn->oauth_scope && conn->oauth_scope[0])
+- build_urlencoded(work_buffer, "scope", conn->oauth_scope);
++ if (oauth_scope && oauth_scope[0])
++ build_urlencoded(work_buffer, "scope", oauth_scope);
+
+ if (!add_client_identification(actx, work_buffer, conn))
+ return false;
+@@ src/interfaces/libpq-oauth/oauth-curl.c: start_token_request(struct async_ctx *actx, PGconn *conn)
+ const char *device_code = actx->authz.device_code;
+ PQExpBuffer work_buffer = &actx->work_data;
+
+- Assert(conn->oauth_client_id); /* ensured by setup_oauth_parameters() */
++ Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
+ Assert(token_uri); /* ensured by parse_provider() */
+ Assert(device_code); /* ensured by parse_device_authz() */
+
@@ src/interfaces/libpq-oauth/oauth-curl.c: prompt_user(struct async_ctx *actx, PGconn *conn)
.verification_uri_complete = actx->authz.verification_uri_complete,
.expires_in = actx->authz.expires_in,
@@ src/interfaces/libpq-oauth/oauth-curl.c: prompt_user(struct async_ctx *actx, PGc
if (!res)
{
-@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+@@ src/interfaces/libpq-oauth/oauth-curl.c: done:
+ static PostgresPollingStatusType
+ pg_fe_run_oauth_flow_impl(PGconn *conn)
{
- fe_oauth_state *state = conn->sasl_state;
+- fe_oauth_state *state = conn->sasl_state;
++ fe_oauth_state *state = conn_sasl_state(conn);
struct async_ctx *actx;
++ char *oauth_token = NULL;
+ PQExpBuffer errbuf;
if (!initialize_curl(conn))
return PGRES_POLLING_FAILED;
@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ do
+ {
+ /* By default, the multiplexer is the altsock. Reassign as desired. */
+- conn->altsock = actx->mux;
++ set_conn_altsock(conn, actx->mux);
+
+ switch (actx->step)
+ {
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ */
+ if (!timer_expired(actx))
+ {
+- conn->altsock = actx->timerfd;
++ set_conn_altsock(conn, actx->timerfd);
+ return PGRES_POLLING_READING;
+ }
- error_return:
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ {
+ case OAUTH_STEP_INIT:
+ actx->errctx = "failed to fetch OpenID discovery document";
+- if (!start_discovery(actx, conn->oauth_discovery_uri))
++ if (!start_discovery(actx, conn_oauth_discovery_uri(conn)))
+ goto error_return;
-+ /*
-+ * For the dynamic module build, we can't safely rely on the offset of
-+ * conn->errorMessage, since it depends on build options like USE_SSL et
-+ * al. libpq gives us a translator function instead.
-+ */
-+#ifdef USE_DYNAMIC_OAUTH
+ actx->step = OAUTH_STEP_DISCOVERY;
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ break;
+
+ case OAUTH_STEP_TOKEN_REQUEST:
+- if (!handle_token_response(actx, &conn->oauth_token))
++ if (!handle_token_response(actx, &oauth_token))
+ goto error_return;
+
++ /*
++ * Hook any oauth_token into the PGconn immediately so that
++ * the allocation isn't lost in case of an error.
++ */
++ set_conn_oauth_token(conn, oauth_token);
++
+ if (!actx->user_prompted)
+ {
+ /*
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ actx->user_prompted = true;
+ }
+
+- if (conn->oauth_token)
++ if (oauth_token)
+ break; /* done! */
+
+ /*
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ * the client wait directly on the timerfd rather than the
+ * multiplexer.
+ */
+- conn->altsock = actx->timerfd;
++ set_conn_altsock(conn, actx->timerfd);
+
+ actx->step = OAUTH_STEP_WAIT_INTERVAL;
+ actx->running = 1;
+@@ src/interfaces/libpq-oauth/oauth-curl.c: pg_fe_run_oauth_flow_impl(PGconn *conn)
+ * point, actx->running will be set. But there are some corner cases
+ * where we can immediately loop back around; see start_request().
+ */
+- } while (!conn->oauth_token && !actx->running);
++ } while (!oauth_token && !actx->running);
+
+ /* If we've stored a token, we're done. Otherwise come back later. */
+- return conn->oauth_token ? PGRES_POLLING_OK : PGRES_POLLING_READING;
++ return oauth_token ? PGRES_POLLING_OK : PGRES_POLLING_READING;
+
+ error_return:
+ errbuf = conn_errorMessage(conn);
-+#else
-+ errbuf = &conn->errorMessage;
-+#endif
-+
+
/*
* Assemble the three parts of our error: context, body, and detail. See
* also the documentation for struct async_ctx.
@@ src/interfaces/libpq-oauth/oauth-utils.c (new)
+
+#include <signal.h>
+
-+#include "libpq-int.h"
+#include "oauth-utils.h"
+
+#ifndef USE_DYNAMIC_OAUTH
+#error oauth-utils.c is not supported in static builds
+#endif
+
-+static libpq_gettext_func libpq_gettext_impl;
++#ifdef LIBPQ_INT_H
++#error do not rely on libpq-int.h in libpq-oauth
++#endif
++
++/*
++ * Function pointers set by libpq_oauth_init().
++ */
+
+pgthreadlock_t pg_g_threadlock;
++static libpq_gettext_func libpq_gettext_impl;
++
+conn_errorMessage_func conn_errorMessage;
++conn_oauth_client_id_func conn_oauth_client_id;
++conn_oauth_client_secret_func conn_oauth_client_secret;
++conn_oauth_discovery_uri_func conn_oauth_discovery_uri;
++conn_oauth_issuer_id_func conn_oauth_issuer_id;
++conn_oauth_scope_func conn_oauth_scope;
++conn_sasl_state_func conn_sasl_state;
++
++set_conn_altsock_func set_conn_altsock;
++set_conn_oauth_token_func set_conn_oauth_token;
+
+/*-
+ * Initializes libpq-oauth by setting necessary callbacks.
@@ src/interfaces/libpq-oauth/oauth-utils.c (new)
+ *
+ * - libpq_gettext: translates error messages using libpq's message domain
+ *
-+ * - conn->errorMessage: holds translated errors for the connection. This is
-+ * handled through a translation shim, which avoids either depending on the
-+ * offset of the errorMessage in PGconn, or needing to export the variadic
-+ * libpq_append_conn_error().
++ * The implementation also needs access to several members of the PGconn struct,
++ * which are not guaranteed to stay in place across minor versions. Accessors
++ * (named conn_*) and mutators (named set_conn_*) are injected here.
+ */
+void
+libpq_oauth_init(pgthreadlock_t threadlock_impl,
+ libpq_gettext_func gettext_impl,
-+ conn_errorMessage_func errmsg_impl)
++ conn_errorMessage_func errmsg_impl,
++ conn_oauth_client_id_func clientid_impl,
++ conn_oauth_client_secret_func clientsecret_impl,
++ conn_oauth_discovery_uri_func discoveryuri_impl,
++ conn_oauth_issuer_id_func issuerid_impl,
++ conn_oauth_scope_func scope_impl,
++ conn_sasl_state_func saslstate_impl,
++ set_conn_altsock_func setaltsock_impl,
++ set_conn_oauth_token_func settoken_impl)
+{
+ pg_g_threadlock = threadlock_impl;
+ libpq_gettext_impl = gettext_impl;
+ conn_errorMessage = errmsg_impl;
++ conn_oauth_client_id = clientid_impl;
++ conn_oauth_client_secret = clientsecret_impl;
++ conn_oauth_discovery_uri = discoveryuri_impl;
++ conn_oauth_issuer_id = issuerid_impl;
++ conn_oauth_scope = scope_impl;
++ conn_sasl_state = saslstate_impl;
++ set_conn_altsock = setaltsock_impl;
++ set_conn_oauth_token = settoken_impl;
+}
+
+/*
@@ src/interfaces/libpq-oauth/oauth-utils.h (new)
+#ifndef OAUTH_UTILS_H
+#define OAUTH_UTILS_H
+
++#include "fe-auth-oauth.h"
+#include "libpq-fe.h"
+#include "pqexpbuffer.h"
+
++/*
++ * A bank of callbacks to safely access members of PGconn, which are all passed
++ * to libpq_oauth_init() by libpq.
++ *
++ * Keep these aligned with the definitions in fe-auth-oauth.c as well as the
++ * static declarations in oauth-curl.c.
++ */
++#define DECLARE_GETTER(TYPE, MEMBER) \
++ typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
++ extern conn_ ## MEMBER ## _func conn_ ## MEMBER;
++
++#define DECLARE_SETTER(TYPE, MEMBER) \
++ typedef void (*set_conn_ ## MEMBER ## _func) (PGconn *conn, TYPE val); \
++ extern set_conn_ ## MEMBER ## _func set_conn_ ## MEMBER;
++
++DECLARE_GETTER(PQExpBuffer, errorMessage);
++DECLARE_GETTER(char *, oauth_client_id);
++DECLARE_GETTER(char *, oauth_client_secret);
++DECLARE_GETTER(char *, oauth_discovery_uri);
++DECLARE_GETTER(char *, oauth_issuer_id);
++DECLARE_GETTER(char *, oauth_scope);
++DECLARE_GETTER(fe_oauth_state *, sasl_state);
++
++DECLARE_SETTER(pgsocket, altsock);
++DECLARE_SETTER(char *, oauth_token);
++
++#undef DECLARE_GETTER
++#undef DECLARE_SETTER
++
+typedef char *(*libpq_gettext_func) (const char *msgid);
-+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/* Initializes libpq-oauth. */
+extern PGDLLEXPORT void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
-+ conn_errorMessage_func errmsg_impl);
++ conn_errorMessage_func errmsg_impl,
++ conn_oauth_client_id_func clientid_impl,
++ conn_oauth_client_secret_func clientsecret_impl,
++ conn_oauth_discovery_uri_func discoveryuri_impl,
++ conn_oauth_issuer_id_func issuerid_impl,
++ conn_oauth_scope_func scope_impl,
++ conn_sasl_state_func saslstate_impl,
++ set_conn_altsock_func setaltsock_impl,
++ set_conn_oauth_token_func settoken_impl);
+
-+/* Callback to safely obtain conn->errorMessage from a PGconn. */
-+extern conn_errorMessage_func conn_errorMessage;
++/*
++ * Duplicated APIs, copied from libpq (primarily libpq-int.h, which we cannot
++ * depend on here).
++ */
++
++typedef enum
++{
++ PG_BOOL_UNKNOWN = 0, /* Currently unknown */
++ PG_BOOL_YES, /* Yes (true) */
++ PG_BOOL_NO /* No (false) */
++} PGTernaryBool;
+
-+/* Duplicated APIs, copied from libpq. */
+extern void libpq_append_conn_error(PGconn *conn, const char *fmt,...) pg_attribute_printf(2, 3);
+extern bool oauth_unsafe_debugging_enabled(void);
+extern int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
+extern void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe);
+
++#ifdef ENABLE_NLS
++extern char *libpq_gettext(const char *msgid) pg_attribute_format_arg(1);
++#else
++#define libpq_gettext(x) (x)
++#endif
++
++extern pgthreadlock_t pg_g_threadlock;
++
++#define pglock_thread() pg_g_threadlock(true)
++#define pgunlock_thread() pg_g_threadlock(false)
++
+#endif /* OAUTH_UTILS_H */
## src/interfaces/libpq/Makefile ##
@@ src/interfaces/libpq/fe-auth-oauth.c: cleanup_user_oauth_flow(PGconn *conn)
+ */
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
-+typedef PQExpBuffer (*conn_errorMessage_func) (PGconn *conn);
+
+/*
-+ * This shim is injected into libpq-oauth so that it doesn't depend on the
-+ * offset of conn->errorMessage.
-+ *
-+ * TODO: look into exporting libpq_append_conn_error or a comparable API from
-+ * libpq, instead.
++ * Define accessor/mutator shims to inject into libpq-oauth, so that it doesn't
++ * depend on the offsets within PGconn. (These have changed during minor version
++ * updates in the past.)
+ */
-+static PQExpBuffer
-+conn_errorMessage(PGconn *conn)
-+{
-+ return &conn->errorMessage;
-+}
++
++#define DEFINE_GETTER(TYPE, MEMBER) \
++ typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
++ static TYPE conn_ ## MEMBER(PGconn *conn) { return conn->MEMBER; }
++
++/* Like DEFINE_GETTER, but returns a pointer to the member. */
++#define DEFINE_GETTER_P(TYPE, MEMBER) \
++ typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
++ static TYPE conn_ ## MEMBER(PGconn *conn) { return &conn->MEMBER; }
++
++#define DEFINE_SETTER(TYPE, MEMBER) \
++ typedef void (*set_conn_ ## MEMBER ## _func) (PGconn *conn, TYPE val); \
++ static void set_conn_ ## MEMBER(PGconn *conn, TYPE val) { conn->MEMBER = val; }
++
++DEFINE_GETTER_P(PQExpBuffer, errorMessage);
++DEFINE_GETTER(char *, oauth_client_id);
++DEFINE_GETTER(char *, oauth_client_secret);
++DEFINE_GETTER(char *, oauth_discovery_uri);
++DEFINE_GETTER(char *, oauth_issuer_id);
++DEFINE_GETTER(char *, oauth_scope);
++DEFINE_GETTER(fe_oauth_state *, sasl_state);
++
++DEFINE_SETTER(pgsocket, altsock);
++DEFINE_SETTER(char *, oauth_token);
+
+/*
+ * Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
@@ src/interfaces/libpq/fe-auth-oauth.c: cleanup_user_oauth_flow(PGconn *conn)
+
+ void (*init) (pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
-+ conn_errorMessage_func errmsg_impl);
++ conn_errorMessage_func errmsg_impl,
++ conn_oauth_client_id_func clientid_impl,
++ conn_oauth_client_secret_func clientsecret_impl,
++ conn_oauth_discovery_uri_func discoveryuri_impl,
++ conn_oauth_issuer_id_func issuerid_impl,
++ conn_oauth_scope_func scope_impl,
++ conn_sasl_state_func saslstate_impl,
++ set_conn_altsock_func setaltsock_impl,
++ set_conn_oauth_token_func settoken_impl);
+ PostgresPollingStatusType (*flow) (PGconn *conn);
+ void (*cleanup) (PGconn *conn);
+
@@ src/interfaces/libpq/fe-auth-oauth.c: cleanup_user_oauth_flow(PGconn *conn)
+ */
+ const char *const module_name =
+#if defined(__darwin__)
-+ LIBDIR "/libpq-oauth-" PG_MAJORVERSION "-" PG_MINORVERSION DLSUFFIX;
++ LIBDIR "/libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
+#else
-+ "libpq-oauth-" PG_MAJORVERSION "-" PG_MINORVERSION DLSUFFIX;
++ "libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
+#endif
+
+ state->builtin_flow = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
@@ src/interfaces/libpq/fe-auth-oauth.c: cleanup_user_oauth_flow(PGconn *conn)
+#else
+ NULL,
+#endif
-+ conn_errorMessage);
++ conn_errorMessage,
++ conn_oauth_client_id,
++ conn_oauth_client_secret,
++ conn_oauth_discovery_uri,
++ conn_oauth_issuer_id,
++ conn_oauth_scope,
++ conn_sasl_state,
++ set_conn_altsock,
++ set_conn_oauth_token);
+
+ initialized = true;
+ }
@@ src/interfaces/libpq/fe-auth-oauth.c: setup_token_request(PGconn *conn, fe_oauth
return true;
## src/interfaces/libpq/fe-auth-oauth.h ##
-@@ src/interfaces/libpq/fe-auth-oauth.h: typedef struct
+@@
+ #ifndef FE_AUTH_OAUTH_H
+ #define FE_AUTH_OAUTH_H
+
++#include "fe-auth-sasl.h"
+ #include "libpq-fe.h"
+-#include "libpq-int.h"
+
+
+ enum fe_oauth_step
+@@ src/interfaces/libpq/fe-auth-oauth.h: enum fe_oauth_step
+ FE_OAUTH_SERVER_ERROR,
+ };
+
++/*
++ * This struct is exported to the libpq-oauth module. If changes are needed
++ * during backports to stable branches, please keep ABI compatibility (no
++ * changes to existing members, add new members at the end, etc.).
++ */
+ typedef struct
+ {
+ enum fe_oauth_step step;
PGconn *conn;
void *async_ctx;
[application/x-patch] v10-0001-oauth-Move-the-builtin-flow-into-a-separate-modu.patch (69.6K, ../../CAOYmi+=UsSnLM4K+hYkhrezpQROQ5jr=72feAkQ0Te8GJf3Fbg@mail.gmail.com/3-v10-0001-oauth-Move-the-builtin-flow-into-a-separate-modu.patch)
download | inline diff:
From e86e93f7ac8e0ee746b95d804b8367a6ea4c9d30 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Wed, 26 Mar 2025 10:55:28 -0700
Subject: [PATCH v10] oauth: Move the builtin flow into a separate module
The additional packaging footprint of the OAuth Curl dependency, as well
as the existence of libcurl in the address space even if OAuth isn't
ever used by a client, has raised some concerns. Split off this
dependency into a separate loadable module called libpq-oauth.
When configured using --with-libcurl, libpq.so searches for this new
module via dlopen(). End users may choose not to install the libpq-oauth
module, in which case the default flow is disabled.
For static applications using libpq.a, the libpq-oauth staticlib is a
mandatory link-time dependency for --with-libcurl builds. libpq.pc has
been updated accordingly.
The default flow relies on some libpq internals. Some of these can be
safely duplicated (such as the SIGPIPE handlers), but others need to be
shared between libpq and libpq-oauth for thread-safety. To avoid
exporting these internals to all libpq clients forever, these
dependencies are instead injected from the libpq side via an
initialization function. This also lets libpq communicate the offsets of
PGconn struct members to libpq-oauth, so that we can function without
crashing if the module on the search path came from a different build of
Postgres. (A minor-version upgrade could swap the libpq-oauth module out
from under a long-running libpq client before it does its first load of
the OAuth flow.)
This ABI is considered "private". The module has no SONAME or version
symlinks, and it's named libpq-oauth-<major>.so to avoid mixing and
matching across Postgres versions. (Future improvements may promote this
"OAuth flow plugin" to a first-class concept, at which point we would
need a public API to replace this anyway.)
Additionally, NLS support for error messages in b3f0be788a was
incomplete, because the new error macros weren't being scanned by
xgettext. Fix that now.
Per request from Tom Lane and Bruce Momjian. Based on an initial patch
by Daniel Gustafsson, who also contributed docs changes. The "bare"
dlopen() concept came from Thomas Munro. Many many people reviewed the
design and implementation; thank you!
Co-authored-by: Daniel Gustafsson <[email protected]>
Reviewed-by: Andres Freund <[email protected]>
Reviewed-by: Christoph Berg <[email protected]>
Reviewed-by: Jelte Fennema-Nio <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Wolfgang Walther <[email protected]>
Discussion: https://postgr.es/m/641687.1742360249%40sss.pgh.pa.us
---
config/programs.m4 | 17 +-
configure | 50 +++-
configure.ac | 26 +-
doc/src/sgml/installation.sgml | 8 +
doc/src/sgml/libpq.sgml | 30 ++-
meson.build | 32 ++-
src/Makefile.global.in | 3 +
src/interfaces/Makefile | 12 +
src/interfaces/libpq-oauth/Makefile | 83 +++++++
src/interfaces/libpq-oauth/README | 58 +++++
src/interfaces/libpq-oauth/exports.txt | 4 +
src/interfaces/libpq-oauth/meson.build | 45 ++++
.../oauth-curl.c} | 180 ++++++++------
src/interfaces/libpq-oauth/oauth-curl.h | 24 ++
src/interfaces/libpq-oauth/oauth-utils.c | 233 ++++++++++++++++++
src/interfaces/libpq-oauth/oauth-utils.h | 94 +++++++
src/interfaces/libpq/Makefile | 36 ++-
src/interfaces/libpq/exports.txt | 1 +
src/interfaces/libpq/fe-auth-oauth.c | 229 ++++++++++++++++-
src/interfaces/libpq/fe-auth-oauth.h | 12 +-
src/interfaces/libpq/meson.build | 25 +-
src/interfaces/libpq/nls.mk | 12 +-
src/makefiles/meson.build | 2 +
src/test/modules/oauth_validator/meson.build | 2 +-
.../modules/oauth_validator/t/002_client.pl | 2 +-
25 files changed, 1080 insertions(+), 140 deletions(-)
create mode 100644 src/interfaces/libpq-oauth/Makefile
create mode 100644 src/interfaces/libpq-oauth/README
create mode 100644 src/interfaces/libpq-oauth/exports.txt
create mode 100644 src/interfaces/libpq-oauth/meson.build
rename src/interfaces/{libpq/fe-auth-oauth-curl.c => libpq-oauth/oauth-curl.c} (94%)
create mode 100644 src/interfaces/libpq-oauth/oauth-curl.h
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.c
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.h
diff --git a/config/programs.m4 b/config/programs.m4
index 0a07feb37cc..0ad1e58b48d 100644
--- a/config/programs.m4
+++ b/config/programs.m4
@@ -286,9 +286,20 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
[
AC_CHECK_HEADER(curl/curl.h, [],
[AC_MSG_ERROR([header file <curl/curl.h> is required for --with-libcurl])])
- AC_CHECK_LIB(curl, curl_multi_init, [],
+ AC_CHECK_LIB(curl, curl_multi_init, [
+ AC_DEFINE([HAVE_LIBCURL], [1], [Define to 1 if you have the `curl' library (-lcurl).])
+ AC_SUBST(LIBCURL_LDLIBS, -lcurl)
+ ],
[AC_MSG_ERROR([library 'curl' does not provide curl_multi_init])])
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+ LIBS="$LIBCURL_LDLIBS $LIBS"
+
# Check to see whether the current platform supports threadsafe Curl
# initialization.
AC_CACHE_CHECK([for curl_global_init thread safety], [pgac_cv__libcurl_threadsafe_init],
@@ -338,4 +349,8 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
*** lookups. Rebuild libcurl with the AsynchDNS feature enabled in order
*** to use it with libpq.])
fi
+
+ CPPFLAGS=$pgac_save_CPPFLAGS
+ LDFLAGS=$pgac_save_LDFLAGS
+ LIBS=$pgac_save_LIBS
])# PGAC_CHECK_LIBCURL
diff --git a/configure b/configure
index 0936010718d..a4c4bcb40ea 100755
--- a/configure
+++ b/configure
@@ -655,6 +655,7 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+LIBCURL_LDLIBS
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -711,6 +712,8 @@ with_libxml
LIBNUMA_LIBS
LIBNUMA_CFLAGS
with_libnuma
+LIBCURL_LDFLAGS
+LIBCURL_CPPFLAGS
LIBCURL_LIBS
LIBCURL_CFLAGS
with_libcurl
@@ -9053,19 +9056,27 @@ $as_echo "yes" >&6; }
fi
- # We only care about -I, -D, and -L switches;
- # note that -lcurl will be added by PGAC_CHECK_LIBCURL below.
+ # Curl's flags are kept separate from the standard CPPFLAGS/LDFLAGS. We use
+ # them only for libpq-oauth.
+ LIBCURL_CPPFLAGS=
+ LIBCURL_LDFLAGS=
+
+ # We only care about -I, -D, and -L switches. Note that -lcurl will be added
+ # to LIBCURL_LDLIBS by PGAC_CHECK_LIBCURL, below.
for pgac_option in $LIBCURL_CFLAGS; do
case $pgac_option in
- -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+ -I*|-D*) LIBCURL_CPPFLAGS="$LIBCURL_CPPFLAGS $pgac_option";;
esac
done
for pgac_option in $LIBCURL_LIBS; do
case $pgac_option in
- -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+ -L*) LIBCURL_LDFLAGS="$LIBCURL_LDFLAGS $pgac_option";;
esac
done
+
+
+
# OAuth requires python for testing
if test "$with_python" != yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: *** OAuth support tests require --with-python to run" >&5
@@ -12704,9 +12715,6 @@ fi
fi
-# XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
-# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
-# dependency on that platform?
if test "$with_libcurl" = yes ; then
ac_fn_c_check_header_mongrel "$LINENO" "curl/curl.h" "ac_cv_header_curl_curl_h" "$ac_includes_default"
@@ -12754,17 +12762,26 @@ fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_curl_curl_multi_init" >&5
$as_echo "$ac_cv_lib_curl_curl_multi_init" >&6; }
if test "x$ac_cv_lib_curl_curl_multi_init" = xyes; then :
- cat >>confdefs.h <<_ACEOF
-#define HAVE_LIBCURL 1
-_ACEOF
- LIBS="-lcurl $LIBS"
+
+$as_echo "#define HAVE_LIBCURL 1" >>confdefs.h
+
+ LIBCURL_LDLIBS=-lcurl
+
else
as_fn_error $? "library 'curl' does not provide curl_multi_init" "$LINENO" 5
fi
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+ LIBS="$LIBCURL_LDLIBS $LIBS"
+
# Check to see whether the current platform supports threadsafe Curl
# initialization.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_global_init thread safety" >&5
@@ -12868,6 +12885,10 @@ $as_echo "$pgac_cv__libcurl_async_dns" >&6; }
*** to use it with libpq." "$LINENO" 5
fi
+ CPPFLAGS=$pgac_save_CPPFLAGS
+ LDFLAGS=$pgac_save_LDFLAGS
+ LIBS=$pgac_save_LIBS
+
fi
if test "$with_gssapi" = yes ; then
@@ -14516,6 +14537,13 @@ done
fi
+if test "$with_libcurl" = yes ; then
+ # Error out early if this platform can't support libpq-oauth.
+ if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
+ as_fn_error $? "client OAuth is not supported on this platform" "$LINENO" 5
+ fi
+fi
+
##
## Types, structures, compiler characteristics
##
diff --git a/configure.ac b/configure.ac
index 2a78cddd825..c0471030e90 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1033,19 +1033,27 @@ if test "$with_libcurl" = yes ; then
# to explicitly set TLS 1.3 ciphersuites).
PKG_CHECK_MODULES(LIBCURL, [libcurl >= 7.61.0])
- # We only care about -I, -D, and -L switches;
- # note that -lcurl will be added by PGAC_CHECK_LIBCURL below.
+ # Curl's flags are kept separate from the standard CPPFLAGS/LDFLAGS. We use
+ # them only for libpq-oauth.
+ LIBCURL_CPPFLAGS=
+ LIBCURL_LDFLAGS=
+
+ # We only care about -I, -D, and -L switches. Note that -lcurl will be added
+ # to LIBCURL_LDLIBS by PGAC_CHECK_LIBCURL, below.
for pgac_option in $LIBCURL_CFLAGS; do
case $pgac_option in
- -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+ -I*|-D*) LIBCURL_CPPFLAGS="$LIBCURL_CPPFLAGS $pgac_option";;
esac
done
for pgac_option in $LIBCURL_LIBS; do
case $pgac_option in
- -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+ -L*) LIBCURL_LDFLAGS="$LIBCURL_LDFLAGS $pgac_option";;
esac
done
+ AC_SUBST(LIBCURL_CPPFLAGS)
+ AC_SUBST(LIBCURL_LDFLAGS)
+
# OAuth requires python for testing
if test "$with_python" != yes; then
AC_MSG_WARN([*** OAuth support tests require --with-python to run])
@@ -1354,9 +1362,6 @@ failure. It is possible the compiler isn't looking in the proper directory.
Use --without-zlib to disable zlib support.])])
fi
-# XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
-# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
-# dependency on that platform?
if test "$with_libcurl" = yes ; then
PGAC_CHECK_LIBCURL
fi
@@ -1654,6 +1659,13 @@ if test "$PORTNAME" = "win32" ; then
AC_CHECK_HEADERS(crtdefs.h)
fi
+if test "$with_libcurl" = yes ; then
+ # Error out early if this platform can't support libpq-oauth.
+ if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
+ AC_MSG_ERROR([client-side OAuth is not supported on this platform])
+ fi
+fi
+
##
## Types, structures, compiler characteristics
##
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 077bcc20759..d928b103d22 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -313,6 +313,14 @@
</para>
</listitem>
+ <listitem>
+ <para>
+ You need <productname>Curl</productname> to build an optional module
+ which implements the <link linkend="libpq-oauth">OAuth Device
+ Authorization flow</link> for client applications.
+ </para>
+ </listitem>
+
<listitem>
<para>
You need <productname>LZ4</productname>, if you want to support
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 8cdd2997d43..695fe958c3e 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -10226,15 +10226,20 @@ void PQinitSSL(int do_ssl);
<title>OAuth Support</title>
<para>
- libpq implements support for the OAuth v2 Device Authorization client flow,
+ <application>libpq</application> implements support for the OAuth v2 Device Authorization client flow,
documented in
<ulink url="https://datatracker.ietf.org/doc/html/rfc8628">RFC 8628</ulink>,
- which it will attempt to use by default if the server
+ as an optional module. See the <link linkend="configure-option-with-libcurl">
+ installation documentation</link> for information on how to enable support
+ for Device Authorization as a builtin flow.
+ </para>
+ <para>
+ When support is enabled and the optional module installed, <application>libpq</application>
+ will use the builtin flow by default if the server
<link linkend="auth-oauth">requests a bearer token</link> during
authentication. This flow can be utilized even if the system running the
client application does not have a usable web browser, for example when
- running a client via <application>SSH</application>. Client applications may implement their own flows
- instead; see <xref linkend="libpq-oauth-authdata-hooks"/>.
+ running a client via <acronym>SSH</acronym>.
</para>
<para>
The builtin flow will, by default, print a URL to visit and a user code to
@@ -10251,6 +10256,11 @@ Visit https://example.com/device and enter the code: ABCD-EFGH
they match expectations, before continuing. Permissions should not be given
to untrusted third parties.
</para>
+ <para>
+ Client applications may implement their own flows to customize interaction
+ and integration with applications. See <xref linkend="libpq-oauth-authdata-hooks"/>
+ for more information on how add a custom flow to <application>libpq</application>.
+ </para>
<para>
For an OAuth client flow to be usable, the connection string must at minimum
contain <xref linkend="libpq-connect-oauth-issuer"/> and
@@ -10366,7 +10376,9 @@ typedef struct _PGpromptOAuthDevice
</synopsis>
</para>
<para>
- The OAuth Device Authorization flow included in <application>libpq</application>
+ The OAuth Device Authorization flow which
+ <link linkend="configure-option-with-libcurl">can be included</link>
+ in <application>libpq</application>
requires the end user to visit a URL with a browser, then enter a code
which permits <application>libpq</application> to connect to the server
on their behalf. The default prompt simply prints the
@@ -10378,7 +10390,8 @@ typedef struct _PGpromptOAuthDevice
This callback is only invoked during the builtin device
authorization flow. If the application installs a
<link linkend="libpq-oauth-authdata-oauth-bearer-token">custom OAuth
- flow</link>, this authdata type will not be used.
+ flow</link>, or <application>libpq</application> was not built with
+ support for the builtin flow, this authdata type will not be used.
</para>
<para>
If a non-NULL <structfield>verification_uri_complete</structfield> is
@@ -10400,8 +10413,9 @@ typedef struct _PGpromptOAuthDevice
</term>
<listitem>
<para>
- Replaces the entire OAuth flow with a custom implementation. The hook
- should either directly return a Bearer token for the current
+ Adds a custom implementation of a flow, replacing the builtin flow if
+ it is <link linkend="configure-option-with-libcurl">installed</link>.
+ The hook should either directly return a Bearer token for the current
user/issuer/scope combination, if one is available without blocking, or
else set up an asynchronous callback to retrieve one.
</para>
diff --git a/meson.build b/meson.build
index a1516e54529..29d46c8ad01 100644
--- a/meson.build
+++ b/meson.build
@@ -107,6 +107,7 @@ os_deps = []
backend_both_deps = []
backend_deps = []
libpq_deps = []
+libpq_oauth_deps = []
pg_sysroot = ''
@@ -860,13 +861,13 @@ endif
###############################################################
libcurlopt = get_option('libcurl')
+oauth_flow_supported = false
+
if not libcurlopt.disabled()
# Check for libcurl 7.61.0 or higher (corresponding to RHEL8 and the ability
# to explicitly set TLS 1.3 ciphersuites).
libcurl = dependency('libcurl', version: '>= 7.61.0', required: libcurlopt)
if libcurl.found()
- cdata.set('USE_LIBCURL', 1)
-
# Check to see whether the current platform supports thread-safe Curl
# initialization.
libcurl_threadsafe_init = false
@@ -938,6 +939,22 @@ if not libcurlopt.disabled()
endif
endif
+ # Check that the current platform supports our builtin flow. This requires
+ # libcurl and one of either epoll or kqueue.
+ oauth_flow_supported = (
+ libcurl.found()
+ and (cc.check_header('sys/event.h', required: false,
+ args: test_c_args, include_directories: postgres_inc)
+ or cc.check_header('sys/epoll.h', required: false,
+ args: test_c_args, include_directories: postgres_inc))
+ )
+
+ if oauth_flow_supported
+ cdata.set('USE_LIBCURL', 1)
+ elif libcurlopt.enabled()
+ error('client-side OAuth is not supported on this platform')
+ endif
+
else
libcurl = not_found_dep
endif
@@ -3272,17 +3289,18 @@ libpq_deps += [
gssapi,
ldap_r,
- # XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
- # during gss_acquire_cred(). This is possibly related to Curl's Heimdal
- # dependency on that platform?
- libcurl,
libintl,
ssl,
]
+libpq_oauth_deps += [
+ libcurl,
+]
+
subdir('src/interfaces/libpq')
-# fe_utils depends on libpq
+# fe_utils and libpq-oauth depends on libpq
subdir('src/fe_utils')
+subdir('src/interfaces/libpq-oauth')
# for frontend binaries
frontend_code = declare_dependency(
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 6722fbdf365..04952b533de 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -347,6 +347,9 @@ perl_embed_ldflags = @perl_embed_ldflags@
AWK = @AWK@
LN_S = @LN_S@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBCURL_LDFLAGS = @LIBCURL_LDFLAGS@
+LIBCURL_LDLIBS = @LIBCURL_LDLIBS@
MSGFMT = @MSGFMT@
MSGFMT_FLAGS = @MSGFMT_FLAGS@
MSGMERGE = @MSGMERGE@
diff --git a/src/interfaces/Makefile b/src/interfaces/Makefile
index 7d56b29d28f..e6822caa206 100644
--- a/src/interfaces/Makefile
+++ b/src/interfaces/Makefile
@@ -14,7 +14,19 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = libpq ecpg
+ifeq ($(with_libcurl), yes)
+SUBDIRS += libpq-oauth
+else
+ALWAYS_SUBDIRS += libpq-oauth
+endif
+
$(recurse)
+$(recurse_always)
all-ecpg-recurse: all-libpq-recurse
install-ecpg-recurse: install-libpq-recurse
+
+ifeq ($(with_libcurl), yes)
+all-libpq-oauth-recurse: all-libpq-recurse
+install-libpq-oauth-recurse: install-libpq-recurse
+endif
diff --git a/src/interfaces/libpq-oauth/Makefile b/src/interfaces/libpq-oauth/Makefile
new file mode 100644
index 00000000000..3e4b34142e0
--- /dev/null
+++ b/src/interfaces/libpq-oauth/Makefile
@@ -0,0 +1,83 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for libpq-oauth
+#
+# Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/interfaces/libpq-oauth/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/interfaces/libpq-oauth
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+PGFILEDESC = "libpq-oauth - device authorization OAuth support"
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
+NAME = pq-oauth-$(MAJORVERSION)
+
+# Force the name "libpq-oauth" for both the static and shared libraries. The
+# staticlib doesn't need version information in its name.
+override shlib := lib$(NAME)$(DLSUFFIX)
+override stlib := libpq-oauth.a
+
+override CPPFLAGS := -I$(libpq_srcdir) -I$(top_builddir)/src/port $(LIBCURL_CPPFLAGS) $(CPPFLAGS)
+
+OBJS = \
+ $(WIN32RES)
+
+OBJS_STATIC = oauth-curl.o
+
+# The shared library needs additional glue symbols.
+OBJS_SHLIB = \
+ oauth-curl_shlib.o \
+ oauth-utils.o \
+
+oauth-utils.o: override CPPFLAGS += -DUSE_DYNAMIC_OAUTH
+oauth-curl_shlib.o: override CPPFLAGS_SHLIB += -DUSE_DYNAMIC_OAUTH
+
+# Add shlib-/stlib-specific objects.
+$(shlib): override OBJS += $(OBJS_SHLIB)
+$(shlib): $(OBJS_SHLIB)
+
+$(stlib): override OBJS += $(OBJS_STATIC)
+$(stlib): $(OBJS_STATIC)
+
+SHLIB_LINK_INTERNAL = $(libpq_pgport_shlib)
+SHLIB_LINK = $(LIBCURL_LDFLAGS) $(LIBCURL_LDLIBS)
+SHLIB_PREREQS = submake-libpq
+SHLIB_EXPORTS = exports.txt
+
+# Disable -bundle_loader on macOS.
+BE_DLLLIBS =
+
+# By default, a library without an SONAME doesn't get a static library, so we
+# add it to the build explicitly.
+all: all-lib all-static-lib
+
+# Shared library stuff
+include $(top_srcdir)/src/Makefile.shlib
+
+# Use src/common/Makefile's trick for tracking dependencies of shlib-specific
+# objects.
+%_shlib.o: %.c %.o
+ $(CC) $(CFLAGS) $(CFLAGS_SL) $(CPPFLAGS) $(CPPFLAGS_SHLIB) -c $< -o $@
+
+# Ignore the standard rules for SONAME-less installation; we want both the
+# static and shared libraries to go into libdir.
+install: all installdirs $(stlib) $(shlib)
+ $(INSTALL_SHLIB) $(shlib) '$(DESTDIR)$(libdir)/$(shlib)'
+ $(INSTALL_STLIB) $(stlib) '$(DESTDIR)$(libdir)/$(stlib)'
+
+installdirs:
+ $(MKDIR_P) '$(DESTDIR)$(libdir)'
+
+uninstall:
+ rm -f '$(DESTDIR)$(libdir)/$(stlib)'
+ rm -f '$(DESTDIR)$(libdir)/$(shlib)'
+
+clean distclean: clean-lib
+ rm -f $(OBJS) $(OBJS_STATIC) $(OBJS_SHLIB)
diff --git a/src/interfaces/libpq-oauth/README b/src/interfaces/libpq-oauth/README
new file mode 100644
index 00000000000..4579b45c0f9
--- /dev/null
+++ b/src/interfaces/libpq-oauth/README
@@ -0,0 +1,58 @@
+libpq-oauth is an optional module implementing the Device Authorization flow for
+OAuth clients (RFC 8628). It was originally developed as part of libpq core and
+later split out as its own shared library in order to isolate its dependency on
+libcurl. (End users who don't want the Curl dependency can simply choose not to
+install this module.)
+
+If a connection string allows the use of OAuth, and the server asks for it, and
+a libpq client has not installed its own custom OAuth flow, libpq will attempt
+to delay-load this module using dlopen() and the following ABI. Failure to load
+results in a failed connection.
+
+= Load-Time ABI =
+
+This module ABI is an internal implementation detail, so it's subject to change
+across major releases; the name of the module (libpq-oauth-MAJOR) reflects this.
+The module exports the following symbols:
+
+- PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+- void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+pg_fe_run_oauth_flow and pg_fe_cleanup_oauth_flow are implementations of
+conn->async_auth and conn->cleanup_async_auth, respectively.
+
+- void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl,
+ conn_oauth_client_id_func clientid_impl,
+ conn_oauth_client_secret_func clientsecret_impl,
+ conn_oauth_discovery_uri_func discoveryuri_impl,
+ conn_oauth_issuer_id_func issuerid_impl,
+ conn_oauth_scope_func scope_impl,
+ conn_sasl_state_func saslstate_impl,
+ set_conn_altsock_func setaltsock_impl,
+ set_conn_oauth_token_func settoken_impl);
+
+At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
+libpq_gettext(), which must be injected by libpq using this initialization
+function before the flow is run.
+
+It also relies on access to several members of the PGconn struct. Not only can
+these change positions across minor versions, but the offsets aren't necessarily
+stable within a single minor release (conn->errorMessage, for instance, can
+change offsets depending on configure-time options). Therefore the necessary
+accessors (named conn_*) and mutators (set_conn_*) are injected here. With this
+approach, we can safely search the standard dlopen() paths (e.g. RPATH,
+LD_LIBRARY_PATH, the SO cache) for an implementation module to use, even if that
+module wasn't compiled at the same time as libpq -- which becomes especially
+important during "live upgrade" situations where a running libpq application has
+the libpq-oauth module updated out from under it before it's first loaded from
+disk.
+
+= Static Build =
+
+The static library libpq.a does not perform any dynamic loading. If the builtin
+flow is enabled, the application is expected to link against libpq-oauth.a
+directly to provide the necessary symbols. (libpq.a and libpq-oauth.a must be
+part of the same build. Unlike the dynamic module, there are no translation
+shims provided.)
diff --git a/src/interfaces/libpq-oauth/exports.txt b/src/interfaces/libpq-oauth/exports.txt
new file mode 100644
index 00000000000..6891a83dbf9
--- /dev/null
+++ b/src/interfaces/libpq-oauth/exports.txt
@@ -0,0 +1,4 @@
+# src/interfaces/libpq-oauth/exports.txt
+libpq_oauth_init 1
+pg_fe_run_oauth_flow 2
+pg_fe_cleanup_oauth_flow 3
diff --git a/src/interfaces/libpq-oauth/meson.build b/src/interfaces/libpq-oauth/meson.build
new file mode 100644
index 00000000000..9e7301a7f63
--- /dev/null
+++ b/src/interfaces/libpq-oauth/meson.build
@@ -0,0 +1,45 @@
+# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+
+if not oauth_flow_supported
+ subdir_done()
+endif
+
+libpq_oauth_sources = files(
+ 'oauth-curl.c',
+)
+
+# The shared library needs additional glue symbols.
+libpq_oauth_so_sources = files(
+ 'oauth-utils.c',
+)
+libpq_oauth_so_c_args = ['-DUSE_DYNAMIC_OAUTH']
+
+export_file = custom_target('libpq-oauth.exports',
+ kwargs: gen_export_kwargs,
+)
+
+# port needs to be in include path due to pthread-win32.h
+libpq_oauth_inc = include_directories('.', '../libpq', '../../port')
+
+libpq_oauth_st = static_library('libpq-oauth',
+ libpq_oauth_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_stlib_code, libpq_oauth_deps],
+ kwargs: default_lib_args,
+)
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
+libpq_oauth_name = 'libpq-oauth-@0@'.format(pg_version_major)
+
+libpq_oauth_so = shared_module(libpq_oauth_name,
+ libpq_oauth_sources + libpq_oauth_so_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_args: libpq_so_c_args,
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_shlib_code, libpq, libpq_oauth_deps],
+ link_depends: export_file,
+ link_args: export_fmt.format(export_file.full_path()),
+ kwargs: default_lib_args,
+)
diff --git a/src/interfaces/libpq/fe-auth-oauth-curl.c b/src/interfaces/libpq-oauth/oauth-curl.c
similarity index 94%
rename from src/interfaces/libpq/fe-auth-oauth-curl.c
rename to src/interfaces/libpq-oauth/oauth-curl.c
index c195e00cd28..3acdcc52de8 100644
--- a/src/interfaces/libpq/fe-auth-oauth-curl.c
+++ b/src/interfaces/libpq-oauth/oauth-curl.c
@@ -1,6 +1,6 @@
/*-------------------------------------------------------------------------
*
- * fe-auth-oauth-curl.c
+ * oauth-curl.c
* The libcurl implementation of OAuth/OIDC authentication, using the
* OAuth Device Authorization Grant (RFC 8628).
*
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * src/interfaces/libpq/fe-auth-oauth-curl.c
+ * src/interfaces/libpq-oauth/oauth-curl.c
*
*-------------------------------------------------------------------------
*/
@@ -17,20 +17,56 @@
#include <curl/curl.h>
#include <math.h>
-#ifdef HAVE_SYS_EPOLL_H
+#include <unistd.h>
+
+#if defined(HAVE_SYS_EPOLL_H)
#include <sys/epoll.h>
#include <sys/timerfd.h>
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
#include <sys/event.h>
+#else
+#error libpq-oauth is not supported on this platform
#endif
-#include <unistd.h>
#include "common/jsonapi.h"
-#include "fe-auth.h"
#include "fe-auth-oauth.h"
-#include "libpq-int.h"
#include "mb/pg_wchar.h"
+#include "oauth-curl.h"
+
+#ifdef USE_DYNAMIC_OAUTH
+
+/*
+ * The module build is decoupled from libpq-int.h, to try to avoid inadvertent
+ * ABI breaks during minor version bumps. Replacements for the missing internals
+ * are provided by oauth-utils.
+ */
+#include "oauth-utils.h"
+
+#else /* !USE_DYNAMIC_OAUTH */
+
+/*
+ * Static builds may rely on PGconn offsets directly. Keep these aligned with
+ * the bank of callbacks in oauth-utils.h.
+ */
+#include "libpq-int.h"
+
+#define conn_errorMessage(CONN) (&CONN->errorMessage)
+#define conn_oauth_client_id(CONN) (CONN->oauth_client_id)
+#define conn_oauth_client_secret(CONN) (CONN->oauth_client_secret)
+#define conn_oauth_discovery_uri(CONN) (CONN->oauth_discovery_uri)
+#define conn_oauth_issuer_id(CONN) (CONN->oauth_issuer_id)
+#define conn_oauth_scope(CONN) (CONN->oauth_scope)
+#define conn_sasl_state(CONN) (CONN->sasl_state)
+
+#define set_conn_altsock(CONN, VAL) do { CONN->altsock = VAL; } while (0)
+#define set_conn_oauth_token(CONN, VAL) do { CONN->oauth_token = VAL; } while (0)
+
+#endif /* !USE_DYNAMIC_OAUTH */
+
+/* One final guardrail against accidental inclusion... */
+#if defined(USE_DYNAMIC_OAUTH) && defined(LIBPQ_INT_H)
+#error do not rely on libpq-int.h in libpq-oauth.so
+#endif
/*
* It's generally prudent to set a maximum response size to buffer in memory,
@@ -303,7 +339,7 @@ free_async_ctx(PGconn *conn, struct async_ctx *actx)
void
pg_fe_cleanup_oauth_flow(PGconn *conn)
{
- fe_oauth_state *state = conn->sasl_state;
+ fe_oauth_state *state = conn_sasl_state(conn);
if (state->async_ctx)
{
@@ -311,7 +347,7 @@ pg_fe_cleanup_oauth_flow(PGconn *conn)
state->async_ctx = NULL;
}
- conn->altsock = PGINVALID_SOCKET;
+ set_conn_altsock(conn, PGINVALID_SOCKET);
}
/*
@@ -1110,7 +1146,7 @@ parse_access_token(struct async_ctx *actx, struct token *tok)
static bool
setup_multiplexer(struct async_ctx *actx)
{
-#ifdef HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct epoll_event ev = {.events = EPOLLIN};
actx->mux = epoll_create1(EPOLL_CLOEXEC);
@@ -1134,8 +1170,7 @@ setup_multiplexer(struct async_ctx *actx)
}
return true;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
actx->mux = kqueue();
if (actx->mux < 0)
{
@@ -1158,10 +1193,9 @@ setup_multiplexer(struct async_ctx *actx)
}
return true;
+#else
+#error setup_multiplexer is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support the Device Authorization flow on this platform");
- return false;
}
/*
@@ -1174,7 +1208,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
{
struct async_ctx *actx = ctx;
-#ifdef HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct epoll_event ev = {0};
int res;
int op = EPOLL_CTL_ADD;
@@ -1230,8 +1264,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
}
return 0;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
struct kevent ev[2] = {0};
struct kevent ev_out[2];
struct timespec timeout = {0};
@@ -1312,10 +1345,9 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
}
return 0;
+#else
+#error register_socket is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support multiplexer sockets on this platform");
- return -1;
}
/*
@@ -1334,7 +1366,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
static bool
set_timer(struct async_ctx *actx, long timeout)
{
-#if HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct itimerspec spec = {0};
if (timeout < 0)
@@ -1363,8 +1395,7 @@ set_timer(struct async_ctx *actx, long timeout)
}
return true;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
struct kevent ev;
#ifdef __NetBSD__
@@ -1419,10 +1450,9 @@ set_timer(struct async_ctx *actx, long timeout)
}
return true;
+#else
+#error set_timer is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support timers on this platform");
- return false;
}
/*
@@ -1433,7 +1463,7 @@ set_timer(struct async_ctx *actx, long timeout)
static int
timer_expired(struct async_ctx *actx)
{
-#if HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct itimerspec spec = {0};
if (timerfd_gettime(actx->timerfd, &spec) < 0)
@@ -1453,8 +1483,7 @@ timer_expired(struct async_ctx *actx)
/* If the remaining time to expiration is zero, we're done. */
return (spec.it_value.tv_sec == 0
&& spec.it_value.tv_nsec == 0);
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
int res;
/* Is the timer queue ready? */
@@ -1466,10 +1495,9 @@ timer_expired(struct async_ctx *actx)
}
return (res > 0);
+#else
+#error timer_expired is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support timers on this platform");
- return -1;
}
/*
@@ -2070,8 +2098,9 @@ static bool
check_issuer(struct async_ctx *actx, PGconn *conn)
{
const struct provider *provider = &actx->provider;
+ const char *oauth_issuer_id = conn_oauth_issuer_id(conn);
- Assert(conn->oauth_issuer_id); /* ensured by setup_oauth_parameters() */
+ Assert(oauth_issuer_id); /* ensured by setup_oauth_parameters() */
Assert(provider->issuer); /* ensured by parse_provider() */
/*---
@@ -2091,11 +2120,11 @@ check_issuer(struct async_ctx *actx, PGconn *conn)
* sent to. This comparison MUST use simple string comparison as defined
* in Section 6.2.1 of [RFC3986].
*/
- if (strcmp(conn->oauth_issuer_id, provider->issuer) != 0)
+ if (strcmp(oauth_issuer_id, provider->issuer) != 0)
{
actx_error(actx,
"the issuer identifier (%s) does not match oauth_issuer (%s)",
- provider->issuer, conn->oauth_issuer_id);
+ provider->issuer, oauth_issuer_id);
return false;
}
@@ -2172,11 +2201,14 @@ check_for_device_flow(struct async_ctx *actx)
static bool
add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *conn)
{
+ const char *oauth_client_id = conn_oauth_client_id(conn);
+ const char *oauth_client_secret = conn_oauth_client_secret(conn);
+
bool success = false;
char *username = NULL;
char *password = NULL;
- if (conn->oauth_client_secret) /* Zero-length secrets are permitted! */
+ if (oauth_client_secret) /* Zero-length secrets are permitted! */
{
/*----
* Use HTTP Basic auth to send the client_id and secret. Per RFC 6749,
@@ -2204,8 +2236,8 @@ add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *c
* would it be redundant, but some providers in the wild (e.g. Okta)
* refuse to accept it.
*/
- username = urlencode(conn->oauth_client_id);
- password = urlencode(conn->oauth_client_secret);
+ username = urlencode(oauth_client_id);
+ password = urlencode(oauth_client_secret);
if (!username || !password)
{
@@ -2225,7 +2257,7 @@ add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *c
* If we're not otherwise authenticating, client_id is REQUIRED in the
* request body.
*/
- build_urlencoded(reqbody, "client_id", conn->oauth_client_id);
+ build_urlencoded(reqbody, "client_id", oauth_client_id);
CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_NONE, goto cleanup);
actx->used_basic_auth = false;
@@ -2253,16 +2285,17 @@ cleanup:
static bool
start_device_authz(struct async_ctx *actx, PGconn *conn)
{
+ const char *oauth_scope = conn_oauth_scope(conn);
const char *device_authz_uri = actx->provider.device_authorization_endpoint;
PQExpBuffer work_buffer = &actx->work_data;
- Assert(conn->oauth_client_id); /* ensured by setup_oauth_parameters() */
+ Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
Assert(device_authz_uri); /* ensured by check_for_device_flow() */
/* Construct our request body. */
resetPQExpBuffer(work_buffer);
- if (conn->oauth_scope && conn->oauth_scope[0])
- build_urlencoded(work_buffer, "scope", conn->oauth_scope);
+ if (oauth_scope && oauth_scope[0])
+ build_urlencoded(work_buffer, "scope", oauth_scope);
if (!add_client_identification(actx, work_buffer, conn))
return false;
@@ -2344,7 +2377,7 @@ start_token_request(struct async_ctx *actx, PGconn *conn)
const char *device_code = actx->authz.device_code;
PQExpBuffer work_buffer = &actx->work_data;
- Assert(conn->oauth_client_id); /* ensured by setup_oauth_parameters() */
+ Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
Assert(token_uri); /* ensured by parse_provider() */
Assert(device_code); /* ensured by parse_device_authz() */
@@ -2487,8 +2520,9 @@ prompt_user(struct async_ctx *actx, PGconn *conn)
.verification_uri_complete = actx->authz.verification_uri_complete,
.expires_in = actx->authz.expires_in,
};
+ PQauthDataHook_type hook = PQgetAuthDataHook();
- res = PQauthDataHook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
+ res = hook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
if (!res)
{
@@ -2633,8 +2667,10 @@ done:
static PostgresPollingStatusType
pg_fe_run_oauth_flow_impl(PGconn *conn)
{
- fe_oauth_state *state = conn->sasl_state;
+ fe_oauth_state *state = conn_sasl_state(conn);
struct async_ctx *actx;
+ char *oauth_token = NULL;
+ PQExpBuffer errbuf;
if (!initialize_curl(conn))
return PGRES_POLLING_FAILED;
@@ -2676,7 +2712,7 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
do
{
/* By default, the multiplexer is the altsock. Reassign as desired. */
- conn->altsock = actx->mux;
+ set_conn_altsock(conn, actx->mux);
switch (actx->step)
{
@@ -2712,7 +2748,7 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
*/
if (!timer_expired(actx))
{
- conn->altsock = actx->timerfd;
+ set_conn_altsock(conn, actx->timerfd);
return PGRES_POLLING_READING;
}
@@ -2732,7 +2768,7 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
{
case OAUTH_STEP_INIT:
actx->errctx = "failed to fetch OpenID discovery document";
- if (!start_discovery(actx, conn->oauth_discovery_uri))
+ if (!start_discovery(actx, conn_oauth_discovery_uri(conn)))
goto error_return;
actx->step = OAUTH_STEP_DISCOVERY;
@@ -2768,9 +2804,15 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
break;
case OAUTH_STEP_TOKEN_REQUEST:
- if (!handle_token_response(actx, &conn->oauth_token))
+ if (!handle_token_response(actx, &oauth_token))
goto error_return;
+ /*
+ * Hook any oauth_token into the PGconn immediately so that
+ * the allocation isn't lost in case of an error.
+ */
+ set_conn_oauth_token(conn, oauth_token);
+
if (!actx->user_prompted)
{
/*
@@ -2783,7 +2825,7 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
actx->user_prompted = true;
}
- if (conn->oauth_token)
+ if (oauth_token)
break; /* done! */
/*
@@ -2798,7 +2840,7 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
* the client wait directly on the timerfd rather than the
* multiplexer.
*/
- conn->altsock = actx->timerfd;
+ set_conn_altsock(conn, actx->timerfd);
actx->step = OAUTH_STEP_WAIT_INTERVAL;
actx->running = 1;
@@ -2818,48 +2860,40 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
* point, actx->running will be set. But there are some corner cases
* where we can immediately loop back around; see start_request().
*/
- } while (!conn->oauth_token && !actx->running);
+ } while (!oauth_token && !actx->running);
/* If we've stored a token, we're done. Otherwise come back later. */
- return conn->oauth_token ? PGRES_POLLING_OK : PGRES_POLLING_READING;
+ return oauth_token ? PGRES_POLLING_OK : PGRES_POLLING_READING;
error_return:
+ errbuf = conn_errorMessage(conn);
/*
* Assemble the three parts of our error: context, body, and detail. See
* also the documentation for struct async_ctx.
*/
if (actx->errctx)
- {
- appendPQExpBufferStr(&conn->errorMessage,
- libpq_gettext(actx->errctx));
- appendPQExpBufferStr(&conn->errorMessage, ": ");
- }
+ appendPQExpBuffer(errbuf, "%s: ", libpq_gettext(actx->errctx));
if (PQExpBufferDataBroken(actx->errbuf))
- appendPQExpBufferStr(&conn->errorMessage,
- libpq_gettext("out of memory"));
+ appendPQExpBufferStr(errbuf, libpq_gettext("out of memory"));
else
- appendPQExpBufferStr(&conn->errorMessage, actx->errbuf.data);
+ appendPQExpBufferStr(errbuf, actx->errbuf.data);
if (actx->curl_err[0])
{
- size_t len;
-
- appendPQExpBuffer(&conn->errorMessage,
- " (libcurl: %s)", actx->curl_err);
+ appendPQExpBuffer(errbuf, " (libcurl: %s)", actx->curl_err);
/* Sometimes libcurl adds a newline to the error buffer. :( */
- len = conn->errorMessage.len;
- if (len >= 2 && conn->errorMessage.data[len - 2] == '\n')
+ if (errbuf->len >= 2 && errbuf->data[errbuf->len - 2] == '\n')
{
- conn->errorMessage.data[len - 2] = ')';
- conn->errorMessage.data[len - 1] = '\0';
- conn->errorMessage.len--;
+ errbuf->data[errbuf->len - 2] = ')';
+ errbuf->data[errbuf->len - 1] = '\0';
+ errbuf->len--;
}
}
- appendPQExpBufferChar(&conn->errorMessage, '\n');
+ appendPQExpBufferChar(errbuf, '\n');
return PGRES_POLLING_FAILED;
}
diff --git a/src/interfaces/libpq-oauth/oauth-curl.h b/src/interfaces/libpq-oauth/oauth-curl.h
new file mode 100644
index 00000000000..248d0424ad0
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-curl.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-curl.h
+ *
+ * Definitions for OAuth Device Authorization module
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-curl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_CURL_H
+#define OAUTH_CURL_H
+
+#include "libpq-fe.h"
+
+/* Exported async-auth callbacks. */
+extern PGDLLEXPORT PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+extern PGDLLEXPORT void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+#endif /* OAUTH_CURL_H */
diff --git a/src/interfaces/libpq-oauth/oauth-utils.c b/src/interfaces/libpq-oauth/oauth-utils.c
new file mode 100644
index 00000000000..57d543ac06f
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.c
@@ -0,0 +1,233 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.c
+ *
+ * "Glue" helpers providing a copy of some internal APIs from libpq. At
+ * some point in the future, we might be able to deduplicate.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq-oauth/oauth-utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+
+#include "oauth-utils.h"
+
+#ifndef USE_DYNAMIC_OAUTH
+#error oauth-utils.c is not supported in static builds
+#endif
+
+#ifdef LIBPQ_INT_H
+#error do not rely on libpq-int.h in libpq-oauth
+#endif
+
+/*
+ * Function pointers set by libpq_oauth_init().
+ */
+
+pgthreadlock_t pg_g_threadlock;
+static libpq_gettext_func libpq_gettext_impl;
+
+conn_errorMessage_func conn_errorMessage;
+conn_oauth_client_id_func conn_oauth_client_id;
+conn_oauth_client_secret_func conn_oauth_client_secret;
+conn_oauth_discovery_uri_func conn_oauth_discovery_uri;
+conn_oauth_issuer_id_func conn_oauth_issuer_id;
+conn_oauth_scope_func conn_oauth_scope;
+conn_sasl_state_func conn_sasl_state;
+
+set_conn_altsock_func set_conn_altsock;
+set_conn_oauth_token_func set_conn_oauth_token;
+
+/*-
+ * Initializes libpq-oauth by setting necessary callbacks.
+ *
+ * The current implementation relies on the following private implementation
+ * details of libpq:
+ *
+ * - pg_g_threadlock: protects libcurl initialization if the underlying Curl
+ * installation is not threadsafe
+ *
+ * - libpq_gettext: translates error messages using libpq's message domain
+ *
+ * The implementation also needs access to several members of the PGconn struct,
+ * which are not guaranteed to stay in place across minor versions. Accessors
+ * (named conn_*) and mutators (named set_conn_*) are injected here.
+ */
+void
+libpq_oauth_init(pgthreadlock_t threadlock_impl,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl,
+ conn_oauth_client_id_func clientid_impl,
+ conn_oauth_client_secret_func clientsecret_impl,
+ conn_oauth_discovery_uri_func discoveryuri_impl,
+ conn_oauth_issuer_id_func issuerid_impl,
+ conn_oauth_scope_func scope_impl,
+ conn_sasl_state_func saslstate_impl,
+ set_conn_altsock_func setaltsock_impl,
+ set_conn_oauth_token_func settoken_impl)
+{
+ pg_g_threadlock = threadlock_impl;
+ libpq_gettext_impl = gettext_impl;
+ conn_errorMessage = errmsg_impl;
+ conn_oauth_client_id = clientid_impl;
+ conn_oauth_client_secret = clientsecret_impl;
+ conn_oauth_discovery_uri = discoveryuri_impl;
+ conn_oauth_issuer_id = issuerid_impl;
+ conn_oauth_scope = scope_impl;
+ conn_sasl_state = saslstate_impl;
+ set_conn_altsock = setaltsock_impl;
+ set_conn_oauth_token = settoken_impl;
+}
+
+/*
+ * Append a formatted string to the error message buffer of the given
+ * connection, after translating it. This is a copy of libpq's internal API.
+ */
+void
+libpq_append_conn_error(PGconn *conn, const char *fmt,...)
+{
+ int save_errno = errno;
+ bool done;
+ va_list args;
+ PQExpBuffer errorMessage = conn_errorMessage(conn);
+
+ Assert(fmt[strlen(fmt) - 1] != '\n');
+
+ if (PQExpBufferBroken(errorMessage))
+ return; /* already failed */
+
+ /* Loop in case we have to retry after enlarging the buffer. */
+ do
+ {
+ errno = save_errno;
+ va_start(args, fmt);
+ done = appendPQExpBufferVA(errorMessage, libpq_gettext(fmt), args);
+ va_end(args);
+ } while (!done);
+
+ appendPQExpBufferChar(errorMessage, '\n');
+}
+
+#ifdef ENABLE_NLS
+
+/*
+ * A shim that defers to the actual libpq_gettext().
+ */
+char *
+libpq_gettext(const char *msgid)
+{
+ if (!libpq_gettext_impl)
+ {
+ /*
+ * Possible if the libpq build didn't enable NLS but the libpq-oauth
+ * build did. That's an odd mismatch, but we can handle it.
+ *
+ * Note that callers of libpq_gettext() have to treat the return value
+ * as if it were const, because builds without NLS simply pass through
+ * their argument.
+ */
+ return unconstify(char *, msgid);
+ }
+
+ return libpq_gettext_impl(msgid);
+}
+
+#endif /* ENABLE_NLS */
+
+/*
+ * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
+ */
+bool
+oauth_unsafe_debugging_enabled(void)
+{
+ const char *env = getenv("PGOAUTHDEBUG");
+
+ return (env && strcmp(env, "UNSAFE") == 0);
+}
+
+/*
+ * Duplicate SOCK_ERRNO* definitions from libpq-int.h, for use by
+ * pq_block/reset_sigpipe().
+ */
+#ifdef WIN32
+#define SOCK_ERRNO (WSAGetLastError())
+#define SOCK_ERRNO_SET(e) WSASetLastError(e)
+#else
+#define SOCK_ERRNO errno
+#define SOCK_ERRNO_SET(e) (errno = (e))
+#endif
+
+/*
+ * Block SIGPIPE for this thread. This is a copy of libpq's internal API.
+ */
+int
+pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
+{
+ sigset_t sigpipe_sigset;
+ sigset_t sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ /* Block SIGPIPE and save previous mask for later reset */
+ SOCK_ERRNO_SET(pthread_sigmask(SIG_BLOCK, &sigpipe_sigset, osigset));
+ if (SOCK_ERRNO)
+ return -1;
+
+ /* We can have a pending SIGPIPE only if it was blocked before */
+ if (sigismember(osigset, SIGPIPE))
+ {
+ /* Is there a pending SIGPIPE? */
+ if (sigpending(&sigset) != 0)
+ return -1;
+
+ if (sigismember(&sigset, SIGPIPE))
+ *sigpipe_pending = true;
+ else
+ *sigpipe_pending = false;
+ }
+ else
+ *sigpipe_pending = false;
+
+ return 0;
+}
+
+/*
+ * Discard any pending SIGPIPE and reset the signal mask. This is a copy of
+ * libpq's internal API.
+ */
+void
+pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
+{
+ int save_errno = SOCK_ERRNO;
+ int signo;
+ sigset_t sigset;
+
+ /* Clear SIGPIPE only if none was pending */
+ if (got_epipe && !sigpipe_pending)
+ {
+ if (sigpending(&sigset) == 0 &&
+ sigismember(&sigset, SIGPIPE))
+ {
+ sigset_t sigpipe_sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ sigwait(&sigpipe_sigset, &signo);
+ }
+ }
+
+ /* Restore saved block mask */
+ pthread_sigmask(SIG_SETMASK, osigset, NULL);
+
+ SOCK_ERRNO_SET(save_errno);
+}
diff --git a/src/interfaces/libpq-oauth/oauth-utils.h b/src/interfaces/libpq-oauth/oauth-utils.h
new file mode 100644
index 00000000000..f4ffefef208
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.h
@@ -0,0 +1,94 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.h
+ *
+ * Definitions providing missing libpq internal APIs
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_UTILS_H
+#define OAUTH_UTILS_H
+
+#include "fe-auth-oauth.h"
+#include "libpq-fe.h"
+#include "pqexpbuffer.h"
+
+/*
+ * A bank of callbacks to safely access members of PGconn, which are all passed
+ * to libpq_oauth_init() by libpq.
+ *
+ * Keep these aligned with the definitions in fe-auth-oauth.c as well as the
+ * static declarations in oauth-curl.c.
+ */
+#define DECLARE_GETTER(TYPE, MEMBER) \
+ typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
+ extern conn_ ## MEMBER ## _func conn_ ## MEMBER;
+
+#define DECLARE_SETTER(TYPE, MEMBER) \
+ typedef void (*set_conn_ ## MEMBER ## _func) (PGconn *conn, TYPE val); \
+ extern set_conn_ ## MEMBER ## _func set_conn_ ## MEMBER;
+
+DECLARE_GETTER(PQExpBuffer, errorMessage);
+DECLARE_GETTER(char *, oauth_client_id);
+DECLARE_GETTER(char *, oauth_client_secret);
+DECLARE_GETTER(char *, oauth_discovery_uri);
+DECLARE_GETTER(char *, oauth_issuer_id);
+DECLARE_GETTER(char *, oauth_scope);
+DECLARE_GETTER(fe_oauth_state *, sasl_state);
+
+DECLARE_SETTER(pgsocket, altsock);
+DECLARE_SETTER(char *, oauth_token);
+
+#undef DECLARE_GETTER
+#undef DECLARE_SETTER
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
+
+/* Initializes libpq-oauth. */
+extern PGDLLEXPORT void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl,
+ conn_oauth_client_id_func clientid_impl,
+ conn_oauth_client_secret_func clientsecret_impl,
+ conn_oauth_discovery_uri_func discoveryuri_impl,
+ conn_oauth_issuer_id_func issuerid_impl,
+ conn_oauth_scope_func scope_impl,
+ conn_sasl_state_func saslstate_impl,
+ set_conn_altsock_func setaltsock_impl,
+ set_conn_oauth_token_func settoken_impl);
+
+/*
+ * Duplicated APIs, copied from libpq (primarily libpq-int.h, which we cannot
+ * depend on here).
+ */
+
+typedef enum
+{
+ PG_BOOL_UNKNOWN = 0, /* Currently unknown */
+ PG_BOOL_YES, /* Yes (true) */
+ PG_BOOL_NO /* No (false) */
+} PGTernaryBool;
+
+extern void libpq_append_conn_error(PGconn *conn, const char *fmt,...) pg_attribute_printf(2, 3);
+extern bool oauth_unsafe_debugging_enabled(void);
+extern int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
+extern void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe);
+
+#ifdef ENABLE_NLS
+extern char *libpq_gettext(const char *msgid) pg_attribute_format_arg(1);
+#else
+#define libpq_gettext(x) (x)
+#endif
+
+extern pgthreadlock_t pg_g_threadlock;
+
+#define pglock_thread() pg_g_threadlock(true)
+#define pgunlock_thread() pg_g_threadlock(false)
+
+#endif /* OAUTH_UTILS_H */
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 90b0b65db6f..c6fe5fec7f6 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -31,7 +31,6 @@ endif
OBJS = \
$(WIN32RES) \
- fe-auth-oauth.o \
fe-auth-scram.o \
fe-cancel.o \
fe-connect.o \
@@ -64,9 +63,11 @@ OBJS += \
fe-secure-gssapi.o
endif
-ifeq ($(with_libcurl),yes)
-OBJS += fe-auth-oauth-curl.o
-endif
+# The OAuth implementation differs depending on the type of library being built.
+OBJS_STATIC = fe-auth-oauth.o
+
+fe-auth-oauth_shlib.o: override CPPFLAGS_SHLIB += -DUSE_DYNAMIC_OAUTH
+OBJS_SHLIB = fe-auth-oauth_shlib.o
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
@@ -86,7 +87,7 @@ endif
# that are built correctly for use in a shlib.
SHLIB_LINK_INTERNAL = -lpgcommon_shlib -lpgport_shlib
ifneq ($(PORTNAME), win32)
-SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lcurl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
+SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
else
SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi32 -lssl -lsocket -lnsl -lresolv -lintl -lm $(PTHREAD_LIBS), $(LIBS)) $(LDAP_LIBS_FE)
endif
@@ -101,12 +102,26 @@ ifeq ($(with_ssl),openssl)
PKG_CONFIG_REQUIRES_PRIVATE = libssl, libcrypto
endif
+ifeq ($(with_libcurl),yes)
+# libpq.so doesn't link against libcurl, but libpq.a needs libpq-oauth, and
+# libpq-oauth needs libcurl. Put both into *.private.
+PKG_CONFIG_REQUIRES_PRIVATE += libcurl
+%.pc: override SHLIB_LINK_INTERNAL += -lpq-oauth
+endif
+
all: all-lib libpq-refs-stamp
# Shared library stuff
include $(top_srcdir)/src/Makefile.shlib
backend_src = $(top_srcdir)/src/backend
+# Add shlib-/stlib-specific objects.
+$(shlib): override OBJS += $(OBJS_SHLIB)
+$(shlib): $(OBJS_SHLIB)
+
+$(stlib): override OBJS += $(OBJS_STATIC)
+$(stlib): $(OBJS_STATIC)
+
# Check for functions that libpq must not call, currently just exit().
# (Ideally we'd reject abort() too, but there are various scenarios where
# build toolchains insert abort() calls, e.g. to implement assert().)
@@ -115,8 +130,6 @@ backend_src = $(top_srcdir)/src/backend
# which seems to insert references to that even in pure C code. Excluding
# __tsan_func_exit is necessary when using ThreadSanitizer data race detector
# which use this function for instrumentation of function exit.
-# libcurl registers an exit handler in the memory debugging code when running
-# with LeakSanitizer.
# Skip the test when profiling, as gcc may insert exit() calls for that.
# Also skip the test on platforms where libpq infrastructure may be provided
# by statically-linked libraries, as we can't expect them to honor this
@@ -124,7 +137,7 @@ backend_src = $(top_srcdir)/src/backend
libpq-refs-stamp: $(shlib)
ifneq ($(enable_coverage), yes)
ifeq (,$(filter solaris,$(PORTNAME)))
- @if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit -e _atexit | grep exit; then \
+ @if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit | grep exit; then \
echo 'libpq must not be calling any function which invokes exit'; exit 1; \
fi
endif
@@ -138,6 +151,11 @@ fe-misc.o: fe-misc.c $(top_builddir)/src/port/pg_config_paths.h
$(top_builddir)/src/port/pg_config_paths.h:
$(MAKE) -C $(top_builddir)/src/port pg_config_paths.h
+# Use src/common/Makefile's trick for tracking dependencies of shlib-specific
+# objects.
+%_shlib.o: %.c %.o
+ $(CC) $(CFLAGS) $(CFLAGS_SL) $(CPPFLAGS) $(CPPFLAGS_SHLIB) -c $< -o $@
+
install: all installdirs install-lib
$(INSTALL_DATA) $(srcdir)/libpq-fe.h '$(DESTDIR)$(includedir)'
$(INSTALL_DATA) $(srcdir)/libpq-events.h '$(DESTDIR)$(includedir)'
@@ -171,6 +189,6 @@ uninstall: uninstall-lib
clean distclean: clean-lib
$(MAKE) -C test $@
rm -rf tmp_check
- rm -f $(OBJS) pthread.h libpq-refs-stamp
+ rm -f $(OBJS) $(OBJS_SHLIB) $(OBJS_STATIC) pthread.h libpq-refs-stamp
# Might be left over from a Win32 client-only build
rm -f pg_config_paths.h
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index d5143766858..0625cf39e9a 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -210,3 +210,4 @@ PQsetAuthDataHook 207
PQgetAuthDataHook 208
PQdefaultAuthDataHook 209
PQfullProtocolVersion 210
+appendPQExpBufferVA 211
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
index ab6a45e2aba..9fbff89a21d 100644
--- a/src/interfaces/libpq/fe-auth-oauth.c
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -15,6 +15,10 @@
#include "postgres_fe.h"
+#ifdef USE_DYNAMIC_OAUTH
+#include <dlfcn.h>
+#endif
+
#include "common/base64.h"
#include "common/hmac.h"
#include "common/jsonapi.h"
@@ -22,6 +26,7 @@
#include "fe-auth.h"
#include "fe-auth-oauth.h"
#include "mb/pg_wchar.h"
+#include "pg_config_paths.h"
/* The exported OAuth callback mechanism. */
static void *oauth_init(PGconn *conn, const char *password,
@@ -721,6 +726,218 @@ cleanup_user_oauth_flow(PGconn *conn)
state->async_ctx = NULL;
}
+/*-------------
+ * Builtin Flow
+ *
+ * There are three potential implementations of use_builtin_flow:
+ *
+ * 1) If the OAuth client is disabled at configuration time, return false.
+ * Dependent clients must provide their own flow.
+ * 2) If the OAuth client is enabled and USE_DYNAMIC_OAUTH is defined, dlopen()
+ * the libpq-oauth plugin and use its implementation.
+ * 3) Otherwise, use flow callbacks that are statically linked into the
+ * executable.
+ */
+
+#if !defined(USE_LIBCURL)
+
+/*
+ * This configuration doesn't support the builtin flow.
+ */
+
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ return false;
+}
+
+#elif defined(USE_DYNAMIC_OAUTH)
+
+/*
+ * Use the builtin flow in the libpq-oauth plugin, which is loaded at runtime.
+ */
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
+
+/*
+ * Define accessor/mutator shims to inject into libpq-oauth, so that it doesn't
+ * depend on the offsets within PGconn. (These have changed during minor version
+ * updates in the past.)
+ */
+
+#define DEFINE_GETTER(TYPE, MEMBER) \
+ typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
+ static TYPE conn_ ## MEMBER(PGconn *conn) { return conn->MEMBER; }
+
+/* Like DEFINE_GETTER, but returns a pointer to the member. */
+#define DEFINE_GETTER_P(TYPE, MEMBER) \
+ typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
+ static TYPE conn_ ## MEMBER(PGconn *conn) { return &conn->MEMBER; }
+
+#define DEFINE_SETTER(TYPE, MEMBER) \
+ typedef void (*set_conn_ ## MEMBER ## _func) (PGconn *conn, TYPE val); \
+ static void set_conn_ ## MEMBER(PGconn *conn, TYPE val) { conn->MEMBER = val; }
+
+DEFINE_GETTER_P(PQExpBuffer, errorMessage);
+DEFINE_GETTER(char *, oauth_client_id);
+DEFINE_GETTER(char *, oauth_client_secret);
+DEFINE_GETTER(char *, oauth_discovery_uri);
+DEFINE_GETTER(char *, oauth_issuer_id);
+DEFINE_GETTER(char *, oauth_scope);
+DEFINE_GETTER(fe_oauth_state *, sasl_state);
+
+DEFINE_SETTER(pgsocket, altsock);
+DEFINE_SETTER(char *, oauth_token);
+
+/*
+ * Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
+ * callbacks into the connection's async auth handlers.
+ *
+ * Failure to load here results in a relatively quiet connection error, to
+ * handle the use case where the build supports loading a flow but a user does
+ * not want to install it. Troubleshooting of linker/loader failures can be done
+ * via PGOAUTHDEBUG.
+ */
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ static bool initialized = false;
+ static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
+ int lockerr;
+
+ void (*init) (pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl,
+ conn_oauth_client_id_func clientid_impl,
+ conn_oauth_client_secret_func clientsecret_impl,
+ conn_oauth_discovery_uri_func discoveryuri_impl,
+ conn_oauth_issuer_id_func issuerid_impl,
+ conn_oauth_scope_func scope_impl,
+ conn_sasl_state_func saslstate_impl,
+ set_conn_altsock_func setaltsock_impl,
+ set_conn_oauth_token_func settoken_impl);
+ PostgresPollingStatusType (*flow) (PGconn *conn);
+ void (*cleanup) (PGconn *conn);
+
+ /*
+ * On macOS only, load the module using its absolute install path; the
+ * standard search behavior is not very helpful for this use case. Unlike
+ * on other platforms, DYLD_LIBRARY_PATH is used as a fallback even with
+ * absolute paths (modulo SIP effects), so tests can continue to work.
+ *
+ * On the other platforms, load the module using only the basename, to
+ * rely on the runtime linker's standard search behavior.
+ */
+ const char *const module_name =
+#if defined(__darwin__)
+ LIBDIR "/libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
+#else
+ "libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
+#endif
+
+ state->builtin_flow = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
+ if (!state->builtin_flow)
+ {
+ /*
+ * For end users, this probably isn't an error condition, it just
+ * means the flow isn't installed. Developers and package maintainers
+ * may want to debug this via the PGOAUTHDEBUG envvar, though.
+ *
+ * Note that POSIX dlerror() isn't guaranteed to be threadsafe.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
+
+ return false;
+ }
+
+ if ((init = dlsym(state->builtin_flow, "libpq_oauth_init")) == NULL
+ || (flow = dlsym(state->builtin_flow, "pg_fe_run_oauth_flow")) == NULL
+ || (cleanup = dlsym(state->builtin_flow, "pg_fe_cleanup_oauth_flow")) == NULL)
+ {
+ /*
+ * This is more of an error condition than the one above, but due to
+ * the dlerror() threadsafety issue, lock it behind PGOAUTHDEBUG too.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
+
+ dlclose(state->builtin_flow);
+ return false;
+ }
+
+ /*
+ * Past this point, we do not unload the module. It stays in the process
+ * permanently.
+ */
+
+ /*
+ * We need to inject necessary function pointers into the module. This
+ * only needs to be done once -- even if the pointers are constant,
+ * assigning them while another thread is executing the flows feels like
+ * tempting fate.
+ */
+ if ((lockerr = pthread_mutex_lock(&init_mutex)) != 0)
+ {
+ /* Should not happen... but don't continue if it does. */
+ Assert(false);
+
+ libpq_append_conn_error(conn, "failed to lock mutex (%d)", lockerr);
+ return false;
+ }
+
+ if (!initialized)
+ {
+ init(pg_g_threadlock,
+#ifdef ENABLE_NLS
+ libpq_gettext,
+#else
+ NULL,
+#endif
+ conn_errorMessage,
+ conn_oauth_client_id,
+ conn_oauth_client_secret,
+ conn_oauth_discovery_uri,
+ conn_oauth_issuer_id,
+ conn_oauth_scope,
+ conn_sasl_state,
+ set_conn_altsock,
+ set_conn_oauth_token);
+
+ initialized = true;
+ }
+
+ pthread_mutex_unlock(&init_mutex);
+
+ /* Set our asynchronous callbacks. */
+ conn->async_auth = flow;
+ conn->cleanup_async_auth = cleanup;
+
+ return true;
+}
+
+#else
+
+/*
+ * Use the builtin flow in libpq-oauth.a (see libpq-oauth/oauth-curl.h).
+ */
+
+extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ /* Set our asynchronous callbacks. */
+ conn->async_auth = pg_fe_run_oauth_flow;
+ conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
+
+ return true;
+}
+
+#endif /* USE_LIBCURL */
+
+
/*
* Chooses an OAuth client flow for the connection, which will retrieve a Bearer
* token for presentation to the server.
@@ -792,18 +1009,10 @@ setup_token_request(PGconn *conn, fe_oauth_state *state)
libpq_append_conn_error(conn, "user-defined OAuth flow failed");
goto fail;
}
- else
+ else if (!use_builtin_flow(conn, state))
{
-#if USE_LIBCURL
- /* Hand off to our built-in OAuth flow. */
- conn->async_auth = pg_fe_run_oauth_flow;
- conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
-
-#else
- libpq_append_conn_error(conn, "no custom OAuth flows are available, and libpq was not built with libcurl support");
+ libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
goto fail;
-
-#endif
}
return true;
diff --git a/src/interfaces/libpq/fe-auth-oauth.h b/src/interfaces/libpq/fe-auth-oauth.h
index 3f1a7503a01..0d59e91605b 100644
--- a/src/interfaces/libpq/fe-auth-oauth.h
+++ b/src/interfaces/libpq/fe-auth-oauth.h
@@ -15,8 +15,8 @@
#ifndef FE_AUTH_OAUTH_H
#define FE_AUTH_OAUTH_H
+#include "fe-auth-sasl.h"
#include "libpq-fe.h"
-#include "libpq-int.h"
enum fe_oauth_step
@@ -27,18 +27,24 @@ enum fe_oauth_step
FE_OAUTH_SERVER_ERROR,
};
+/*
+ * This struct is exported to the libpq-oauth module. If changes are needed
+ * during backports to stable branches, please keep ABI compatibility (no
+ * changes to existing members, add new members at the end, etc.).
+ */
typedef struct
{
enum fe_oauth_step step;
PGconn *conn;
void *async_ctx;
+
+ void *builtin_flow;
} fe_oauth_state;
-extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
-extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
extern void pqClearOAuthToken(PGconn *conn);
extern bool oauth_unsafe_debugging_enabled(void);
+extern bool use_builtin_flow(PGconn *conn, fe_oauth_state *state);
/* Mechanisms in fe-auth-oauth.c */
extern const pg_fe_sasl_mech pg_oauth_mech;
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index 292fecf3320..a74e885b169 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -38,10 +38,6 @@ if gssapi.found()
)
endif
-if libcurl.found()
- libpq_sources += files('fe-auth-oauth-curl.c')
-endif
-
export_file = custom_target('libpq.exports',
kwargs: gen_export_kwargs,
)
@@ -50,6 +46,9 @@ export_file = custom_target('libpq.exports',
libpq_inc = include_directories('.', '../../port')
libpq_c_args = ['-DSO_MAJOR_VERSION=5']
+# The OAuth implementation differs depending on the type of library being built.
+libpq_so_c_args = ['-DUSE_DYNAMIC_OAUTH']
+
# Not using both_libraries() here as
# 1) resource files should only be in the shared library
# 2) we want the .pc file to include a dependency to {pgport,common}_static for
@@ -70,7 +69,7 @@ libpq_st = static_library('libpq',
libpq_so = shared_library('libpq',
libpq_sources + libpq_so_sources,
include_directories: [libpq_inc, postgres_inc],
- c_args: libpq_c_args,
+ c_args: libpq_c_args + libpq_so_c_args,
c_pch: pch_postgres_fe_h,
version: '5.' + pg_version_major.to_string(),
soversion: host_system != 'windows' ? '5' : '',
@@ -86,12 +85,26 @@ libpq = declare_dependency(
include_directories: [include_directories('.')]
)
+private_deps = [
+ frontend_stlib_code,
+ libpq_deps,
+]
+
+if oauth_flow_supported
+ # libpq.so doesn't link against libcurl, but libpq.a needs libpq-oauth, and
+ # libpq-oauth needs libcurl. Put both into *.private.
+ private_deps += [
+ libpq_oauth_deps,
+ '-lpq-oauth',
+ ]
+endif
+
pkgconfig.generate(
name: 'libpq',
description: 'PostgreSQL libpq library',
url: pg_url,
libraries: libpq,
- libraries_private: [frontend_stlib_code, libpq_deps],
+ libraries_private: private_deps,
)
install_headers(
diff --git a/src/interfaces/libpq/nls.mk b/src/interfaces/libpq/nls.mk
index ae761265852..b87df277d93 100644
--- a/src/interfaces/libpq/nls.mk
+++ b/src/interfaces/libpq/nls.mk
@@ -13,15 +13,21 @@ GETTEXT_FILES = fe-auth.c \
fe-secure-common.c \
fe-secure-gssapi.c \
fe-secure-openssl.c \
- win32.c
-GETTEXT_TRIGGERS = libpq_append_conn_error:2 \
+ win32.c \
+ ../libpq-oauth/oauth-curl.c \
+ ../libpq-oauth/oauth-utils.c
+GETTEXT_TRIGGERS = actx_error:2 \
+ libpq_append_conn_error:2 \
libpq_append_error:2 \
libpq_gettext \
libpq_ngettext:1,2 \
+ oauth_parse_set_error:2 \
pqInternalNotice:2
-GETTEXT_FLAGS = libpq_append_conn_error:2:c-format \
+GETTEXT_FLAGS = actx_error:2:c-format \
+ libpq_append_conn_error:2:c-format \
libpq_append_error:2:c-format \
libpq_gettext:1:pass-c-format \
libpq_ngettext:1:pass-c-format \
libpq_ngettext:2:pass-c-format \
+ oauth_parse_set_error:2:c-format \
pqInternalNotice:2:c-format
diff --git a/src/makefiles/meson.build b/src/makefiles/meson.build
index 55da678ec27..91a8de1ee9b 100644
--- a/src/makefiles/meson.build
+++ b/src/makefiles/meson.build
@@ -203,6 +203,8 @@ pgxs_empty = [
'LIBNUMA_CFLAGS', 'LIBNUMA_LIBS',
'LIBURING_CFLAGS', 'LIBURING_LIBS',
+
+ 'LIBCURL_CPPFLAGS', 'LIBCURL_LDFLAGS', 'LIBCURL_LDLIBS',
]
if host_system == 'windows' and cc.get_argument_syntax() != 'msvc'
diff --git a/src/test/modules/oauth_validator/meson.build b/src/test/modules/oauth_validator/meson.build
index 36d1b26369f..e190f9cf15a 100644
--- a/src/test/modules/oauth_validator/meson.build
+++ b/src/test/modules/oauth_validator/meson.build
@@ -78,7 +78,7 @@ tests += {
],
'env': {
'PYTHON': python.path(),
- 'with_libcurl': libcurl.found() ? 'yes' : 'no',
+ 'with_libcurl': oauth_flow_supported ? 'yes' : 'no',
'with_python': 'yes',
},
},
diff --git a/src/test/modules/oauth_validator/t/002_client.pl b/src/test/modules/oauth_validator/t/002_client.pl
index 8dd502f41e1..21d4acc1926 100644
--- a/src/test/modules/oauth_validator/t/002_client.pl
+++ b/src/test/modules/oauth_validator/t/002_client.pl
@@ -110,7 +110,7 @@ if ($ENV{with_libcurl} ne 'yes')
"fails without custom hook installed",
flags => ["--no-hook"],
expected_stderr =>
- qr/no custom OAuth flows are available, and libpq was not built with libcurl support/
+ qr/no OAuth flows are available \(try installing the libpq-oauth package\)/
);
}
--
2.34.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-30 17:59 ` Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-04-30 17:59 UTC (permalink / raw)
To: Daniel Gustafsson <[email protected]>; +Cc: Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Wed, Apr 30, 2025 at 5:55 AM Daniel Gustafsson <[email protected]> wrote:
> > To keep things moving: I assume this is unacceptable. So v10 redirects
> > every access to a PGconn struct member through a shim, similarly to
> > how conn->errorMessage was translated in v9. This adds plenty of new
> > boilerplate, but not a whole lot of complexity. To try to keep us
> > honest, libpq-int.h has been removed from the libpq-oauth includes.
>
> That admittedly seems like a win regardless.
Yeah, it moves us much closer to the long-term goal.
> We should either clarify that it was never shipped as part of libpq core, or
> remove this altogether.
Done in v11, with your suggested wording.
> I think this explanatory paragraph should come before the function prototype.
Done.
> Nitpick, but it won't be .so everywhere. Would this be clearar if spelled out
> with something like "do not rely on libpq-int.h when building libpq-oauth as
> dynamic shared lib"?
I went with "do not rely on libpq-int.h in dynamic builds of
libpq-oauth", since devs are hopefully going to be the only people who
see it. I've also fixed up an errant #endif label right above it.
I'd ideally like to get a working split in for beta. Barring
objections, I plan to get this pushed tomorrow so that the buildfarm
has time to highlight any corner cases well before the Saturday
freeze. I still see the choice of naming (with its forced-ABI break
every major version) as needing more scrutiny, and probably worth a
Revisit entry.
The CI still looks happy, and I will spend today with VMs and more
testing on the Autoconf side. I'll try to peer at Alpine and musl
libc, too; dogfish and basilisk are the Curl-enabled animals that
caught my attention most.
Thanks!
--Jacob
1: e86e93f7ac8 ! 1: 5a1d1345919 oauth: Move the builtin flow into a separate module
@@ Commit message
Per request from Tom Lane and Bruce Momjian. Based on an initial patch
by Daniel Gustafsson, who also contributed docs changes. The "bare"
- dlopen() concept came from Thomas Munro. Many many people reviewed the
- design and implementation; thank you!
+ dlopen() concept came from Thomas Munro. Many people reviewed the design
+ and implementation; thank you!
Co-authored-by: Daniel Gustafsson <[email protected]>
Reviewed-by: Andres Freund <[email protected]>
Reviewed-by: Christoph Berg <[email protected]>
+ Reviewed-by: Daniel Gustafsson <[email protected]>
Reviewed-by: Jelte Fennema-Nio <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Wolfgang Walther <[email protected]>
@@ src/interfaces/libpq-oauth/Makefile (new)
## src/interfaces/libpq-oauth/README (new) ##
@@
+libpq-oauth is an optional module implementing the Device Authorization flow for
-+OAuth clients (RFC 8628). It was originally developed as part of libpq core and
-+later split out as its own shared library in order to isolate its dependency on
-+libcurl. (End users who don't want the Curl dependency can simply choose not to
-+install this module.)
++OAuth clients (RFC 8628). It is maintained as its own shared library in order to
++isolate its dependency on libcurl. (End users who don't want the Curl dependency
++can simply choose not to install this module.)
+
+If a connection string allows the use of OAuth, and the server asks for it, and
+a libpq client has not installed its own custom OAuth flow, libpq will attempt
@@ src/interfaces/libpq-oauth/README (new)
+pg_fe_run_oauth_flow and pg_fe_cleanup_oauth_flow are implementations of
+conn->async_auth and conn->cleanup_async_auth, respectively.
+
++At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
++libpq_gettext(), which must be injected by libpq using this initialization
++function before the flow is run:
++
+- void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl,
@@ src/interfaces/libpq-oauth/README (new)
+ set_conn_altsock_func setaltsock_impl,
+ set_conn_oauth_token_func settoken_impl);
+
-+At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
-+libpq_gettext(), which must be injected by libpq using this initialization
-+function before the flow is run.
-+
+It also relies on access to several members of the PGconn struct. Not only can
+these change positions across minor versions, but the offsets aren't necessarily
+stable within a single minor release (conn->errorMessage, for instance, can
@@ src/interfaces/libpq/fe-auth-oauth-curl.c => src/interfaces/libpq-oauth/oauth-cu
+#define set_conn_altsock(CONN, VAL) do { CONN->altsock = VAL; } while (0)
+#define set_conn_oauth_token(CONN, VAL) do { CONN->oauth_token = VAL; } while (0)
+
-+#endif /* !USE_DYNAMIC_OAUTH */
++#endif /* USE_DYNAMIC_OAUTH */
+
+/* One final guardrail against accidental inclusion... */
+#if defined(USE_DYNAMIC_OAUTH) && defined(LIBPQ_INT_H)
-+#error do not rely on libpq-int.h in libpq-oauth.so
++#error do not rely on libpq-int.h in dynamic builds of libpq-oauth
+#endif
/*
@@ src/interfaces/libpq-oauth/oauth-utils.c (new)
+#endif
+
+#ifdef LIBPQ_INT_H
-+#error do not rely on libpq-int.h in libpq-oauth
++#error do not rely on libpq-int.h in dynamic builds of libpq-oauth
+#endif
+
+/*
Attachments:
[text/plain] since-v10.diff.txt (3.8K, ../../CAOYmi+mXoq9dUagnkXDgWa72QjCg8SdagBz_yGPUdh1Px0XD5g@mail.gmail.com/2-since-v10.diff.txt)
download | inline:
1: e86e93f7ac8 ! 1: 5a1d1345919 oauth: Move the builtin flow into a separate module
@@ Commit message
Per request from Tom Lane and Bruce Momjian. Based on an initial patch
by Daniel Gustafsson, who also contributed docs changes. The "bare"
- dlopen() concept came from Thomas Munro. Many many people reviewed the
- design and implementation; thank you!
+ dlopen() concept came from Thomas Munro. Many people reviewed the design
+ and implementation; thank you!
Co-authored-by: Daniel Gustafsson <[email protected]>
Reviewed-by: Andres Freund <[email protected]>
Reviewed-by: Christoph Berg <[email protected]>
+ Reviewed-by: Daniel Gustafsson <[email protected]>
Reviewed-by: Jelte Fennema-Nio <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Wolfgang Walther <[email protected]>
@@ src/interfaces/libpq-oauth/Makefile (new)
## src/interfaces/libpq-oauth/README (new) ##
@@
+libpq-oauth is an optional module implementing the Device Authorization flow for
-+OAuth clients (RFC 8628). It was originally developed as part of libpq core and
-+later split out as its own shared library in order to isolate its dependency on
-+libcurl. (End users who don't want the Curl dependency can simply choose not to
-+install this module.)
++OAuth clients (RFC 8628). It is maintained as its own shared library in order to
++isolate its dependency on libcurl. (End users who don't want the Curl dependency
++can simply choose not to install this module.)
+
+If a connection string allows the use of OAuth, and the server asks for it, and
+a libpq client has not installed its own custom OAuth flow, libpq will attempt
@@ src/interfaces/libpq-oauth/README (new)
+pg_fe_run_oauth_flow and pg_fe_cleanup_oauth_flow are implementations of
+conn->async_auth and conn->cleanup_async_auth, respectively.
+
++At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
++libpq_gettext(), which must be injected by libpq using this initialization
++function before the flow is run:
++
+- void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl,
@@ src/interfaces/libpq-oauth/README (new)
+ set_conn_altsock_func setaltsock_impl,
+ set_conn_oauth_token_func settoken_impl);
+
-+At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
-+libpq_gettext(), which must be injected by libpq using this initialization
-+function before the flow is run.
-+
+It also relies on access to several members of the PGconn struct. Not only can
+these change positions across minor versions, but the offsets aren't necessarily
+stable within a single minor release (conn->errorMessage, for instance, can
@@ src/interfaces/libpq/fe-auth-oauth-curl.c => src/interfaces/libpq-oauth/oauth-cu
+#define set_conn_altsock(CONN, VAL) do { CONN->altsock = VAL; } while (0)
+#define set_conn_oauth_token(CONN, VAL) do { CONN->oauth_token = VAL; } while (0)
+
-+#endif /* !USE_DYNAMIC_OAUTH */
++#endif /* USE_DYNAMIC_OAUTH */
+
+/* One final guardrail against accidental inclusion... */
+#if defined(USE_DYNAMIC_OAUTH) && defined(LIBPQ_INT_H)
-+#error do not rely on libpq-int.h in libpq-oauth.so
++#error do not rely on libpq-int.h in dynamic builds of libpq-oauth
+#endif
/*
@@ src/interfaces/libpq-oauth/oauth-utils.c (new)
+#endif
+
+#ifdef LIBPQ_INT_H
-+#error do not rely on libpq-int.h in libpq-oauth
++#error do not rely on libpq-int.h in dynamic builds of libpq-oauth
+#endif
+
+/*
[application/octet-stream] v11-0001-oauth-Move-the-builtin-flow-into-a-separate-modu.patch (69.6K, ../../CAOYmi+mXoq9dUagnkXDgWa72QjCg8SdagBz_yGPUdh1Px0XD5g@mail.gmail.com/3-v11-0001-oauth-Move-the-builtin-flow-into-a-separate-modu.patch)
download | inline diff:
From 5a1d134591976ddb235aa4c077b2ea623f368f0a Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Wed, 26 Mar 2025 10:55:28 -0700
Subject: [PATCH v11] oauth: Move the builtin flow into a separate module
The additional packaging footprint of the OAuth Curl dependency, as well
as the existence of libcurl in the address space even if OAuth isn't
ever used by a client, has raised some concerns. Split off this
dependency into a separate loadable module called libpq-oauth.
When configured using --with-libcurl, libpq.so searches for this new
module via dlopen(). End users may choose not to install the libpq-oauth
module, in which case the default flow is disabled.
For static applications using libpq.a, the libpq-oauth staticlib is a
mandatory link-time dependency for --with-libcurl builds. libpq.pc has
been updated accordingly.
The default flow relies on some libpq internals. Some of these can be
safely duplicated (such as the SIGPIPE handlers), but others need to be
shared between libpq and libpq-oauth for thread-safety. To avoid
exporting these internals to all libpq clients forever, these
dependencies are instead injected from the libpq side via an
initialization function. This also lets libpq communicate the offsets of
PGconn struct members to libpq-oauth, so that we can function without
crashing if the module on the search path came from a different build of
Postgres. (A minor-version upgrade could swap the libpq-oauth module out
from under a long-running libpq client before it does its first load of
the OAuth flow.)
This ABI is considered "private". The module has no SONAME or version
symlinks, and it's named libpq-oauth-<major>.so to avoid mixing and
matching across Postgres versions. (Future improvements may promote this
"OAuth flow plugin" to a first-class concept, at which point we would
need a public API to replace this anyway.)
Additionally, NLS support for error messages in b3f0be788a was
incomplete, because the new error macros weren't being scanned by
xgettext. Fix that now.
Per request from Tom Lane and Bruce Momjian. Based on an initial patch
by Daniel Gustafsson, who also contributed docs changes. The "bare"
dlopen() concept came from Thomas Munro. Many people reviewed the design
and implementation; thank you!
Co-authored-by: Daniel Gustafsson <[email protected]>
Reviewed-by: Andres Freund <[email protected]>
Reviewed-by: Christoph Berg <[email protected]>
Reviewed-by: Daniel Gustafsson <[email protected]>
Reviewed-by: Jelte Fennema-Nio <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Wolfgang Walther <[email protected]>
Discussion: https://postgr.es/m/641687.1742360249%40sss.pgh.pa.us
---
config/programs.m4 | 17 +-
configure | 50 +++-
configure.ac | 26 +-
doc/src/sgml/installation.sgml | 8 +
doc/src/sgml/libpq.sgml | 30 ++-
meson.build | 32 ++-
src/Makefile.global.in | 3 +
src/interfaces/Makefile | 12 +
src/interfaces/libpq-oauth/Makefile | 83 +++++++
src/interfaces/libpq-oauth/README | 57 +++++
src/interfaces/libpq-oauth/exports.txt | 4 +
src/interfaces/libpq-oauth/meson.build | 45 ++++
.../oauth-curl.c} | 180 ++++++++------
src/interfaces/libpq-oauth/oauth-curl.h | 24 ++
src/interfaces/libpq-oauth/oauth-utils.c | 233 ++++++++++++++++++
src/interfaces/libpq-oauth/oauth-utils.h | 94 +++++++
src/interfaces/libpq/Makefile | 36 ++-
src/interfaces/libpq/exports.txt | 1 +
src/interfaces/libpq/fe-auth-oauth.c | 229 ++++++++++++++++-
src/interfaces/libpq/fe-auth-oauth.h | 12 +-
src/interfaces/libpq/meson.build | 25 +-
src/interfaces/libpq/nls.mk | 12 +-
src/makefiles/meson.build | 2 +
src/test/modules/oauth_validator/meson.build | 2 +-
.../modules/oauth_validator/t/002_client.pl | 2 +-
25 files changed, 1079 insertions(+), 140 deletions(-)
create mode 100644 src/interfaces/libpq-oauth/Makefile
create mode 100644 src/interfaces/libpq-oauth/README
create mode 100644 src/interfaces/libpq-oauth/exports.txt
create mode 100644 src/interfaces/libpq-oauth/meson.build
rename src/interfaces/{libpq/fe-auth-oauth-curl.c => libpq-oauth/oauth-curl.c} (94%)
create mode 100644 src/interfaces/libpq-oauth/oauth-curl.h
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.c
create mode 100644 src/interfaces/libpq-oauth/oauth-utils.h
diff --git a/config/programs.m4 b/config/programs.m4
index 0a07feb37cc..0ad1e58b48d 100644
--- a/config/programs.m4
+++ b/config/programs.m4
@@ -286,9 +286,20 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
[
AC_CHECK_HEADER(curl/curl.h, [],
[AC_MSG_ERROR([header file <curl/curl.h> is required for --with-libcurl])])
- AC_CHECK_LIB(curl, curl_multi_init, [],
+ AC_CHECK_LIB(curl, curl_multi_init, [
+ AC_DEFINE([HAVE_LIBCURL], [1], [Define to 1 if you have the `curl' library (-lcurl).])
+ AC_SUBST(LIBCURL_LDLIBS, -lcurl)
+ ],
[AC_MSG_ERROR([library 'curl' does not provide curl_multi_init])])
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+ LIBS="$LIBCURL_LDLIBS $LIBS"
+
# Check to see whether the current platform supports threadsafe Curl
# initialization.
AC_CACHE_CHECK([for curl_global_init thread safety], [pgac_cv__libcurl_threadsafe_init],
@@ -338,4 +349,8 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
*** lookups. Rebuild libcurl with the AsynchDNS feature enabled in order
*** to use it with libpq.])
fi
+
+ CPPFLAGS=$pgac_save_CPPFLAGS
+ LDFLAGS=$pgac_save_LDFLAGS
+ LIBS=$pgac_save_LIBS
])# PGAC_CHECK_LIBCURL
diff --git a/configure b/configure
index 0936010718d..a4c4bcb40ea 100755
--- a/configure
+++ b/configure
@@ -655,6 +655,7 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+LIBCURL_LDLIBS
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -711,6 +712,8 @@ with_libxml
LIBNUMA_LIBS
LIBNUMA_CFLAGS
with_libnuma
+LIBCURL_LDFLAGS
+LIBCURL_CPPFLAGS
LIBCURL_LIBS
LIBCURL_CFLAGS
with_libcurl
@@ -9053,19 +9056,27 @@ $as_echo "yes" >&6; }
fi
- # We only care about -I, -D, and -L switches;
- # note that -lcurl will be added by PGAC_CHECK_LIBCURL below.
+ # Curl's flags are kept separate from the standard CPPFLAGS/LDFLAGS. We use
+ # them only for libpq-oauth.
+ LIBCURL_CPPFLAGS=
+ LIBCURL_LDFLAGS=
+
+ # We only care about -I, -D, and -L switches. Note that -lcurl will be added
+ # to LIBCURL_LDLIBS by PGAC_CHECK_LIBCURL, below.
for pgac_option in $LIBCURL_CFLAGS; do
case $pgac_option in
- -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+ -I*|-D*) LIBCURL_CPPFLAGS="$LIBCURL_CPPFLAGS $pgac_option";;
esac
done
for pgac_option in $LIBCURL_LIBS; do
case $pgac_option in
- -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+ -L*) LIBCURL_LDFLAGS="$LIBCURL_LDFLAGS $pgac_option";;
esac
done
+
+
+
# OAuth requires python for testing
if test "$with_python" != yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: *** OAuth support tests require --with-python to run" >&5
@@ -12704,9 +12715,6 @@ fi
fi
-# XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
-# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
-# dependency on that platform?
if test "$with_libcurl" = yes ; then
ac_fn_c_check_header_mongrel "$LINENO" "curl/curl.h" "ac_cv_header_curl_curl_h" "$ac_includes_default"
@@ -12754,17 +12762,26 @@ fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_curl_curl_multi_init" >&5
$as_echo "$ac_cv_lib_curl_curl_multi_init" >&6; }
if test "x$ac_cv_lib_curl_curl_multi_init" = xyes; then :
- cat >>confdefs.h <<_ACEOF
-#define HAVE_LIBCURL 1
-_ACEOF
- LIBS="-lcurl $LIBS"
+
+$as_echo "#define HAVE_LIBCURL 1" >>confdefs.h
+
+ LIBCURL_LDLIBS=-lcurl
+
else
as_fn_error $? "library 'curl' does not provide curl_multi_init" "$LINENO" 5
fi
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+ LIBS="$LIBCURL_LDLIBS $LIBS"
+
# Check to see whether the current platform supports threadsafe Curl
# initialization.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_global_init thread safety" >&5
@@ -12868,6 +12885,10 @@ $as_echo "$pgac_cv__libcurl_async_dns" >&6; }
*** to use it with libpq." "$LINENO" 5
fi
+ CPPFLAGS=$pgac_save_CPPFLAGS
+ LDFLAGS=$pgac_save_LDFLAGS
+ LIBS=$pgac_save_LIBS
+
fi
if test "$with_gssapi" = yes ; then
@@ -14516,6 +14537,13 @@ done
fi
+if test "$with_libcurl" = yes ; then
+ # Error out early if this platform can't support libpq-oauth.
+ if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
+ as_fn_error $? "client OAuth is not supported on this platform" "$LINENO" 5
+ fi
+fi
+
##
## Types, structures, compiler characteristics
##
diff --git a/configure.ac b/configure.ac
index 2a78cddd825..c0471030e90 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1033,19 +1033,27 @@ if test "$with_libcurl" = yes ; then
# to explicitly set TLS 1.3 ciphersuites).
PKG_CHECK_MODULES(LIBCURL, [libcurl >= 7.61.0])
- # We only care about -I, -D, and -L switches;
- # note that -lcurl will be added by PGAC_CHECK_LIBCURL below.
+ # Curl's flags are kept separate from the standard CPPFLAGS/LDFLAGS. We use
+ # them only for libpq-oauth.
+ LIBCURL_CPPFLAGS=
+ LIBCURL_LDFLAGS=
+
+ # We only care about -I, -D, and -L switches. Note that -lcurl will be added
+ # to LIBCURL_LDLIBS by PGAC_CHECK_LIBCURL, below.
for pgac_option in $LIBCURL_CFLAGS; do
case $pgac_option in
- -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+ -I*|-D*) LIBCURL_CPPFLAGS="$LIBCURL_CPPFLAGS $pgac_option";;
esac
done
for pgac_option in $LIBCURL_LIBS; do
case $pgac_option in
- -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+ -L*) LIBCURL_LDFLAGS="$LIBCURL_LDFLAGS $pgac_option";;
esac
done
+ AC_SUBST(LIBCURL_CPPFLAGS)
+ AC_SUBST(LIBCURL_LDFLAGS)
+
# OAuth requires python for testing
if test "$with_python" != yes; then
AC_MSG_WARN([*** OAuth support tests require --with-python to run])
@@ -1354,9 +1362,6 @@ failure. It is possible the compiler isn't looking in the proper directory.
Use --without-zlib to disable zlib support.])])
fi
-# XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
-# during gss_acquire_cred(). This is possibly related to Curl's Heimdal
-# dependency on that platform?
if test "$with_libcurl" = yes ; then
PGAC_CHECK_LIBCURL
fi
@@ -1654,6 +1659,13 @@ if test "$PORTNAME" = "win32" ; then
AC_CHECK_HEADERS(crtdefs.h)
fi
+if test "$with_libcurl" = yes ; then
+ # Error out early if this platform can't support libpq-oauth.
+ if test "$ac_cv_header_sys_event_h" != yes -a "$ac_cv_header_sys_epoll_h" != yes; then
+ AC_MSG_ERROR([client-side OAuth is not supported on this platform])
+ fi
+fi
+
##
## Types, structures, compiler characteristics
##
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index e7ffb942bbd..60419312113 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -313,6 +313,14 @@
</para>
</listitem>
+ <listitem>
+ <para>
+ You need <productname>Curl</productname> to build an optional module
+ which implements the <link linkend="libpq-oauth">OAuth Device
+ Authorization flow</link> for client applications.
+ </para>
+ </listitem>
+
<listitem>
<para>
You need <productname>LZ4</productname>, if you want to support
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 8cdd2997d43..695fe958c3e 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -10226,15 +10226,20 @@ void PQinitSSL(int do_ssl);
<title>OAuth Support</title>
<para>
- libpq implements support for the OAuth v2 Device Authorization client flow,
+ <application>libpq</application> implements support for the OAuth v2 Device Authorization client flow,
documented in
<ulink url="https://datatracker.ietf.org/doc/html/rfc8628">RFC 8628</ulink>,
- which it will attempt to use by default if the server
+ as an optional module. See the <link linkend="configure-option-with-libcurl">
+ installation documentation</link> for information on how to enable support
+ for Device Authorization as a builtin flow.
+ </para>
+ <para>
+ When support is enabled and the optional module installed, <application>libpq</application>
+ will use the builtin flow by default if the server
<link linkend="auth-oauth">requests a bearer token</link> during
authentication. This flow can be utilized even if the system running the
client application does not have a usable web browser, for example when
- running a client via <application>SSH</application>. Client applications may implement their own flows
- instead; see <xref linkend="libpq-oauth-authdata-hooks"/>.
+ running a client via <acronym>SSH</acronym>.
</para>
<para>
The builtin flow will, by default, print a URL to visit and a user code to
@@ -10251,6 +10256,11 @@ Visit https://example.com/device and enter the code: ABCD-EFGH
they match expectations, before continuing. Permissions should not be given
to untrusted third parties.
</para>
+ <para>
+ Client applications may implement their own flows to customize interaction
+ and integration with applications. See <xref linkend="libpq-oauth-authdata-hooks"/>
+ for more information on how add a custom flow to <application>libpq</application>.
+ </para>
<para>
For an OAuth client flow to be usable, the connection string must at minimum
contain <xref linkend="libpq-connect-oauth-issuer"/> and
@@ -10366,7 +10376,9 @@ typedef struct _PGpromptOAuthDevice
</synopsis>
</para>
<para>
- The OAuth Device Authorization flow included in <application>libpq</application>
+ The OAuth Device Authorization flow which
+ <link linkend="configure-option-with-libcurl">can be included</link>
+ in <application>libpq</application>
requires the end user to visit a URL with a browser, then enter a code
which permits <application>libpq</application> to connect to the server
on their behalf. The default prompt simply prints the
@@ -10378,7 +10390,8 @@ typedef struct _PGpromptOAuthDevice
This callback is only invoked during the builtin device
authorization flow. If the application installs a
<link linkend="libpq-oauth-authdata-oauth-bearer-token">custom OAuth
- flow</link>, this authdata type will not be used.
+ flow</link>, or <application>libpq</application> was not built with
+ support for the builtin flow, this authdata type will not be used.
</para>
<para>
If a non-NULL <structfield>verification_uri_complete</structfield> is
@@ -10400,8 +10413,9 @@ typedef struct _PGpromptOAuthDevice
</term>
<listitem>
<para>
- Replaces the entire OAuth flow with a custom implementation. The hook
- should either directly return a Bearer token for the current
+ Adds a custom implementation of a flow, replacing the builtin flow if
+ it is <link linkend="configure-option-with-libcurl">installed</link>.
+ The hook should either directly return a Bearer token for the current
user/issuer/scope combination, if one is available without blocking, or
else set up an asynchronous callback to retrieve one.
</para>
diff --git a/meson.build b/meson.build
index a1516e54529..29d46c8ad01 100644
--- a/meson.build
+++ b/meson.build
@@ -107,6 +107,7 @@ os_deps = []
backend_both_deps = []
backend_deps = []
libpq_deps = []
+libpq_oauth_deps = []
pg_sysroot = ''
@@ -860,13 +861,13 @@ endif
###############################################################
libcurlopt = get_option('libcurl')
+oauth_flow_supported = false
+
if not libcurlopt.disabled()
# Check for libcurl 7.61.0 or higher (corresponding to RHEL8 and the ability
# to explicitly set TLS 1.3 ciphersuites).
libcurl = dependency('libcurl', version: '>= 7.61.0', required: libcurlopt)
if libcurl.found()
- cdata.set('USE_LIBCURL', 1)
-
# Check to see whether the current platform supports thread-safe Curl
# initialization.
libcurl_threadsafe_init = false
@@ -938,6 +939,22 @@ if not libcurlopt.disabled()
endif
endif
+ # Check that the current platform supports our builtin flow. This requires
+ # libcurl and one of either epoll or kqueue.
+ oauth_flow_supported = (
+ libcurl.found()
+ and (cc.check_header('sys/event.h', required: false,
+ args: test_c_args, include_directories: postgres_inc)
+ or cc.check_header('sys/epoll.h', required: false,
+ args: test_c_args, include_directories: postgres_inc))
+ )
+
+ if oauth_flow_supported
+ cdata.set('USE_LIBCURL', 1)
+ elif libcurlopt.enabled()
+ error('client-side OAuth is not supported on this platform')
+ endif
+
else
libcurl = not_found_dep
endif
@@ -3272,17 +3289,18 @@ libpq_deps += [
gssapi,
ldap_r,
- # XXX libcurl must link after libgssapi_krb5 on FreeBSD to avoid segfaults
- # during gss_acquire_cred(). This is possibly related to Curl's Heimdal
- # dependency on that platform?
- libcurl,
libintl,
ssl,
]
+libpq_oauth_deps += [
+ libcurl,
+]
+
subdir('src/interfaces/libpq')
-# fe_utils depends on libpq
+# fe_utils and libpq-oauth depends on libpq
subdir('src/fe_utils')
+subdir('src/interfaces/libpq-oauth')
# for frontend binaries
frontend_code = declare_dependency(
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 6722fbdf365..04952b533de 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -347,6 +347,9 @@ perl_embed_ldflags = @perl_embed_ldflags@
AWK = @AWK@
LN_S = @LN_S@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBCURL_LDFLAGS = @LIBCURL_LDFLAGS@
+LIBCURL_LDLIBS = @LIBCURL_LDLIBS@
MSGFMT = @MSGFMT@
MSGFMT_FLAGS = @MSGFMT_FLAGS@
MSGMERGE = @MSGMERGE@
diff --git a/src/interfaces/Makefile b/src/interfaces/Makefile
index 7d56b29d28f..e6822caa206 100644
--- a/src/interfaces/Makefile
+++ b/src/interfaces/Makefile
@@ -14,7 +14,19 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = libpq ecpg
+ifeq ($(with_libcurl), yes)
+SUBDIRS += libpq-oauth
+else
+ALWAYS_SUBDIRS += libpq-oauth
+endif
+
$(recurse)
+$(recurse_always)
all-ecpg-recurse: all-libpq-recurse
install-ecpg-recurse: install-libpq-recurse
+
+ifeq ($(with_libcurl), yes)
+all-libpq-oauth-recurse: all-libpq-recurse
+install-libpq-oauth-recurse: install-libpq-recurse
+endif
diff --git a/src/interfaces/libpq-oauth/Makefile b/src/interfaces/libpq-oauth/Makefile
new file mode 100644
index 00000000000..3e4b34142e0
--- /dev/null
+++ b/src/interfaces/libpq-oauth/Makefile
@@ -0,0 +1,83 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for libpq-oauth
+#
+# Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/interfaces/libpq-oauth/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/interfaces/libpq-oauth
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+PGFILEDESC = "libpq-oauth - device authorization OAuth support"
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
+NAME = pq-oauth-$(MAJORVERSION)
+
+# Force the name "libpq-oauth" for both the static and shared libraries. The
+# staticlib doesn't need version information in its name.
+override shlib := lib$(NAME)$(DLSUFFIX)
+override stlib := libpq-oauth.a
+
+override CPPFLAGS := -I$(libpq_srcdir) -I$(top_builddir)/src/port $(LIBCURL_CPPFLAGS) $(CPPFLAGS)
+
+OBJS = \
+ $(WIN32RES)
+
+OBJS_STATIC = oauth-curl.o
+
+# The shared library needs additional glue symbols.
+OBJS_SHLIB = \
+ oauth-curl_shlib.o \
+ oauth-utils.o \
+
+oauth-utils.o: override CPPFLAGS += -DUSE_DYNAMIC_OAUTH
+oauth-curl_shlib.o: override CPPFLAGS_SHLIB += -DUSE_DYNAMIC_OAUTH
+
+# Add shlib-/stlib-specific objects.
+$(shlib): override OBJS += $(OBJS_SHLIB)
+$(shlib): $(OBJS_SHLIB)
+
+$(stlib): override OBJS += $(OBJS_STATIC)
+$(stlib): $(OBJS_STATIC)
+
+SHLIB_LINK_INTERNAL = $(libpq_pgport_shlib)
+SHLIB_LINK = $(LIBCURL_LDFLAGS) $(LIBCURL_LDLIBS)
+SHLIB_PREREQS = submake-libpq
+SHLIB_EXPORTS = exports.txt
+
+# Disable -bundle_loader on macOS.
+BE_DLLLIBS =
+
+# By default, a library without an SONAME doesn't get a static library, so we
+# add it to the build explicitly.
+all: all-lib all-static-lib
+
+# Shared library stuff
+include $(top_srcdir)/src/Makefile.shlib
+
+# Use src/common/Makefile's trick for tracking dependencies of shlib-specific
+# objects.
+%_shlib.o: %.c %.o
+ $(CC) $(CFLAGS) $(CFLAGS_SL) $(CPPFLAGS) $(CPPFLAGS_SHLIB) -c $< -o $@
+
+# Ignore the standard rules for SONAME-less installation; we want both the
+# static and shared libraries to go into libdir.
+install: all installdirs $(stlib) $(shlib)
+ $(INSTALL_SHLIB) $(shlib) '$(DESTDIR)$(libdir)/$(shlib)'
+ $(INSTALL_STLIB) $(stlib) '$(DESTDIR)$(libdir)/$(stlib)'
+
+installdirs:
+ $(MKDIR_P) '$(DESTDIR)$(libdir)'
+
+uninstall:
+ rm -f '$(DESTDIR)$(libdir)/$(stlib)'
+ rm -f '$(DESTDIR)$(libdir)/$(shlib)'
+
+clean distclean: clean-lib
+ rm -f $(OBJS) $(OBJS_STATIC) $(OBJS_SHLIB)
diff --git a/src/interfaces/libpq-oauth/README b/src/interfaces/libpq-oauth/README
new file mode 100644
index 00000000000..553962d644e
--- /dev/null
+++ b/src/interfaces/libpq-oauth/README
@@ -0,0 +1,57 @@
+libpq-oauth is an optional module implementing the Device Authorization flow for
+OAuth clients (RFC 8628). It is maintained as its own shared library in order to
+isolate its dependency on libcurl. (End users who don't want the Curl dependency
+can simply choose not to install this module.)
+
+If a connection string allows the use of OAuth, and the server asks for it, and
+a libpq client has not installed its own custom OAuth flow, libpq will attempt
+to delay-load this module using dlopen() and the following ABI. Failure to load
+results in a failed connection.
+
+= Load-Time ABI =
+
+This module ABI is an internal implementation detail, so it's subject to change
+across major releases; the name of the module (libpq-oauth-MAJOR) reflects this.
+The module exports the following symbols:
+
+- PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+- void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+pg_fe_run_oauth_flow and pg_fe_cleanup_oauth_flow are implementations of
+conn->async_auth and conn->cleanup_async_auth, respectively.
+
+At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
+libpq_gettext(), which must be injected by libpq using this initialization
+function before the flow is run:
+
+- void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl,
+ conn_oauth_client_id_func clientid_impl,
+ conn_oauth_client_secret_func clientsecret_impl,
+ conn_oauth_discovery_uri_func discoveryuri_impl,
+ conn_oauth_issuer_id_func issuerid_impl,
+ conn_oauth_scope_func scope_impl,
+ conn_sasl_state_func saslstate_impl,
+ set_conn_altsock_func setaltsock_impl,
+ set_conn_oauth_token_func settoken_impl);
+
+It also relies on access to several members of the PGconn struct. Not only can
+these change positions across minor versions, but the offsets aren't necessarily
+stable within a single minor release (conn->errorMessage, for instance, can
+change offsets depending on configure-time options). Therefore the necessary
+accessors (named conn_*) and mutators (set_conn_*) are injected here. With this
+approach, we can safely search the standard dlopen() paths (e.g. RPATH,
+LD_LIBRARY_PATH, the SO cache) for an implementation module to use, even if that
+module wasn't compiled at the same time as libpq -- which becomes especially
+important during "live upgrade" situations where a running libpq application has
+the libpq-oauth module updated out from under it before it's first loaded from
+disk.
+
+= Static Build =
+
+The static library libpq.a does not perform any dynamic loading. If the builtin
+flow is enabled, the application is expected to link against libpq-oauth.a
+directly to provide the necessary symbols. (libpq.a and libpq-oauth.a must be
+part of the same build. Unlike the dynamic module, there are no translation
+shims provided.)
diff --git a/src/interfaces/libpq-oauth/exports.txt b/src/interfaces/libpq-oauth/exports.txt
new file mode 100644
index 00000000000..6891a83dbf9
--- /dev/null
+++ b/src/interfaces/libpq-oauth/exports.txt
@@ -0,0 +1,4 @@
+# src/interfaces/libpq-oauth/exports.txt
+libpq_oauth_init 1
+pg_fe_run_oauth_flow 2
+pg_fe_cleanup_oauth_flow 3
diff --git a/src/interfaces/libpq-oauth/meson.build b/src/interfaces/libpq-oauth/meson.build
new file mode 100644
index 00000000000..9e7301a7f63
--- /dev/null
+++ b/src/interfaces/libpq-oauth/meson.build
@@ -0,0 +1,45 @@
+# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+
+if not oauth_flow_supported
+ subdir_done()
+endif
+
+libpq_oauth_sources = files(
+ 'oauth-curl.c',
+)
+
+# The shared library needs additional glue symbols.
+libpq_oauth_so_sources = files(
+ 'oauth-utils.c',
+)
+libpq_oauth_so_c_args = ['-DUSE_DYNAMIC_OAUTH']
+
+export_file = custom_target('libpq-oauth.exports',
+ kwargs: gen_export_kwargs,
+)
+
+# port needs to be in include path due to pthread-win32.h
+libpq_oauth_inc = include_directories('.', '../libpq', '../../port')
+
+libpq_oauth_st = static_library('libpq-oauth',
+ libpq_oauth_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_stlib_code, libpq_oauth_deps],
+ kwargs: default_lib_args,
+)
+
+# This is an internal module; we don't want an SONAME and therefore do not set
+# SO_MAJOR_VERSION.
+libpq_oauth_name = 'libpq-oauth-@0@'.format(pg_version_major)
+
+libpq_oauth_so = shared_module(libpq_oauth_name,
+ libpq_oauth_sources + libpq_oauth_so_sources,
+ include_directories: [libpq_oauth_inc, postgres_inc],
+ c_args: libpq_so_c_args,
+ c_pch: pch_postgres_fe_h,
+ dependencies: [frontend_shlib_code, libpq, libpq_oauth_deps],
+ link_depends: export_file,
+ link_args: export_fmt.format(export_file.full_path()),
+ kwargs: default_lib_args,
+)
diff --git a/src/interfaces/libpq/fe-auth-oauth-curl.c b/src/interfaces/libpq-oauth/oauth-curl.c
similarity index 94%
rename from src/interfaces/libpq/fe-auth-oauth-curl.c
rename to src/interfaces/libpq-oauth/oauth-curl.c
index c195e00cd28..d13b9cbabb4 100644
--- a/src/interfaces/libpq/fe-auth-oauth-curl.c
+++ b/src/interfaces/libpq-oauth/oauth-curl.c
@@ -1,6 +1,6 @@
/*-------------------------------------------------------------------------
*
- * fe-auth-oauth-curl.c
+ * oauth-curl.c
* The libcurl implementation of OAuth/OIDC authentication, using the
* OAuth Device Authorization Grant (RFC 8628).
*
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * src/interfaces/libpq/fe-auth-oauth-curl.c
+ * src/interfaces/libpq-oauth/oauth-curl.c
*
*-------------------------------------------------------------------------
*/
@@ -17,20 +17,56 @@
#include <curl/curl.h>
#include <math.h>
-#ifdef HAVE_SYS_EPOLL_H
+#include <unistd.h>
+
+#if defined(HAVE_SYS_EPOLL_H)
#include <sys/epoll.h>
#include <sys/timerfd.h>
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
#include <sys/event.h>
+#else
+#error libpq-oauth is not supported on this platform
#endif
-#include <unistd.h>
#include "common/jsonapi.h"
-#include "fe-auth.h"
#include "fe-auth-oauth.h"
-#include "libpq-int.h"
#include "mb/pg_wchar.h"
+#include "oauth-curl.h"
+
+#ifdef USE_DYNAMIC_OAUTH
+
+/*
+ * The module build is decoupled from libpq-int.h, to try to avoid inadvertent
+ * ABI breaks during minor version bumps. Replacements for the missing internals
+ * are provided by oauth-utils.
+ */
+#include "oauth-utils.h"
+
+#else /* !USE_DYNAMIC_OAUTH */
+
+/*
+ * Static builds may rely on PGconn offsets directly. Keep these aligned with
+ * the bank of callbacks in oauth-utils.h.
+ */
+#include "libpq-int.h"
+
+#define conn_errorMessage(CONN) (&CONN->errorMessage)
+#define conn_oauth_client_id(CONN) (CONN->oauth_client_id)
+#define conn_oauth_client_secret(CONN) (CONN->oauth_client_secret)
+#define conn_oauth_discovery_uri(CONN) (CONN->oauth_discovery_uri)
+#define conn_oauth_issuer_id(CONN) (CONN->oauth_issuer_id)
+#define conn_oauth_scope(CONN) (CONN->oauth_scope)
+#define conn_sasl_state(CONN) (CONN->sasl_state)
+
+#define set_conn_altsock(CONN, VAL) do { CONN->altsock = VAL; } while (0)
+#define set_conn_oauth_token(CONN, VAL) do { CONN->oauth_token = VAL; } while (0)
+
+#endif /* USE_DYNAMIC_OAUTH */
+
+/* One final guardrail against accidental inclusion... */
+#if defined(USE_DYNAMIC_OAUTH) && defined(LIBPQ_INT_H)
+#error do not rely on libpq-int.h in dynamic builds of libpq-oauth
+#endif
/*
* It's generally prudent to set a maximum response size to buffer in memory,
@@ -303,7 +339,7 @@ free_async_ctx(PGconn *conn, struct async_ctx *actx)
void
pg_fe_cleanup_oauth_flow(PGconn *conn)
{
- fe_oauth_state *state = conn->sasl_state;
+ fe_oauth_state *state = conn_sasl_state(conn);
if (state->async_ctx)
{
@@ -311,7 +347,7 @@ pg_fe_cleanup_oauth_flow(PGconn *conn)
state->async_ctx = NULL;
}
- conn->altsock = PGINVALID_SOCKET;
+ set_conn_altsock(conn, PGINVALID_SOCKET);
}
/*
@@ -1110,7 +1146,7 @@ parse_access_token(struct async_ctx *actx, struct token *tok)
static bool
setup_multiplexer(struct async_ctx *actx)
{
-#ifdef HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct epoll_event ev = {.events = EPOLLIN};
actx->mux = epoll_create1(EPOLL_CLOEXEC);
@@ -1134,8 +1170,7 @@ setup_multiplexer(struct async_ctx *actx)
}
return true;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
actx->mux = kqueue();
if (actx->mux < 0)
{
@@ -1158,10 +1193,9 @@ setup_multiplexer(struct async_ctx *actx)
}
return true;
+#else
+#error setup_multiplexer is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support the Device Authorization flow on this platform");
- return false;
}
/*
@@ -1174,7 +1208,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
{
struct async_ctx *actx = ctx;
-#ifdef HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct epoll_event ev = {0};
int res;
int op = EPOLL_CTL_ADD;
@@ -1230,8 +1264,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
}
return 0;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
struct kevent ev[2] = {0};
struct kevent ev_out[2];
struct timespec timeout = {0};
@@ -1312,10 +1345,9 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
}
return 0;
+#else
+#error register_socket is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support multiplexer sockets on this platform");
- return -1;
}
/*
@@ -1334,7 +1366,7 @@ register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx,
static bool
set_timer(struct async_ctx *actx, long timeout)
{
-#if HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct itimerspec spec = {0};
if (timeout < 0)
@@ -1363,8 +1395,7 @@ set_timer(struct async_ctx *actx, long timeout)
}
return true;
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
struct kevent ev;
#ifdef __NetBSD__
@@ -1419,10 +1450,9 @@ set_timer(struct async_ctx *actx, long timeout)
}
return true;
+#else
+#error set_timer is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support timers on this platform");
- return false;
}
/*
@@ -1433,7 +1463,7 @@ set_timer(struct async_ctx *actx, long timeout)
static int
timer_expired(struct async_ctx *actx)
{
-#if HAVE_SYS_EPOLL_H
+#if defined(HAVE_SYS_EPOLL_H)
struct itimerspec spec = {0};
if (timerfd_gettime(actx->timerfd, &spec) < 0)
@@ -1453,8 +1483,7 @@ timer_expired(struct async_ctx *actx)
/* If the remaining time to expiration is zero, we're done. */
return (spec.it_value.tv_sec == 0
&& spec.it_value.tv_nsec == 0);
-#endif
-#ifdef HAVE_SYS_EVENT_H
+#elif defined(HAVE_SYS_EVENT_H)
int res;
/* Is the timer queue ready? */
@@ -1466,10 +1495,9 @@ timer_expired(struct async_ctx *actx)
}
return (res > 0);
+#else
+#error timer_expired is not implemented on this platform
#endif
-
- actx_error(actx, "libpq does not support timers on this platform");
- return -1;
}
/*
@@ -2070,8 +2098,9 @@ static bool
check_issuer(struct async_ctx *actx, PGconn *conn)
{
const struct provider *provider = &actx->provider;
+ const char *oauth_issuer_id = conn_oauth_issuer_id(conn);
- Assert(conn->oauth_issuer_id); /* ensured by setup_oauth_parameters() */
+ Assert(oauth_issuer_id); /* ensured by setup_oauth_parameters() */
Assert(provider->issuer); /* ensured by parse_provider() */
/*---
@@ -2091,11 +2120,11 @@ check_issuer(struct async_ctx *actx, PGconn *conn)
* sent to. This comparison MUST use simple string comparison as defined
* in Section 6.2.1 of [RFC3986].
*/
- if (strcmp(conn->oauth_issuer_id, provider->issuer) != 0)
+ if (strcmp(oauth_issuer_id, provider->issuer) != 0)
{
actx_error(actx,
"the issuer identifier (%s) does not match oauth_issuer (%s)",
- provider->issuer, conn->oauth_issuer_id);
+ provider->issuer, oauth_issuer_id);
return false;
}
@@ -2172,11 +2201,14 @@ check_for_device_flow(struct async_ctx *actx)
static bool
add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *conn)
{
+ const char *oauth_client_id = conn_oauth_client_id(conn);
+ const char *oauth_client_secret = conn_oauth_client_secret(conn);
+
bool success = false;
char *username = NULL;
char *password = NULL;
- if (conn->oauth_client_secret) /* Zero-length secrets are permitted! */
+ if (oauth_client_secret) /* Zero-length secrets are permitted! */
{
/*----
* Use HTTP Basic auth to send the client_id and secret. Per RFC 6749,
@@ -2204,8 +2236,8 @@ add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *c
* would it be redundant, but some providers in the wild (e.g. Okta)
* refuse to accept it.
*/
- username = urlencode(conn->oauth_client_id);
- password = urlencode(conn->oauth_client_secret);
+ username = urlencode(oauth_client_id);
+ password = urlencode(oauth_client_secret);
if (!username || !password)
{
@@ -2225,7 +2257,7 @@ add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *c
* If we're not otherwise authenticating, client_id is REQUIRED in the
* request body.
*/
- build_urlencoded(reqbody, "client_id", conn->oauth_client_id);
+ build_urlencoded(reqbody, "client_id", oauth_client_id);
CHECK_SETOPT(actx, CURLOPT_HTTPAUTH, CURLAUTH_NONE, goto cleanup);
actx->used_basic_auth = false;
@@ -2253,16 +2285,17 @@ cleanup:
static bool
start_device_authz(struct async_ctx *actx, PGconn *conn)
{
+ const char *oauth_scope = conn_oauth_scope(conn);
const char *device_authz_uri = actx->provider.device_authorization_endpoint;
PQExpBuffer work_buffer = &actx->work_data;
- Assert(conn->oauth_client_id); /* ensured by setup_oauth_parameters() */
+ Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
Assert(device_authz_uri); /* ensured by check_for_device_flow() */
/* Construct our request body. */
resetPQExpBuffer(work_buffer);
- if (conn->oauth_scope && conn->oauth_scope[0])
- build_urlencoded(work_buffer, "scope", conn->oauth_scope);
+ if (oauth_scope && oauth_scope[0])
+ build_urlencoded(work_buffer, "scope", oauth_scope);
if (!add_client_identification(actx, work_buffer, conn))
return false;
@@ -2344,7 +2377,7 @@ start_token_request(struct async_ctx *actx, PGconn *conn)
const char *device_code = actx->authz.device_code;
PQExpBuffer work_buffer = &actx->work_data;
- Assert(conn->oauth_client_id); /* ensured by setup_oauth_parameters() */
+ Assert(conn_oauth_client_id(conn)); /* ensured by setup_oauth_parameters() */
Assert(token_uri); /* ensured by parse_provider() */
Assert(device_code); /* ensured by parse_device_authz() */
@@ -2487,8 +2520,9 @@ prompt_user(struct async_ctx *actx, PGconn *conn)
.verification_uri_complete = actx->authz.verification_uri_complete,
.expires_in = actx->authz.expires_in,
};
+ PQauthDataHook_type hook = PQgetAuthDataHook();
- res = PQauthDataHook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
+ res = hook(PQAUTHDATA_PROMPT_OAUTH_DEVICE, conn, &prompt);
if (!res)
{
@@ -2633,8 +2667,10 @@ done:
static PostgresPollingStatusType
pg_fe_run_oauth_flow_impl(PGconn *conn)
{
- fe_oauth_state *state = conn->sasl_state;
+ fe_oauth_state *state = conn_sasl_state(conn);
struct async_ctx *actx;
+ char *oauth_token = NULL;
+ PQExpBuffer errbuf;
if (!initialize_curl(conn))
return PGRES_POLLING_FAILED;
@@ -2676,7 +2712,7 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
do
{
/* By default, the multiplexer is the altsock. Reassign as desired. */
- conn->altsock = actx->mux;
+ set_conn_altsock(conn, actx->mux);
switch (actx->step)
{
@@ -2712,7 +2748,7 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
*/
if (!timer_expired(actx))
{
- conn->altsock = actx->timerfd;
+ set_conn_altsock(conn, actx->timerfd);
return PGRES_POLLING_READING;
}
@@ -2732,7 +2768,7 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
{
case OAUTH_STEP_INIT:
actx->errctx = "failed to fetch OpenID discovery document";
- if (!start_discovery(actx, conn->oauth_discovery_uri))
+ if (!start_discovery(actx, conn_oauth_discovery_uri(conn)))
goto error_return;
actx->step = OAUTH_STEP_DISCOVERY;
@@ -2768,9 +2804,15 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
break;
case OAUTH_STEP_TOKEN_REQUEST:
- if (!handle_token_response(actx, &conn->oauth_token))
+ if (!handle_token_response(actx, &oauth_token))
goto error_return;
+ /*
+ * Hook any oauth_token into the PGconn immediately so that
+ * the allocation isn't lost in case of an error.
+ */
+ set_conn_oauth_token(conn, oauth_token);
+
if (!actx->user_prompted)
{
/*
@@ -2783,7 +2825,7 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
actx->user_prompted = true;
}
- if (conn->oauth_token)
+ if (oauth_token)
break; /* done! */
/*
@@ -2798,7 +2840,7 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
* the client wait directly on the timerfd rather than the
* multiplexer.
*/
- conn->altsock = actx->timerfd;
+ set_conn_altsock(conn, actx->timerfd);
actx->step = OAUTH_STEP_WAIT_INTERVAL;
actx->running = 1;
@@ -2818,48 +2860,40 @@ pg_fe_run_oauth_flow_impl(PGconn *conn)
* point, actx->running will be set. But there are some corner cases
* where we can immediately loop back around; see start_request().
*/
- } while (!conn->oauth_token && !actx->running);
+ } while (!oauth_token && !actx->running);
/* If we've stored a token, we're done. Otherwise come back later. */
- return conn->oauth_token ? PGRES_POLLING_OK : PGRES_POLLING_READING;
+ return oauth_token ? PGRES_POLLING_OK : PGRES_POLLING_READING;
error_return:
+ errbuf = conn_errorMessage(conn);
/*
* Assemble the three parts of our error: context, body, and detail. See
* also the documentation for struct async_ctx.
*/
if (actx->errctx)
- {
- appendPQExpBufferStr(&conn->errorMessage,
- libpq_gettext(actx->errctx));
- appendPQExpBufferStr(&conn->errorMessage, ": ");
- }
+ appendPQExpBuffer(errbuf, "%s: ", libpq_gettext(actx->errctx));
if (PQExpBufferDataBroken(actx->errbuf))
- appendPQExpBufferStr(&conn->errorMessage,
- libpq_gettext("out of memory"));
+ appendPQExpBufferStr(errbuf, libpq_gettext("out of memory"));
else
- appendPQExpBufferStr(&conn->errorMessage, actx->errbuf.data);
+ appendPQExpBufferStr(errbuf, actx->errbuf.data);
if (actx->curl_err[0])
{
- size_t len;
-
- appendPQExpBuffer(&conn->errorMessage,
- " (libcurl: %s)", actx->curl_err);
+ appendPQExpBuffer(errbuf, " (libcurl: %s)", actx->curl_err);
/* Sometimes libcurl adds a newline to the error buffer. :( */
- len = conn->errorMessage.len;
- if (len >= 2 && conn->errorMessage.data[len - 2] == '\n')
+ if (errbuf->len >= 2 && errbuf->data[errbuf->len - 2] == '\n')
{
- conn->errorMessage.data[len - 2] = ')';
- conn->errorMessage.data[len - 1] = '\0';
- conn->errorMessage.len--;
+ errbuf->data[errbuf->len - 2] = ')';
+ errbuf->data[errbuf->len - 1] = '\0';
+ errbuf->len--;
}
}
- appendPQExpBufferChar(&conn->errorMessage, '\n');
+ appendPQExpBufferChar(errbuf, '\n');
return PGRES_POLLING_FAILED;
}
diff --git a/src/interfaces/libpq-oauth/oauth-curl.h b/src/interfaces/libpq-oauth/oauth-curl.h
new file mode 100644
index 00000000000..248d0424ad0
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-curl.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-curl.h
+ *
+ * Definitions for OAuth Device Authorization module
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-curl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_CURL_H
+#define OAUTH_CURL_H
+
+#include "libpq-fe.h"
+
+/* Exported async-auth callbacks. */
+extern PGDLLEXPORT PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+extern PGDLLEXPORT void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+#endif /* OAUTH_CURL_H */
diff --git a/src/interfaces/libpq-oauth/oauth-utils.c b/src/interfaces/libpq-oauth/oauth-utils.c
new file mode 100644
index 00000000000..45fdc7579f2
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.c
@@ -0,0 +1,233 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.c
+ *
+ * "Glue" helpers providing a copy of some internal APIs from libpq. At
+ * some point in the future, we might be able to deduplicate.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq-oauth/oauth-utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+
+#include "oauth-utils.h"
+
+#ifndef USE_DYNAMIC_OAUTH
+#error oauth-utils.c is not supported in static builds
+#endif
+
+#ifdef LIBPQ_INT_H
+#error do not rely on libpq-int.h in dynamic builds of libpq-oauth
+#endif
+
+/*
+ * Function pointers set by libpq_oauth_init().
+ */
+
+pgthreadlock_t pg_g_threadlock;
+static libpq_gettext_func libpq_gettext_impl;
+
+conn_errorMessage_func conn_errorMessage;
+conn_oauth_client_id_func conn_oauth_client_id;
+conn_oauth_client_secret_func conn_oauth_client_secret;
+conn_oauth_discovery_uri_func conn_oauth_discovery_uri;
+conn_oauth_issuer_id_func conn_oauth_issuer_id;
+conn_oauth_scope_func conn_oauth_scope;
+conn_sasl_state_func conn_sasl_state;
+
+set_conn_altsock_func set_conn_altsock;
+set_conn_oauth_token_func set_conn_oauth_token;
+
+/*-
+ * Initializes libpq-oauth by setting necessary callbacks.
+ *
+ * The current implementation relies on the following private implementation
+ * details of libpq:
+ *
+ * - pg_g_threadlock: protects libcurl initialization if the underlying Curl
+ * installation is not threadsafe
+ *
+ * - libpq_gettext: translates error messages using libpq's message domain
+ *
+ * The implementation also needs access to several members of the PGconn struct,
+ * which are not guaranteed to stay in place across minor versions. Accessors
+ * (named conn_*) and mutators (named set_conn_*) are injected here.
+ */
+void
+libpq_oauth_init(pgthreadlock_t threadlock_impl,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl,
+ conn_oauth_client_id_func clientid_impl,
+ conn_oauth_client_secret_func clientsecret_impl,
+ conn_oauth_discovery_uri_func discoveryuri_impl,
+ conn_oauth_issuer_id_func issuerid_impl,
+ conn_oauth_scope_func scope_impl,
+ conn_sasl_state_func saslstate_impl,
+ set_conn_altsock_func setaltsock_impl,
+ set_conn_oauth_token_func settoken_impl)
+{
+ pg_g_threadlock = threadlock_impl;
+ libpq_gettext_impl = gettext_impl;
+ conn_errorMessage = errmsg_impl;
+ conn_oauth_client_id = clientid_impl;
+ conn_oauth_client_secret = clientsecret_impl;
+ conn_oauth_discovery_uri = discoveryuri_impl;
+ conn_oauth_issuer_id = issuerid_impl;
+ conn_oauth_scope = scope_impl;
+ conn_sasl_state = saslstate_impl;
+ set_conn_altsock = setaltsock_impl;
+ set_conn_oauth_token = settoken_impl;
+}
+
+/*
+ * Append a formatted string to the error message buffer of the given
+ * connection, after translating it. This is a copy of libpq's internal API.
+ */
+void
+libpq_append_conn_error(PGconn *conn, const char *fmt,...)
+{
+ int save_errno = errno;
+ bool done;
+ va_list args;
+ PQExpBuffer errorMessage = conn_errorMessage(conn);
+
+ Assert(fmt[strlen(fmt) - 1] != '\n');
+
+ if (PQExpBufferBroken(errorMessage))
+ return; /* already failed */
+
+ /* Loop in case we have to retry after enlarging the buffer. */
+ do
+ {
+ errno = save_errno;
+ va_start(args, fmt);
+ done = appendPQExpBufferVA(errorMessage, libpq_gettext(fmt), args);
+ va_end(args);
+ } while (!done);
+
+ appendPQExpBufferChar(errorMessage, '\n');
+}
+
+#ifdef ENABLE_NLS
+
+/*
+ * A shim that defers to the actual libpq_gettext().
+ */
+char *
+libpq_gettext(const char *msgid)
+{
+ if (!libpq_gettext_impl)
+ {
+ /*
+ * Possible if the libpq build didn't enable NLS but the libpq-oauth
+ * build did. That's an odd mismatch, but we can handle it.
+ *
+ * Note that callers of libpq_gettext() have to treat the return value
+ * as if it were const, because builds without NLS simply pass through
+ * their argument.
+ */
+ return unconstify(char *, msgid);
+ }
+
+ return libpq_gettext_impl(msgid);
+}
+
+#endif /* ENABLE_NLS */
+
+/*
+ * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
+ */
+bool
+oauth_unsafe_debugging_enabled(void)
+{
+ const char *env = getenv("PGOAUTHDEBUG");
+
+ return (env && strcmp(env, "UNSAFE") == 0);
+}
+
+/*
+ * Duplicate SOCK_ERRNO* definitions from libpq-int.h, for use by
+ * pq_block/reset_sigpipe().
+ */
+#ifdef WIN32
+#define SOCK_ERRNO (WSAGetLastError())
+#define SOCK_ERRNO_SET(e) WSASetLastError(e)
+#else
+#define SOCK_ERRNO errno
+#define SOCK_ERRNO_SET(e) (errno = (e))
+#endif
+
+/*
+ * Block SIGPIPE for this thread. This is a copy of libpq's internal API.
+ */
+int
+pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
+{
+ sigset_t sigpipe_sigset;
+ sigset_t sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ /* Block SIGPIPE and save previous mask for later reset */
+ SOCK_ERRNO_SET(pthread_sigmask(SIG_BLOCK, &sigpipe_sigset, osigset));
+ if (SOCK_ERRNO)
+ return -1;
+
+ /* We can have a pending SIGPIPE only if it was blocked before */
+ if (sigismember(osigset, SIGPIPE))
+ {
+ /* Is there a pending SIGPIPE? */
+ if (sigpending(&sigset) != 0)
+ return -1;
+
+ if (sigismember(&sigset, SIGPIPE))
+ *sigpipe_pending = true;
+ else
+ *sigpipe_pending = false;
+ }
+ else
+ *sigpipe_pending = false;
+
+ return 0;
+}
+
+/*
+ * Discard any pending SIGPIPE and reset the signal mask. This is a copy of
+ * libpq's internal API.
+ */
+void
+pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
+{
+ int save_errno = SOCK_ERRNO;
+ int signo;
+ sigset_t sigset;
+
+ /* Clear SIGPIPE only if none was pending */
+ if (got_epipe && !sigpipe_pending)
+ {
+ if (sigpending(&sigset) == 0 &&
+ sigismember(&sigset, SIGPIPE))
+ {
+ sigset_t sigpipe_sigset;
+
+ sigemptyset(&sigpipe_sigset);
+ sigaddset(&sigpipe_sigset, SIGPIPE);
+
+ sigwait(&sigpipe_sigset, &signo);
+ }
+ }
+
+ /* Restore saved block mask */
+ pthread_sigmask(SIG_SETMASK, osigset, NULL);
+
+ SOCK_ERRNO_SET(save_errno);
+}
diff --git a/src/interfaces/libpq-oauth/oauth-utils.h b/src/interfaces/libpq-oauth/oauth-utils.h
new file mode 100644
index 00000000000..f4ffefef208
--- /dev/null
+++ b/src/interfaces/libpq-oauth/oauth-utils.h
@@ -0,0 +1,94 @@
+/*-------------------------------------------------------------------------
+ *
+ * oauth-utils.h
+ *
+ * Definitions providing missing libpq internal APIs
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq-oauth/oauth-utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef OAUTH_UTILS_H
+#define OAUTH_UTILS_H
+
+#include "fe-auth-oauth.h"
+#include "libpq-fe.h"
+#include "pqexpbuffer.h"
+
+/*
+ * A bank of callbacks to safely access members of PGconn, which are all passed
+ * to libpq_oauth_init() by libpq.
+ *
+ * Keep these aligned with the definitions in fe-auth-oauth.c as well as the
+ * static declarations in oauth-curl.c.
+ */
+#define DECLARE_GETTER(TYPE, MEMBER) \
+ typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
+ extern conn_ ## MEMBER ## _func conn_ ## MEMBER;
+
+#define DECLARE_SETTER(TYPE, MEMBER) \
+ typedef void (*set_conn_ ## MEMBER ## _func) (PGconn *conn, TYPE val); \
+ extern set_conn_ ## MEMBER ## _func set_conn_ ## MEMBER;
+
+DECLARE_GETTER(PQExpBuffer, errorMessage);
+DECLARE_GETTER(char *, oauth_client_id);
+DECLARE_GETTER(char *, oauth_client_secret);
+DECLARE_GETTER(char *, oauth_discovery_uri);
+DECLARE_GETTER(char *, oauth_issuer_id);
+DECLARE_GETTER(char *, oauth_scope);
+DECLARE_GETTER(fe_oauth_state *, sasl_state);
+
+DECLARE_SETTER(pgsocket, altsock);
+DECLARE_SETTER(char *, oauth_token);
+
+#undef DECLARE_GETTER
+#undef DECLARE_SETTER
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
+
+/* Initializes libpq-oauth. */
+extern PGDLLEXPORT void libpq_oauth_init(pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl,
+ conn_oauth_client_id_func clientid_impl,
+ conn_oauth_client_secret_func clientsecret_impl,
+ conn_oauth_discovery_uri_func discoveryuri_impl,
+ conn_oauth_issuer_id_func issuerid_impl,
+ conn_oauth_scope_func scope_impl,
+ conn_sasl_state_func saslstate_impl,
+ set_conn_altsock_func setaltsock_impl,
+ set_conn_oauth_token_func settoken_impl);
+
+/*
+ * Duplicated APIs, copied from libpq (primarily libpq-int.h, which we cannot
+ * depend on here).
+ */
+
+typedef enum
+{
+ PG_BOOL_UNKNOWN = 0, /* Currently unknown */
+ PG_BOOL_YES, /* Yes (true) */
+ PG_BOOL_NO /* No (false) */
+} PGTernaryBool;
+
+extern void libpq_append_conn_error(PGconn *conn, const char *fmt,...) pg_attribute_printf(2, 3);
+extern bool oauth_unsafe_debugging_enabled(void);
+extern int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
+extern void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe);
+
+#ifdef ENABLE_NLS
+extern char *libpq_gettext(const char *msgid) pg_attribute_format_arg(1);
+#else
+#define libpq_gettext(x) (x)
+#endif
+
+extern pgthreadlock_t pg_g_threadlock;
+
+#define pglock_thread() pg_g_threadlock(true)
+#define pgunlock_thread() pg_g_threadlock(false)
+
+#endif /* OAUTH_UTILS_H */
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 90b0b65db6f..c6fe5fec7f6 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -31,7 +31,6 @@ endif
OBJS = \
$(WIN32RES) \
- fe-auth-oauth.o \
fe-auth-scram.o \
fe-cancel.o \
fe-connect.o \
@@ -64,9 +63,11 @@ OBJS += \
fe-secure-gssapi.o
endif
-ifeq ($(with_libcurl),yes)
-OBJS += fe-auth-oauth-curl.o
-endif
+# The OAuth implementation differs depending on the type of library being built.
+OBJS_STATIC = fe-auth-oauth.o
+
+fe-auth-oauth_shlib.o: override CPPFLAGS_SHLIB += -DUSE_DYNAMIC_OAUTH
+OBJS_SHLIB = fe-auth-oauth_shlib.o
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
@@ -86,7 +87,7 @@ endif
# that are built correctly for use in a shlib.
SHLIB_LINK_INTERNAL = -lpgcommon_shlib -lpgport_shlib
ifneq ($(PORTNAME), win32)
-SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lcurl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
+SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
else
SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi32 -lssl -lsocket -lnsl -lresolv -lintl -lm $(PTHREAD_LIBS), $(LIBS)) $(LDAP_LIBS_FE)
endif
@@ -101,12 +102,26 @@ ifeq ($(with_ssl),openssl)
PKG_CONFIG_REQUIRES_PRIVATE = libssl, libcrypto
endif
+ifeq ($(with_libcurl),yes)
+# libpq.so doesn't link against libcurl, but libpq.a needs libpq-oauth, and
+# libpq-oauth needs libcurl. Put both into *.private.
+PKG_CONFIG_REQUIRES_PRIVATE += libcurl
+%.pc: override SHLIB_LINK_INTERNAL += -lpq-oauth
+endif
+
all: all-lib libpq-refs-stamp
# Shared library stuff
include $(top_srcdir)/src/Makefile.shlib
backend_src = $(top_srcdir)/src/backend
+# Add shlib-/stlib-specific objects.
+$(shlib): override OBJS += $(OBJS_SHLIB)
+$(shlib): $(OBJS_SHLIB)
+
+$(stlib): override OBJS += $(OBJS_STATIC)
+$(stlib): $(OBJS_STATIC)
+
# Check for functions that libpq must not call, currently just exit().
# (Ideally we'd reject abort() too, but there are various scenarios where
# build toolchains insert abort() calls, e.g. to implement assert().)
@@ -115,8 +130,6 @@ backend_src = $(top_srcdir)/src/backend
# which seems to insert references to that even in pure C code. Excluding
# __tsan_func_exit is necessary when using ThreadSanitizer data race detector
# which use this function for instrumentation of function exit.
-# libcurl registers an exit handler in the memory debugging code when running
-# with LeakSanitizer.
# Skip the test when profiling, as gcc may insert exit() calls for that.
# Also skip the test on platforms where libpq infrastructure may be provided
# by statically-linked libraries, as we can't expect them to honor this
@@ -124,7 +137,7 @@ backend_src = $(top_srcdir)/src/backend
libpq-refs-stamp: $(shlib)
ifneq ($(enable_coverage), yes)
ifeq (,$(filter solaris,$(PORTNAME)))
- @if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit -e _atexit | grep exit; then \
+ @if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit | grep exit; then \
echo 'libpq must not be calling any function which invokes exit'; exit 1; \
fi
endif
@@ -138,6 +151,11 @@ fe-misc.o: fe-misc.c $(top_builddir)/src/port/pg_config_paths.h
$(top_builddir)/src/port/pg_config_paths.h:
$(MAKE) -C $(top_builddir)/src/port pg_config_paths.h
+# Use src/common/Makefile's trick for tracking dependencies of shlib-specific
+# objects.
+%_shlib.o: %.c %.o
+ $(CC) $(CFLAGS) $(CFLAGS_SL) $(CPPFLAGS) $(CPPFLAGS_SHLIB) -c $< -o $@
+
install: all installdirs install-lib
$(INSTALL_DATA) $(srcdir)/libpq-fe.h '$(DESTDIR)$(includedir)'
$(INSTALL_DATA) $(srcdir)/libpq-events.h '$(DESTDIR)$(includedir)'
@@ -171,6 +189,6 @@ uninstall: uninstall-lib
clean distclean: clean-lib
$(MAKE) -C test $@
rm -rf tmp_check
- rm -f $(OBJS) pthread.h libpq-refs-stamp
+ rm -f $(OBJS) $(OBJS_SHLIB) $(OBJS_STATIC) pthread.h libpq-refs-stamp
# Might be left over from a Win32 client-only build
rm -f pg_config_paths.h
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index d5143766858..0625cf39e9a 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -210,3 +210,4 @@ PQsetAuthDataHook 207
PQgetAuthDataHook 208
PQdefaultAuthDataHook 209
PQfullProtocolVersion 210
+appendPQExpBufferVA 211
diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c
index ab6a45e2aba..9fbff89a21d 100644
--- a/src/interfaces/libpq/fe-auth-oauth.c
+++ b/src/interfaces/libpq/fe-auth-oauth.c
@@ -15,6 +15,10 @@
#include "postgres_fe.h"
+#ifdef USE_DYNAMIC_OAUTH
+#include <dlfcn.h>
+#endif
+
#include "common/base64.h"
#include "common/hmac.h"
#include "common/jsonapi.h"
@@ -22,6 +26,7 @@
#include "fe-auth.h"
#include "fe-auth-oauth.h"
#include "mb/pg_wchar.h"
+#include "pg_config_paths.h"
/* The exported OAuth callback mechanism. */
static void *oauth_init(PGconn *conn, const char *password,
@@ -721,6 +726,218 @@ cleanup_user_oauth_flow(PGconn *conn)
state->async_ctx = NULL;
}
+/*-------------
+ * Builtin Flow
+ *
+ * There are three potential implementations of use_builtin_flow:
+ *
+ * 1) If the OAuth client is disabled at configuration time, return false.
+ * Dependent clients must provide their own flow.
+ * 2) If the OAuth client is enabled and USE_DYNAMIC_OAUTH is defined, dlopen()
+ * the libpq-oauth plugin and use its implementation.
+ * 3) Otherwise, use flow callbacks that are statically linked into the
+ * executable.
+ */
+
+#if !defined(USE_LIBCURL)
+
+/*
+ * This configuration doesn't support the builtin flow.
+ */
+
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ return false;
+}
+
+#elif defined(USE_DYNAMIC_OAUTH)
+
+/*
+ * Use the builtin flow in the libpq-oauth plugin, which is loaded at runtime.
+ */
+
+typedef char *(*libpq_gettext_func) (const char *msgid);
+
+/*
+ * Define accessor/mutator shims to inject into libpq-oauth, so that it doesn't
+ * depend on the offsets within PGconn. (These have changed during minor version
+ * updates in the past.)
+ */
+
+#define DEFINE_GETTER(TYPE, MEMBER) \
+ typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
+ static TYPE conn_ ## MEMBER(PGconn *conn) { return conn->MEMBER; }
+
+/* Like DEFINE_GETTER, but returns a pointer to the member. */
+#define DEFINE_GETTER_P(TYPE, MEMBER) \
+ typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
+ static TYPE conn_ ## MEMBER(PGconn *conn) { return &conn->MEMBER; }
+
+#define DEFINE_SETTER(TYPE, MEMBER) \
+ typedef void (*set_conn_ ## MEMBER ## _func) (PGconn *conn, TYPE val); \
+ static void set_conn_ ## MEMBER(PGconn *conn, TYPE val) { conn->MEMBER = val; }
+
+DEFINE_GETTER_P(PQExpBuffer, errorMessage);
+DEFINE_GETTER(char *, oauth_client_id);
+DEFINE_GETTER(char *, oauth_client_secret);
+DEFINE_GETTER(char *, oauth_discovery_uri);
+DEFINE_GETTER(char *, oauth_issuer_id);
+DEFINE_GETTER(char *, oauth_scope);
+DEFINE_GETTER(fe_oauth_state *, sasl_state);
+
+DEFINE_SETTER(pgsocket, altsock);
+DEFINE_SETTER(char *, oauth_token);
+
+/*
+ * Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
+ * callbacks into the connection's async auth handlers.
+ *
+ * Failure to load here results in a relatively quiet connection error, to
+ * handle the use case where the build supports loading a flow but a user does
+ * not want to install it. Troubleshooting of linker/loader failures can be done
+ * via PGOAUTHDEBUG.
+ */
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ static bool initialized = false;
+ static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
+ int lockerr;
+
+ void (*init) (pgthreadlock_t threadlock,
+ libpq_gettext_func gettext_impl,
+ conn_errorMessage_func errmsg_impl,
+ conn_oauth_client_id_func clientid_impl,
+ conn_oauth_client_secret_func clientsecret_impl,
+ conn_oauth_discovery_uri_func discoveryuri_impl,
+ conn_oauth_issuer_id_func issuerid_impl,
+ conn_oauth_scope_func scope_impl,
+ conn_sasl_state_func saslstate_impl,
+ set_conn_altsock_func setaltsock_impl,
+ set_conn_oauth_token_func settoken_impl);
+ PostgresPollingStatusType (*flow) (PGconn *conn);
+ void (*cleanup) (PGconn *conn);
+
+ /*
+ * On macOS only, load the module using its absolute install path; the
+ * standard search behavior is not very helpful for this use case. Unlike
+ * on other platforms, DYLD_LIBRARY_PATH is used as a fallback even with
+ * absolute paths (modulo SIP effects), so tests can continue to work.
+ *
+ * On the other platforms, load the module using only the basename, to
+ * rely on the runtime linker's standard search behavior.
+ */
+ const char *const module_name =
+#if defined(__darwin__)
+ LIBDIR "/libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
+#else
+ "libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
+#endif
+
+ state->builtin_flow = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
+ if (!state->builtin_flow)
+ {
+ /*
+ * For end users, this probably isn't an error condition, it just
+ * means the flow isn't installed. Developers and package maintainers
+ * may want to debug this via the PGOAUTHDEBUG envvar, though.
+ *
+ * Note that POSIX dlerror() isn't guaranteed to be threadsafe.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
+
+ return false;
+ }
+
+ if ((init = dlsym(state->builtin_flow, "libpq_oauth_init")) == NULL
+ || (flow = dlsym(state->builtin_flow, "pg_fe_run_oauth_flow")) == NULL
+ || (cleanup = dlsym(state->builtin_flow, "pg_fe_cleanup_oauth_flow")) == NULL)
+ {
+ /*
+ * This is more of an error condition than the one above, but due to
+ * the dlerror() threadsafety issue, lock it behind PGOAUTHDEBUG too.
+ */
+ if (oauth_unsafe_debugging_enabled())
+ fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
+
+ dlclose(state->builtin_flow);
+ return false;
+ }
+
+ /*
+ * Past this point, we do not unload the module. It stays in the process
+ * permanently.
+ */
+
+ /*
+ * We need to inject necessary function pointers into the module. This
+ * only needs to be done once -- even if the pointers are constant,
+ * assigning them while another thread is executing the flows feels like
+ * tempting fate.
+ */
+ if ((lockerr = pthread_mutex_lock(&init_mutex)) != 0)
+ {
+ /* Should not happen... but don't continue if it does. */
+ Assert(false);
+
+ libpq_append_conn_error(conn, "failed to lock mutex (%d)", lockerr);
+ return false;
+ }
+
+ if (!initialized)
+ {
+ init(pg_g_threadlock,
+#ifdef ENABLE_NLS
+ libpq_gettext,
+#else
+ NULL,
+#endif
+ conn_errorMessage,
+ conn_oauth_client_id,
+ conn_oauth_client_secret,
+ conn_oauth_discovery_uri,
+ conn_oauth_issuer_id,
+ conn_oauth_scope,
+ conn_sasl_state,
+ set_conn_altsock,
+ set_conn_oauth_token);
+
+ initialized = true;
+ }
+
+ pthread_mutex_unlock(&init_mutex);
+
+ /* Set our asynchronous callbacks. */
+ conn->async_auth = flow;
+ conn->cleanup_async_auth = cleanup;
+
+ return true;
+}
+
+#else
+
+/*
+ * Use the builtin flow in libpq-oauth.a (see libpq-oauth/oauth-curl.h).
+ */
+
+extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
+extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
+
+bool
+use_builtin_flow(PGconn *conn, fe_oauth_state *state)
+{
+ /* Set our asynchronous callbacks. */
+ conn->async_auth = pg_fe_run_oauth_flow;
+ conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
+
+ return true;
+}
+
+#endif /* USE_LIBCURL */
+
+
/*
* Chooses an OAuth client flow for the connection, which will retrieve a Bearer
* token for presentation to the server.
@@ -792,18 +1009,10 @@ setup_token_request(PGconn *conn, fe_oauth_state *state)
libpq_append_conn_error(conn, "user-defined OAuth flow failed");
goto fail;
}
- else
+ else if (!use_builtin_flow(conn, state))
{
-#if USE_LIBCURL
- /* Hand off to our built-in OAuth flow. */
- conn->async_auth = pg_fe_run_oauth_flow;
- conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
-
-#else
- libpq_append_conn_error(conn, "no custom OAuth flows are available, and libpq was not built with libcurl support");
+ libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
goto fail;
-
-#endif
}
return true;
diff --git a/src/interfaces/libpq/fe-auth-oauth.h b/src/interfaces/libpq/fe-auth-oauth.h
index 3f1a7503a01..0d59e91605b 100644
--- a/src/interfaces/libpq/fe-auth-oauth.h
+++ b/src/interfaces/libpq/fe-auth-oauth.h
@@ -15,8 +15,8 @@
#ifndef FE_AUTH_OAUTH_H
#define FE_AUTH_OAUTH_H
+#include "fe-auth-sasl.h"
#include "libpq-fe.h"
-#include "libpq-int.h"
enum fe_oauth_step
@@ -27,18 +27,24 @@ enum fe_oauth_step
FE_OAUTH_SERVER_ERROR,
};
+/*
+ * This struct is exported to the libpq-oauth module. If changes are needed
+ * during backports to stable branches, please keep ABI compatibility (no
+ * changes to existing members, add new members at the end, etc.).
+ */
typedef struct
{
enum fe_oauth_step step;
PGconn *conn;
void *async_ctx;
+
+ void *builtin_flow;
} fe_oauth_state;
-extern PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
-extern void pg_fe_cleanup_oauth_flow(PGconn *conn);
extern void pqClearOAuthToken(PGconn *conn);
extern bool oauth_unsafe_debugging_enabled(void);
+extern bool use_builtin_flow(PGconn *conn, fe_oauth_state *state);
/* Mechanisms in fe-auth-oauth.c */
extern const pg_fe_sasl_mech pg_oauth_mech;
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index 292fecf3320..a74e885b169 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -38,10 +38,6 @@ if gssapi.found()
)
endif
-if libcurl.found()
- libpq_sources += files('fe-auth-oauth-curl.c')
-endif
-
export_file = custom_target('libpq.exports',
kwargs: gen_export_kwargs,
)
@@ -50,6 +46,9 @@ export_file = custom_target('libpq.exports',
libpq_inc = include_directories('.', '../../port')
libpq_c_args = ['-DSO_MAJOR_VERSION=5']
+# The OAuth implementation differs depending on the type of library being built.
+libpq_so_c_args = ['-DUSE_DYNAMIC_OAUTH']
+
# Not using both_libraries() here as
# 1) resource files should only be in the shared library
# 2) we want the .pc file to include a dependency to {pgport,common}_static for
@@ -70,7 +69,7 @@ libpq_st = static_library('libpq',
libpq_so = shared_library('libpq',
libpq_sources + libpq_so_sources,
include_directories: [libpq_inc, postgres_inc],
- c_args: libpq_c_args,
+ c_args: libpq_c_args + libpq_so_c_args,
c_pch: pch_postgres_fe_h,
version: '5.' + pg_version_major.to_string(),
soversion: host_system != 'windows' ? '5' : '',
@@ -86,12 +85,26 @@ libpq = declare_dependency(
include_directories: [include_directories('.')]
)
+private_deps = [
+ frontend_stlib_code,
+ libpq_deps,
+]
+
+if oauth_flow_supported
+ # libpq.so doesn't link against libcurl, but libpq.a needs libpq-oauth, and
+ # libpq-oauth needs libcurl. Put both into *.private.
+ private_deps += [
+ libpq_oauth_deps,
+ '-lpq-oauth',
+ ]
+endif
+
pkgconfig.generate(
name: 'libpq',
description: 'PostgreSQL libpq library',
url: pg_url,
libraries: libpq,
- libraries_private: [frontend_stlib_code, libpq_deps],
+ libraries_private: private_deps,
)
install_headers(
diff --git a/src/interfaces/libpq/nls.mk b/src/interfaces/libpq/nls.mk
index ae761265852..b87df277d93 100644
--- a/src/interfaces/libpq/nls.mk
+++ b/src/interfaces/libpq/nls.mk
@@ -13,15 +13,21 @@ GETTEXT_FILES = fe-auth.c \
fe-secure-common.c \
fe-secure-gssapi.c \
fe-secure-openssl.c \
- win32.c
-GETTEXT_TRIGGERS = libpq_append_conn_error:2 \
+ win32.c \
+ ../libpq-oauth/oauth-curl.c \
+ ../libpq-oauth/oauth-utils.c
+GETTEXT_TRIGGERS = actx_error:2 \
+ libpq_append_conn_error:2 \
libpq_append_error:2 \
libpq_gettext \
libpq_ngettext:1,2 \
+ oauth_parse_set_error:2 \
pqInternalNotice:2
-GETTEXT_FLAGS = libpq_append_conn_error:2:c-format \
+GETTEXT_FLAGS = actx_error:2:c-format \
+ libpq_append_conn_error:2:c-format \
libpq_append_error:2:c-format \
libpq_gettext:1:pass-c-format \
libpq_ngettext:1:pass-c-format \
libpq_ngettext:2:pass-c-format \
+ oauth_parse_set_error:2:c-format \
pqInternalNotice:2:c-format
diff --git a/src/makefiles/meson.build b/src/makefiles/meson.build
index 55da678ec27..91a8de1ee9b 100644
--- a/src/makefiles/meson.build
+++ b/src/makefiles/meson.build
@@ -203,6 +203,8 @@ pgxs_empty = [
'LIBNUMA_CFLAGS', 'LIBNUMA_LIBS',
'LIBURING_CFLAGS', 'LIBURING_LIBS',
+
+ 'LIBCURL_CPPFLAGS', 'LIBCURL_LDFLAGS', 'LIBCURL_LDLIBS',
]
if host_system == 'windows' and cc.get_argument_syntax() != 'msvc'
diff --git a/src/test/modules/oauth_validator/meson.build b/src/test/modules/oauth_validator/meson.build
index 36d1b26369f..e190f9cf15a 100644
--- a/src/test/modules/oauth_validator/meson.build
+++ b/src/test/modules/oauth_validator/meson.build
@@ -78,7 +78,7 @@ tests += {
],
'env': {
'PYTHON': python.path(),
- 'with_libcurl': libcurl.found() ? 'yes' : 'no',
+ 'with_libcurl': oauth_flow_supported ? 'yes' : 'no',
'with_python': 'yes',
},
},
diff --git a/src/test/modules/oauth_validator/t/002_client.pl b/src/test/modules/oauth_validator/t/002_client.pl
index 8dd502f41e1..21d4acc1926 100644
--- a/src/test/modules/oauth_validator/t/002_client.pl
+++ b/src/test/modules/oauth_validator/t/002_client.pl
@@ -110,7 +110,7 @@ if ($ENV{with_libcurl} ne 'yes')
"fails without custom hook installed",
flags => ["--no-hook"],
expected_stderr =>
- qr/no custom OAuth flows are available, and libpq was not built with libcurl support/
+ qr/no OAuth flows are available \(try installing the libpq-oauth package\)/
);
}
--
2.34.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-04-30 18:09 ` Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Daniel Gustafsson @ 2025-04-30 18:09 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
> On 30 Apr 2025, at 19:59, Jacob Champion <[email protected]> wrote:
> On Wed, Apr 30, 2025 at 5:55 AM Daniel Gustafsson <[email protected]> wrote:
>> Nitpick, but it won't be .so everywhere. Would this be clearar if spelled out
>> with something like "do not rely on libpq-int.h when building libpq-oauth as
>> dynamic shared lib"?
>
> I went with "do not rely on libpq-int.h in dynamic builds of
> libpq-oauth", since devs are hopefully going to be the only people who
> see it. I've also fixed up an errant #endif label right above it.
That's indeed better than my suggestion.
> I'd ideally like to get a working split in for beta.
+Many
> Barring
> objections, I plan to get this pushed tomorrow so that the buildfarm
> has time to highlight any corner cases well before the Saturday
> freeze.
I'll try to kick the tyres a bit more as well.
--
Daniel Gustafsson
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
@ 2025-05-01 17:38 ` Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-03 14:54 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
0 siblings, 2 replies; 194+ messages in thread
From: Jacob Champion @ 2025-05-01 17:38 UTC (permalink / raw)
To: Daniel Gustafsson <[email protected]>; +Cc: Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Wed, Apr 30, 2025 at 11:09 AM Daniel Gustafsson <[email protected]> wrote:
> I'll try to kick the tyres a bit more as well.
Thanks! Alpine seems to be happy with the dlopen() arrangement. And
I've thrown some more Autoconf testing at Rocky, Mac, and Ubuntu.
So, committed. Thanks everyone for all the excellent feedback!
(Further feedback is still very welcome.)
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-05-01 19:24 ` Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-05-01 19:24 UTC (permalink / raw)
To: Daniel Gustafsson <[email protected]>; +Cc: Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Thu, May 1, 2025 at 10:38 AM Jacob Champion
<[email protected]> wrote:
> I've thrown some more Autoconf testing at Rocky, Mac, and Ubuntu.
>
> So, committed.
I forgot --enable-nls in my Mac testing, so indri complains about my
omission of -lintl... I'd incorrectly thought it was no longer needed
after all the gettext motion.
I'm running the attached fixup through CI now.
--Jacob
Attachments:
[application/x-patch] 0001-oauth-Fix-Autoconf-build-on-macOS.patch (1.1K, ../../CAOYmi+kSX0bdXXSYfv0De6rTx1d77KJAYoHA7+9fmJ64z5dOjw@mail.gmail.com/2-0001-oauth-Fix-Autoconf-build-on-macOS.patch)
download | inline diff:
From 379c3fb40547391c649bb5c53668581ee017489e Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Thu, 1 May 2025 12:03:28 -0700
Subject: [PATCH] oauth: Fix Autoconf build on macOS
Oversight in b0635bfda. -lintl is necessary for gettext on Mac, which
libpq-oauth depends on via pgport/pgcommon. (I'd incorrectly removed
this change from an earlier version of the patch, where it was suggested
by Peter Eisentraut.)
Per buildfarm member indri.
---
src/interfaces/libpq-oauth/Makefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/interfaces/libpq-oauth/Makefile b/src/interfaces/libpq-oauth/Makefile
index 3e4b34142e0..270fc0cf2d9 100644
--- a/src/interfaces/libpq-oauth/Makefile
+++ b/src/interfaces/libpq-oauth/Makefile
@@ -47,7 +47,7 @@ $(stlib): override OBJS += $(OBJS_STATIC)
$(stlib): $(OBJS_STATIC)
SHLIB_LINK_INTERNAL = $(libpq_pgport_shlib)
-SHLIB_LINK = $(LIBCURL_LDFLAGS) $(LIBCURL_LDLIBS)
+SHLIB_LINK = $(LIBCURL_LDFLAGS) $(LIBCURL_LDLIBS) $(filter -lintl, $(LIBS))
SHLIB_PREREQS = submake-libpq
SHLIB_EXPORTS = exports.txt
--
2.34.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-05-01 20:31 ` Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-05-01 20:31 UTC (permalink / raw)
To: Daniel Gustafsson <[email protected]>; +Cc: Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Thu, May 1, 2025 at 12:24 PM Jacob Champion
<[email protected]> wrote:
> I'm running the attached fixup through CI now.
(Pushed, and indri is happy again.)
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-05-02 15:11 ` Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Nathan Bossart @ 2025-05-02 15:11 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
After commit b0635bf, I'm seeing the following meson build failures on
macOS:
In file included from ../postgresql/src/interfaces/libpq-oauth/oauth-curl.c:51:
../postgresql/src/interfaces/libpq/libpq-int.h:70:10: fatal error: 'openssl/ssl.h' file not found
70 | #include <openssl/ssl.h>
| ^~~~~~~~~~~~~~~
1 error generated.
The following patch seems to resolve it. I'm curious if commit 4ea1254
might apply to meson, too, but FWIW I haven't noticed any related failures
on my machine.
diff --git a/meson.build b/meson.build
index 29d46c8ad01..19ad03042d3 100644
--- a/meson.build
+++ b/meson.build
@@ -3295,6 +3295,7 @@ libpq_deps += [
libpq_oauth_deps += [
libcurl,
+ ssl,
]
subdir('src/interfaces/libpq')
--
nathan
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
@ 2025-05-02 15:46 ` Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-05-02 15:46 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Fri, May 2, 2025 at 8:11 AM Nathan Bossart <[email protected]> wrote:
>
> After commit b0635bf, I'm seeing the following meson build failures on
> macOS:
Thanks for the report, and sorry for the breakage.
> In file included from ../postgresql/src/interfaces/libpq-oauth/oauth-curl.c:51:
> ../postgresql/src/interfaces/libpq/libpq-int.h:70:10: fatal error: 'openssl/ssl.h' file not found
> 70 | #include <openssl/ssl.h>
> | ^~~~~~~~~~~~~~~
> 1 error generated.
Hm. My test setup here is Homebrew with -Dextra_include_dirs, which
may explain why it's not failing for me. Looks like Cirrus also has
-Dextra_include_dirs...
> The following patch seems to resolve it. I'm curious if commit 4ea1254
> might apply to meson, too, but FWIW I haven't noticed any related failures
> on my machine.
Yeah, I wonder if libintl is being similarly "cheated" on the Meson side.
> diff --git a/meson.build b/meson.build
> index 29d46c8ad01..19ad03042d3 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -3295,6 +3295,7 @@ libpq_deps += [
>
> libpq_oauth_deps += [
> libcurl,
> + ssl,
> ]
Thanks! I think the include directory is the only thing needed for the
static library, not the full link dependency. But I'll try to build up
a new Meson setup, with minimal added settings, to see if I can
reproduce here. Can you share your Meson configuration?
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-05-02 15:59 ` Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-05-02 15:59 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Fri, May 2, 2025 at 8:46 AM Jacob Champion
<[email protected]> wrote:
> Yeah, I wonder if libintl is being similarly "cheated" on the Meson side.
libintl is already coming in via frontend_stlib_code, so that's fine.
So now I'm wondering if any other static clients of libpq-int.h (if
there are any) need the ssl dependency too, for correctness, or if
it's just me.
> But I'll try to build up
> a new Meson setup, with minimal added settings, to see if I can
> reproduce here. Can you share your Meson configuration?
(Never mind -- this was pretty easy to hit in a from-scratch configuration.)
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-05-02 17:05 ` Jacob Champion <[email protected]>
2025-05-02 17:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 17:35 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-05-04 12:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Wolfgang Walther <[email protected]>
0 siblings, 3 replies; 194+ messages in thread
From: Jacob Champion @ 2025-05-02 17:05 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Fri, May 2, 2025 at 8:59 AM Jacob Champion
<[email protected]> wrote:
> libintl is already coming in via frontend_stlib_code, so that's fine.
> So now I'm wondering if any other static clients of libpq-int.h (if
> there are any) need the ssl dependency too, for correctness, or if
> it's just me.
Looks like it's just me. And using partial_dependency for the includes
seems like overkill, so I've kept the full ssl dependency object, but
moved it to the staticlib only, which is enough to solve the breakage
on my machine.
Nathan, if you get a chance, does the attached patch work for you?
Thanks!
--Jacob
Attachments:
[application/octet-stream] 0001-oauth-Correct-SSL-dependency-for-libpq-oauth.a.patch (1.2K, ../../CAOYmi+=pwswyjjXUiL-ej2eUiHxPPWx9u9AZimSmZu3kA2REAA@mail.gmail.com/2-0001-oauth-Correct-SSL-dependency-for-libpq-oauth.a.patch)
download | inline diff:
From 39fb3aa3df9633fca393212df9be2efc6c2f9fdc Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Fri, 2 May 2025 09:44:43 -0700
Subject: [PATCH] oauth: Correct SSL dependency for libpq-oauth.a
libpq-oauth.a includes libpq-int.h, which includes OpenSSL headers. The
Autoconf side picks up the necessary include directories via CPPFLAGS,
but Meson needs the dependency to be made explicit.
Reported-by: Nathan Bossart <[email protected]>
Discussion: https://postgr.es/m/aBTgjDfrdOZmaPgv%40nathan
---
src/interfaces/libpq-oauth/meson.build | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/interfaces/libpq-oauth/meson.build b/src/interfaces/libpq-oauth/meson.build
index 9e7301a7f63..df064c59a40 100644
--- a/src/interfaces/libpq-oauth/meson.build
+++ b/src/interfaces/libpq-oauth/meson.build
@@ -25,7 +25,11 @@ libpq_oauth_st = static_library('libpq-oauth',
libpq_oauth_sources,
include_directories: [libpq_oauth_inc, postgres_inc],
c_pch: pch_postgres_fe_h,
- dependencies: [frontend_stlib_code, libpq_oauth_deps],
+ dependencies: [
+ frontend_stlib_code,
+ libpq_oauth_deps,
+ ssl, # libpq-int.h includes OpenSSL headers
+ ],
kwargs: default_lib_args,
)
--
2.34.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-05-02 17:31 ` Nathan Bossart <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2 siblings, 1 reply; 194+ messages in thread
From: Nathan Bossart @ 2025-05-02 17:31 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Fri, May 02, 2025 at 10:05:26AM -0700, Jacob Champion wrote:
> Nathan, if you get a chance, does the attached patch work for you?
Yup, thanks!
--
nathan
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
@ 2025-05-02 17:42 ` Jacob Champion <[email protected]>
2025-06-30 16:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-05-02 17:42 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Fri, May 2, 2025 at 10:31 AM Nathan Bossart <[email protected]> wrote:
> Yup, thanks!
Great, thanks. I'll push it soon.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-06-30 16:58 ` Andres Freund <[email protected]>
2025-06-30 17:01 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Andres Freund @ 2025-06-30 16:58 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
Hi,
On 2025-05-02 10:42:34 -0700, Jacob Champion wrote:
> Great, thanks. I'll push it soon.
I just noticed that I think the dependencies on the meson build aren't quite
sufficient:
andres@awork3:/srv/dev/build/postgres/m-dev-assert$ ninja install-quiet
[2205/2205 1 100%] Generating install-quiet with a custom command
FAILED: install-quiet
/usr/bin/python3 /home/andres/src/meson/meson.py install --quiet --no-rebuild
ERROR: File 'src/interfaces/libpq-oauth/libpq-oauth.a' could not be found
ninja: build stopped: subcommand failed.
Probably just needs to be added to the installed_targets list.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-30 16:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
@ 2025-06-30 17:01 ` Daniel Gustafsson <[email protected]>
2025-06-30 18:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Daniel Gustafsson @ 2025-06-30 17:01 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Jacob Champion <[email protected]>; Nathan Bossart <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
> On 30 Jun 2025, at 18:58, Andres Freund <[email protected]> wrote:
>
> Hi,
>
> On 2025-05-02 10:42:34 -0700, Jacob Champion wrote:
>> Great, thanks. I'll push it soon.
>
> I just noticed that I think the dependencies on the meson build aren't quite
> sufficient:
>
> andres@awork3:/srv/dev/build/postgres/m-dev-assert$ ninja install-quiet
> [2205/2205 1 100%] Generating install-quiet with a custom command
> FAILED: install-quiet
> /usr/bin/python3 /home/andres/src/meson/meson.py install --quiet --no-rebuild
>
> ERROR: File 'src/interfaces/libpq-oauth/libpq-oauth.a' could not be found
> ninja: build stopped: subcommand failed.
>
> Probably just needs to be added to the installed_targets list.
Thanks for the report, I'll take a look today to get it fixed.
--
Daniel Gustafsson
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-30 16:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-06-30 17:01 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
@ 2025-06-30 18:33 ` Jacob Champion <[email protected]>
2025-06-30 22:52 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-06-30 18:33 UTC (permalink / raw)
To: Daniel Gustafsson <[email protected]>; +Cc: Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Mon, Jun 30, 2025 at 10:02 AM Daniel Gustafsson <[email protected]> wrote:
> > On 30 Jun 2025, at 18:58, Andres Freund <[email protected]> wrote:
> > Probably just needs to be added to the installed_targets list.
>
> Thanks for the report, I'll take a look today to get it fixed.
Thanks both!
Looking at the installed_targets stuff, though... why do we use `meson
install --no-rebuild` in combination with `depends:
installed_targets`? Can't we just use Meson's dependency tracking
during installation, and avoid this hazard?
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-30 16:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-06-30 17:01 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-06-30 18:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-06-30 22:52 ` Daniel Gustafsson <[email protected]>
2025-06-30 23:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Daniel Gustafsson @ 2025-06-30 22:52 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
> On 30 Jun 2025, at 20:33, Jacob Champion <[email protected]> wrote:
>
> On Mon, Jun 30, 2025 at 10:02 AM Daniel Gustafsson <[email protected]> wrote:
>>> On 30 Jun 2025, at 18:58, Andres Freund <[email protected]> wrote:
>>> Probably just needs to be added to the installed_targets list.
>>
>> Thanks for the report, I'll take a look today to get it fixed.
>
> Thanks both!
>
> Looking at the installed_targets stuff, though... why do we use `meson
> install --no-rebuild` in combination with `depends:
> installed_targets`? Can't we just use Meson's dependency tracking
> during installation, and avoid this hazard?
I suspect it is because without --no-rebuild the quiet target isn't entirely
quiet. Still, I was unable to make something that work in all build
combinations while keeping --no-rebuild (which isn't indicative of it being
possible to do). Is --no-rebuild just there to reduce output noise, or is
there another reason that I don't see?
--
Daniel Gustafsson
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-30 16:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-06-30 17:01 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-06-30 18:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-30 22:52 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
@ 2025-06-30 23:42 ` Andres Freund <[email protected]>
2025-07-18 17:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Andres Freund @ 2025-06-30 23:42 UTC (permalink / raw)
To: Daniel Gustafsson <[email protected]>; +Cc: Jacob Champion <[email protected]>; Nathan Bossart <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
Hi,
On 2025-07-01 00:52:49 +0200, Daniel Gustafsson wrote:
> > On 30 Jun 2025, at 20:33, Jacob Champion <[email protected]> wrote:
> >
> > On Mon, Jun 30, 2025 at 10:02 AM Daniel Gustafsson <[email protected]> wrote:
> >>> On 30 Jun 2025, at 18:58, Andres Freund <[email protected]> wrote:
> >>> Probably just needs to be added to the installed_targets list.
> >>
> >> Thanks for the report, I'll take a look today to get it fixed.
> >
> > Thanks both!
> >
> > Looking at the installed_targets stuff, though... why do we use `meson
> > install --no-rebuild` in combination with `depends:
> > installed_targets`? Can't we just use Meson's dependency tracking
> > during installation, and avoid this hazard?
I don't think that's really possible - the dependency tracking is useful to
generate granular *rebuild* information, but doesn't help with the first
build.
If we had dependency generation for the install target it could be helpful to
discover missing dependencies though.
> I suspect it is because without --no-rebuild the quiet target isn't entirely
> quiet.
No - the issue is that you're not allowed to run ninja while ninja is running,
as that would corrupt it's tracking (and build things multiple times). meson
install --no-rebuild would run ninja to build things...
> Still, I was unable to make something that work in all build combinations
> while keeping --no-rebuild (which isn't indicative of it being possible to
> do).
Hm, what problem did you encounter? I don't think there should be any
difficulty?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-30 16:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-06-30 17:01 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-06-30 18:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-30 22:52 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-06-30 23:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
@ 2025-07-18 17:26 ` Andres Freund <[email protected]>
2025-07-18 22:29 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Andres Freund @ 2025-07-18 17:26 UTC (permalink / raw)
To: Daniel Gustafsson <[email protected]>; +Cc: Jacob Champion <[email protected]>; Nathan Bossart <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
Hi,
On 2025-06-30 19:42:51 -0400, Andres Freund wrote:
> On 2025-07-01 00:52:49 +0200, Daniel Gustafsson wrote:
> > > On 30 Jun 2025, at 20:33, Jacob Champion <[email protected]> wrote:
> > >
> > > On Mon, Jun 30, 2025 at 10:02 AM Daniel Gustafsson <[email protected]> wrote:
> > >>> On 30 Jun 2025, at 18:58, Andres Freund <[email protected]> wrote:
> > >>> Probably just needs to be added to the installed_targets list.
> > >>
> > >> Thanks for the report, I'll take a look today to get it fixed.
> > >
> > > Thanks both!
> > >
> > > Looking at the installed_targets stuff, though... why do we use `meson
> > > install --no-rebuild` in combination with `depends:
> > > installed_targets`? Can't we just use Meson's dependency tracking
> > > during installation, and avoid this hazard?
>
> I don't think that's really possible - the dependency tracking is useful to
> generate granular *rebuild* information, but doesn't help with the first
> build.
>
> If we had dependency generation for the install target it could be helpful to
> discover missing dependencies though.
>
>
> > I suspect it is because without --no-rebuild the quiet target isn't entirely
> > quiet.
>
> No - the issue is that you're not allowed to run ninja while ninja is running,
> as that would corrupt it's tracking (and build things multiple times). meson
> install --no-rebuild would run ninja to build things...
>
>
> > Still, I was unable to make something that work in all build combinations
> > while keeping --no-rebuild (which isn't indicative of it being possible to
> > do).
>
> Hm, what problem did you encounter? I don't think there should be any
> difficulty?
Ping?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-30 16:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-06-30 17:01 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-06-30 18:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-30 22:52 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-06-30 23:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-07-18 17:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
@ 2025-07-18 22:29 ` Daniel Gustafsson <[email protected]>
2025-07-18 23:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Daniel Gustafsson @ 2025-07-18 22:29 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Jacob Champion <[email protected]>; Nathan Bossart <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
> On 18 Jul 2025, at 19:26, Andres Freund <[email protected]> wrote:
>> Hm, what problem did you encounter? I don't think there should be any
>> difficulty?
>
> Ping?
Ugh, In preparing for going on vacation this fell off the radar. I'll try to
get to looking at it tomorrow during downtime unless beaten to it.
--
Daniel Gustafsson
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-30 16:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-06-30 17:01 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-06-30 18:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-30 22:52 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-06-30 23:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-07-18 17:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-07-18 22:29 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
@ 2025-07-18 23:31 ` Jacob Champion <[email protected]>
2025-08-05 18:54 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-07-18 23:31 UTC (permalink / raw)
To: Daniel Gustafsson <[email protected]>; +Cc: Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Fri, Jul 18, 2025 at 3:29 PM Daniel Gustafsson <[email protected]> wrote:
> Ugh, In preparing for going on vacation this fell off the radar. I'll try to
> get to looking at it tomorrow during downtime unless beaten to it.
Your earlier mail made me worried I'd missed something, but is the
attached diff what Andres was asking for? A `ninja clean; ninja
install-quiet` now works for me with this applied.
--Jacob
Attachments:
[application/octet-stream] fix_installed_targets.diff (298B, ../../CAOYmi+=wgv=ueJLzC1RoQ0qtcOi-SwT+r-QyA7oMQUnzH0NRtQ@mail.gmail.com/2-fix_installed_targets.diff)
download | inline diff:
diff --git a/meson.build b/meson.build
index 5365aaf95e6..28706bcf3bb 100644
--- a/meson.build
+++ b/meson.build
@@ -3469,6 +3469,8 @@ installed_targets = [
backend_targets,
bin_targets,
libpq_st,
+ libpq_oauth_so,
+ libpq_oauth_st,
pl_targets,
contrib_targets,
nls_mo_targets,
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-30 16:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-06-30 17:01 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-06-30 18:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-30 22:52 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-06-30 23:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-07-18 17:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-07-18 22:29 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-07-18 23:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-08-05 18:54 ` Jacob Champion <[email protected]>
2025-08-08 16:23 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-08-05 18:54 UTC (permalink / raw)
To: Daniel Gustafsson <[email protected]>; +Cc: Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Fri, Jul 18, 2025 at 4:31 PM Jacob Champion
<[email protected]> wrote:
> Your earlier mail made me worried I'd missed something, but is the
> attached diff what Andres was asking for? A `ninja clean; ninja
> install-quiet` now works for me with this applied.
Ping. I'll plan to commit this by the beta3 cutoff but it'd be nice to
verify that I'm not missing something obvious. :D
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-30 16:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-06-30 17:01 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-06-30 18:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-30 22:52 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-06-30 23:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-07-18 17:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-07-18 22:29 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-07-18 23:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-08-05 18:54 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-08-08 16:23 ` Jacob Champion <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2025-08-08 16:23 UTC (permalink / raw)
To: Daniel Gustafsson <[email protected]>; +Cc: Andres Freund <[email protected]>; Nathan Bossart <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Tue, Aug 5, 2025 at 11:54 AM Jacob Champion
<[email protected]> wrote:
> Ping. I'll plan to commit this by the beta3 cutoff but it'd be nice to
> verify that I'm not missing something obvious. :D
(Committed yesterday.)
I wonder if there's a Meson feature request in here somewhere, to be
able to compose targets from other builtin targets without having to
duplicate the internal bookkeeping.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-05-02 17:35 ` Tom Lane <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2 siblings, 1 reply; 194+ messages in thread
From: Tom Lane @ 2025-05-02 17:35 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
Jacob Champion <[email protected]> writes:
> Looks like it's just me. And using partial_dependency for the includes
> seems like overkill, so I've kept the full ssl dependency object, but
> moved it to the staticlib only, which is enough to solve the breakage
> on my machine.
> Nathan, if you get a chance, does the attached patch work for you?
FWIW, on my Mac a meson build from HEAD goes through fine, with or
without this patch. I'm getting openssl and libcurl from MacPorts
not Homebrew, but I don't know why that would make any difference.
regards, tom lane
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:35 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
@ 2025-05-02 17:42 ` Jacob Champion <[email protected]>
2025-05-02 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 18:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
0 siblings, 2 replies; 194+ messages in thread
From: Jacob Champion @ 2025-05-02 17:42 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Fri, May 2, 2025 at 10:35 AM Tom Lane <[email protected]> wrote:
> FWIW, on my Mac a meson build from HEAD goes through fine, with or
> without this patch. I'm getting openssl and libcurl from MacPorts
> not Homebrew, but I don't know why that would make any difference.
Do your <libintl.h> and <openssl/*.h> live in the same place? Mine do,
so I had to disable NLS to get this to break.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:35 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-05-02 17:59 ` Nathan Bossart <[email protected]>
1 sibling, 0 replies; 194+ messages in thread
From: Nathan Bossart @ 2025-05-02 17:59 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Tom Lane <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Fri, May 02, 2025 at 10:42:25AM -0700, Jacob Champion wrote:
> On Fri, May 2, 2025 at 10:35 AM Tom Lane <[email protected]> wrote:
>> FWIW, on my Mac a meson build from HEAD goes through fine, with or
>> without this patch. I'm getting openssl and libcurl from MacPorts
>> not Homebrew, but I don't know why that would make any difference.
>
> Do your <libintl.h> and <openssl/*.h> live in the same place? Mine do,
> so I had to disable NLS to get this to break.
I enabled NLS and the problem disappeared for me, but that seems to be a
side effect of setting -Dextra_{include,lib}_dirs to point to my Homebrew
directories, which I needed to do to get NLS to work.
--
nathan
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:35 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-05-02 18:25 ` Tom Lane <[email protected]>
2025-05-02 18:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Tom Lane @ 2025-05-02 18:25 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
Jacob Champion <[email protected]> writes:
> On Fri, May 2, 2025 at 10:35 AM Tom Lane <[email protected]> wrote:
>> FWIW, on my Mac a meson build from HEAD goes through fine, with or
>> without this patch. I'm getting openssl and libcurl from MacPorts
>> not Homebrew, but I don't know why that would make any difference.
> Do your <libintl.h> and <openssl/*.h> live in the same place? Mine do,
> so I had to disable NLS to get this to break.
Yeah, they are both under /opt/local/include in a MacPorts setup.
But disabling NLS doesn't break it for me. I tried
meson setup build --auto-features=disabled -Dlibcurl=enabled
to make sure that /opt/local/include wasn't getting pulled in
some other way, and it still builds.
Apropos of that: our fine manual claims that option is spelled
--auto_features, but that fails for me. Is that a typo in our
manual, or do some meson versions accept the underscore?
regards, tom lane
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:35 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 18:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
@ 2025-05-02 18:46 ` Jacob Champion <[email protected]>
2025-05-02 18:52 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-05-02 19:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
0 siblings, 2 replies; 194+ messages in thread
From: Jacob Champion @ 2025-05-02 18:46 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Fri, May 2, 2025 at 11:26 AM Tom Lane <[email protected]> wrote:
> Yeah, they are both under /opt/local/include in a MacPorts setup.
> But disabling NLS doesn't break it for me. I tried
>
> meson setup build --auto-features=disabled -Dlibcurl=enabled
>
> to make sure that /opt/local/include wasn't getting pulled in
> some other way, and it still builds.
Hm. If you clear out the build artifacts under
src/interfaces/libpq-oauth, and then build with
$ ninja -v src/interfaces/libpq-oauth/libpq-oauth.a
does that help surface anything interesting?
> Apropos of that: our fine manual claims that option is spelled
> --auto_features, but that fails for me. Is that a typo in our
> manual, or do some meson versions accept the underscore?
--auto_features doesn't work for me either. That looks like an
accidental mashup of --auto-features and -Dauto_features.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:35 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 18:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-05-02 18:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-05-02 18:52 ` Tom Lane <[email protected]>
2025-05-02 18:56 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Tom Lane @ 2025-05-02 18:52 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
Jacob Champion <[email protected]> writes:
> Hm. If you clear out the build artifacts under
> src/interfaces/libpq-oauth, and then build with
> $ ninja -v src/interfaces/libpq-oauth/libpq-oauth.a
> does that help surface anything interesting?
$ rm -rf src/interfaces/libpq-oauth
$ ninja -v src/interfaces/libpq-oauth/libpq-oauth.a
[1/2] ccache cc -Isrc/interfaces/libpq-oauth/libpq-oauth.a.p -Isrc/interfaces/libpq-oauth -I../src/interfaces/libpq-oauth -Isrc/interfaces/libpq -I../src/interfaces/libpq -Isrc/port -I../src/port -Isrc/include -I../src/include -I/opt/local/include -I/opt/local/libexec/openssl3/include -fdiagnostics-color=always -Wall -Winvalid-pch -O2 -g -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.4.sdk -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wmissing-prototypes -Wpointer-arith -Werror=vla -Werror=unguarded-availability-new -Wendif-labels -Wmissing-format-attribute -Wcast-function-type -Wformat-security -Wdeclaration-after-statement -Wmissing-variable-declarations -Wno-unused-command-line-argument -Wno-compound-token-split-by-macro -Wno-format-truncation -Wno-cast-function-type-strict -MD -MQ src/interfaces/libpq-oauth/libpq-oauth.a.p/oauth-curl.c.o -MF src/interfaces/libpq-oauth/libpq-oauth.a.p/oauth-curl.c.o.d -o src/interfaces/libpq-oauth/libpq-oauth.a.p/oauth-curl.c.o -c ../src/interfaces/libpq-oauth/oauth-curl.c
[2/2] rm -f src/interfaces/libpq-oauth/libpq-oauth.a && ar csr src/interfaces/libpq-oauth/libpq-oauth.a src/interfaces/libpq-oauth/libpq-oauth.a.p/oauth-curl.c.o && ranlib -c src/interfaces/libpq-oauth/libpq-oauth.a
So it's getting -I/opt/local/include and also
-I/opt/local/libexec/openssl3/include from somewhere,
which I guess must be libcurl's pkg-config data ... yup:
$ pkg-config --cflags libcurl
-I/opt/local/include -I/opt/local/libexec/openssl3/include -I/opt/local/include
I bet Homebrew's libcurl packaging doesn't do that.
regards, tom lane
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:35 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 18:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-05-02 18:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 18:52 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
@ 2025-05-02 18:56 ` Jacob Champion <[email protected]>
2025-05-02 19:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-05-02 18:56 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Fri, May 2, 2025 at 11:52 AM Tom Lane <[email protected]> wrote:
> $ pkg-config --cflags libcurl
> -I/opt/local/include -I/opt/local/libexec/openssl3/include -I/opt/local/include
>
> I bet Homebrew's libcurl packaging doesn't do that.
Nope, Homebrew breaks them out into smaller pieces:
% PKG_CONFIG_PATH=/opt/homebrew/opt/curl/lib/pkgconfig pkg-config
--cflags libcurl
-I/opt/homebrew/Cellar/curl/8.13.0/include
-I/opt/homebrew/Cellar/brotli/1.1.0/include
-I/opt/homebrew/opt/zstd/include
-I/opt/homebrew/Cellar/libssh2/1.11.1/include
-I/opt/homebrew/Cellar/rtmpdump/2.4-20151223_3/include
-I/opt/homebrew/Cellar/openssl@3/3.5.0/include
-I/opt/homebrew/Cellar/libnghttp2/1.65.0/include
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:35 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 18:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-05-02 18:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 18:52 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-05-02 18:56 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-05-02 19:05 ` Jacob Champion <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2025-05-02 19:05 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
On Fri, May 2, 2025 at 11:56 AM Jacob Champion
<[email protected]> wrote:
> -I/opt/homebrew/Cellar/openssl@3/3.5.0/include
Except it _is_ right there.
Oh, ha -- I'm not using Homebrew's Curl in this minimal build. Looks
like it's coming from the sysroot.
% ls -l /Library/Developer/CommandLineTools/SDKs/MacOSX15.2.sdk/usr/include/curl
total 208
-rw-r--r-- 1 root wheel 129052 Nov 9 22:54 curl.h
-rw-r--r-- 1 root wheel 3044 Nov 9 22:54 curlver.h
...
Well, that was fun.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:35 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 18:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-05-02 18:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-05-02 19:05 ` Tom Lane <[email protected]>
1 sibling, 0 replies; 194+ messages in thread
From: Tom Lane @ 2025-05-02 19:05 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
Jacob Champion <[email protected]> writes:
> On Fri, May 2, 2025 at 11:26 AM Tom Lane <[email protected]> wrote:
>> Apropos of that: our fine manual claims that option is spelled
>> --auto_features, but that fails for me. Is that a typo in our
>> manual, or do some meson versions accept the underscore?
> --auto_features doesn't work for me either. That looks like an
> accidental mashup of --auto-features and -Dauto_features.
Ah, I see somebody already complained of this [1], but apparently
we did nothing about it. I shall go fix it now.
regards, tom lane
[1] https://www.postgresql.org/message-id/flat/172465652540.862882.17808523044292761256%40wrigleys.postg...
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-05-04 12:58 ` Wolfgang Walther <[email protected]>
2025-05-06 17:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2 siblings, 1 reply; 194+ messages in thread
From: Wolfgang Walther @ 2025-05-04 12:58 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; Nathan Bossart <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Devrim Gündüz <[email protected]>
Jacob Champion:
>> libintl is already coming in via frontend_stlib_code, so that's fine.
>> So now I'm wondering if any other static clients of libpq-int.h (if
>> there are any) need the ssl dependency too, for correctness, or if
>> it's just me.
>
> Looks like it's just me. And using partial_dependency for the includes
> seems like overkill, so I've kept the full ssl dependency object, but
> moved it to the staticlib only, which is enough to solve the breakage
> on my machine.
>
> Nathan, if you get a chance, does the attached patch work for you?
I couldn't reproduce the problem, so did not test the latest patch. But
I tested a lot of scenarios on nixpkgs with latest master (250a718a):
- aarch64 + x86_64 architectures, both Linux and MacOS
- Autoconf and Meson
- Various features enabled / disabled in different configurations (NLS,
OpenSSL, GSSAPI)
- And additionally some cross-compiling from x86_64 Linux to aarch64
Linux and x86_64 FreeBSD
Worked very well.
The only inconsistency I was able to find is the autoconf-generated
libpq.pc file, which has this:
Requires.private: libssl, libcrypto libcurl
Note the missing "," before libcurl.
It does *not* affect functionality, though:
pkg-config --print-requires-private libpq
libssl
libcrypto
libcurl
The meson-generated libpq.pc looks like this:
Requires.private: openssl, krb5-gssapi, libcurl >= 7.61.0
I was only able to test the latter in an end-to-end fully static build
of a downstream dependency - works great. The final executable has all
the expected oauth strings in it.
Best,
Wolfgang
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-04 12:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Wolfgang Walther <[email protected]>
@ 2025-05-06 17:49 ` Jacob Champion <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2025-05-06 17:49 UTC (permalink / raw)
To: Wolfgang Walther <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Daniel Gustafsson <[email protected]>; Christoph Berg <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Devrim Gündüz <[email protected]>
On Sun, May 4, 2025 at 5:58 AM Wolfgang Walther <[email protected]> wrote:
> The only inconsistency I was able to find is the autoconf-generated
> libpq.pc file, which has this:
>
> Requires.private: libssl, libcrypto libcurl
Oh, I see what I did. Will fix, thanks.
> I was only able to test the latter in an end-to-end fully static build
> of a downstream dependency - works great. The final executable has all
> the expected oauth strings in it.
Thank you so much for all the detailed testing!
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-05-03 14:54 ` Christoph Berg <[email protected]>
2025-05-05 12:37 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Devrim Gündüz <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Christoph Berg @ 2025-05-03 14:54 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Devrim Gündüz <[email protected]>
Re: Jacob Champion
> So, committed. Thanks everyone for all the excellent feedback!
The package split between libpq5 and libpq-oauth in Debian has already
been accepted into the experimental branch.
Thanks,
Christoph
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-03 14:54 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
@ 2025-05-05 12:37 ` Devrim Gündüz <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Devrim Gündüz @ 2025-05-05 12:37 UTC (permalink / raw)
To: Christoph Berg <[email protected]>; Jacob Champion <[email protected]>; Daniel Gustafsson <[email protected]>; Jelte Fennema-Nio <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>
Hi,
On Sat, 2025-05-03 at 16:54 +0200, Christoph Berg wrote:
> The package split between libpq5 and libpq-oauth in Debian has already
> been accepted into the experimental branch.
RPMs will ship postgresql18-libs and postgresql18-libs-oauth. The latter
depends on the former for sure.
Regards,
--
Devrim Gündüz
Open Source Solution Architect, PostgreSQL Major Contributor
BlueSky: @devrim.gunduz.org , @gunduz.org
Attachments:
[application/pgp-signature] signature.asc (858B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
@ 2025-04-15 19:45 ` Jacob Champion <[email protected]>
2 siblings, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2025-04-15 19:45 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Christoph Berg <[email protected]>; Andres Freund <[email protected]>; Tom Lane <[email protected]>; Bruce Momjian <[email protected]>; pgsql-hackers; Daniel Gustafsson <[email protected]>; Thomas Munro <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Antonin Houska <[email protected]>; Wolfgang Walther <[email protected]>; Jelte Fennema-Nio <[email protected]>
On Tue, Apr 15, 2025 at 12:21 PM Robert Haas <[email protected]> wrote:
> I also don't mind being wrong, of course. But I think it's better to
> bet on making this like other things and then change strategy if that
> doesn't work out, rather than starting out by making this different
> from other things.
Works for me. (And it's less work, too!)
Thanks,
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
@ 2025-06-12 19:58 ` Jacob Champion <[email protected]>
2025-06-20 10:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-06-12 19:58 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Andres Freund <[email protected]>; Daniel Gustafsson <[email protected]>; Peter Eisentraut <[email protected]>; Antonin Houska <[email protected]>; pgsql-hackers
On Thu, Mar 6, 2025 at 12:57 PM Jacob Champion
<[email protected]> wrote:
> 3) There is a related performance bug on other platforms. If a Curl
> timeout happens partway through a request (so libcurl won't clear it),
> the timer-expired event will stay set and CPU will be burned to spin
> pointlessly on drive_request(). This is much easier to notice after
> taking Happy Eyeballs out of the picture. It doesn't cause logical
> failures -- Curl basically discards the unnecessary calls -- but it's
> definitely unintended.
>
> ...
>
> I plan to defer working on Problem 3, which should just be a
> performance bug, until the tests are green again. And I would like to
> eventually add some stronger unit tests for the timer behavior, to
> catch other potential OS-specific problems in the future.
To follow up on this: I had intended to send a patch fixing the timer
bug this week, but after fixing it, the performance problem did not
disappear. Turns out: other file descriptors can get stuck open on
BSD, depending on how complicated Curl wants to make the order of
operations, and the existing tests aren't always enough to expose it.
(It also depends on the Curl version installed.)
I will split this off into its own thread soon, because this
megathread is just too big, but I wanted to make a note here and file
an open item. As part of that, I have a set of more rigorous unit
tests for the libcurl-libpq interaction that I'm working on, since the
external view of "the flow worked/didn't work" is not enough to
indicate internal health.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-06-12 19:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-06-20 10:08 ` Ivan Kush <[email protected]>
2025-06-23 15:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Ivan Kush @ 2025-06-20 10:08 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: pgsql-hackers; [email protected]
Hello!
This patch fixes CPPFLAGS, LDFLAGS, LIBS when checking AsyncDNS libcurl
support in configure
Custom parameters and paths to libcurl were mistakenly excluded from
CPPFLAGS, LDFLAGS, and LIBS, although AsyncDNS check was OK.
For example, the command `pkg-config --libs libcurl` gives
`-L/usr/local/lib -lcurl`. LDFLAGS will not contain `-L/usr/local/lib`.
This patch fixes such behaviour.
Test case:
I've tested custom Postgres in an old Debian based Linux distro. This
distro contains old libcurl (< 7.61, package libcurl3) that was compiled
with symbols CURL_OPENSSL_3. So I've installed newer version of
libcurlssl, package libcurl4-openssl-dev, that contains symbols
CURL_OPENSSL_4 and compiled my libcurl version > 7.61.
After compilation during testing some Postgres shared libraries or
binaries that was linked with libcurl showed an error "version
CURL_OPENSSL_3 not found (required by …/libcurl.so.4)"
--
Best wishes,
Ivan Kush
Tantor Labs LLC
Attachments:
[text/x-patch] 0001_oauth_ Fix_CPPFLAGS,_LDFLAGS,_LIBS_when_checking_AsyncDNS_libcurl_support.patch (1.9K, ../../[email protected]/2-0001_oauth_%20Fix_CPPFLAGS%2C_LDFLAGS%2C_LIBS_when_checking_AsyncDNS_libcurl_support.patch)
download | inline diff:
From 8a24c24f85c40e2aa0c40afc8f9cd7a19afa66c3 Mon Sep 17 00:00:00 2001
From: Ivan Kush <[email protected]>
Date: Fri, 20 Jun 2025 12:16:47 +0300
Subject: [PATCH] oauth: Fix CPPFLAGS, LDFLAGS, LIBS when checking AsyncDNS libcurl support
Custom parameters and paths to libcurl were mistakenly excluded from CPPFLAGS,
LDFLAGS, and LIBS.
For example, the command `pkg-config --libs libcurl` gives
`-L/usr/local/lib -lcurl`. LDFLAGS will not contain `-L/usr/local/lib`
This patch fixes this.
Author: Ivan Kush <[email protected]>
Author: Lev Nikolaev <[email protected]>
Reviewed-by:
Discussion:
---
config/programs.m4 | 7 +++----
configure | 8 +++-----
2 files changed, 6 insertions(+), 9 deletions(-)
diff --git a/config/programs.m4 b/config/programs.m4
index 0ad1e58b48d..2556e469323 100644
--- a/config/programs.m4
+++ b/config/programs.m4
@@ -348,9 +348,8 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
*** The installed version of libcurl does not support asynchronous DNS
*** lookups. Rebuild libcurl with the AsynchDNS feature enabled in order
*** to use it with libpq.])
+ CPPFLAGS=$pgac_save_CPPFLAGS
+ LDFLAGS=$pgac_save_LDFLAGS
+ LIBS=$pgac_save_LIBS
fi
-
- CPPFLAGS=$pgac_save_CPPFLAGS
- LDFLAGS=$pgac_save_LDFLAGS
- LIBS=$pgac_save_LIBS
])# PGAC_CHECK_LIBCURL
diff --git a/configure b/configure
index 4f15347cc95..46a011d1d1b 100755
--- a/configure
+++ b/configure
@@ -12883,12 +12883,10 @@ $as_echo "$pgac_cv__libcurl_async_dns" >&6; }
*** The installed version of libcurl does not support asynchronous DNS
*** lookups. Rebuild libcurl with the AsynchDNS feature enabled in order
*** to use it with libpq." "$LINENO" 5
+ CPPFLAGS=$pgac_save_CPPFLAGS
+ LDFLAGS=$pgac_save_LDFLAGS
+ LIBS=$pgac_save_LIBS
fi
-
- CPPFLAGS=$pgac_save_CPPFLAGS
- LDFLAGS=$pgac_save_LDFLAGS
- LIBS=$pgac_save_LIBS
-
fi
if test "$with_gssapi" = yes ; then
--
2.34.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-06-12 19:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-20 10:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
@ 2025-06-23 15:32 ` Jacob Champion <[email protected]>
2025-07-02 12:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-06-23 15:32 UTC (permalink / raw)
To: Ivan Kush <[email protected]>; +Cc: pgsql-hackers; [email protected]
On Fri, Jun 20, 2025 at 3:08 AM Ivan Kush <[email protected]> wrote:
>
> Hello!
>
> This patch fixes CPPFLAGS, LDFLAGS, LIBS when checking AsyncDNS libcurl
> support in configure
Hi Ivan, thanks for the report! Your patch puts new logic directly
after an AC_MSG_ERROR() call, so any effect has to come from the fact
that we're no longer restoring the old compiler and linker flags.
That's not what we want -- Curl needs to be isolated from the rest of
the build.
Let's focus on the error you're seeing:
> After compilation during testing some Postgres shared libraries or
> binaries that was linked with libcurl showed an error "version
> CURL_OPENSSL_3 not found (required by …/libcurl.so.4)"
What's your configure line? You need to make sure that your custom
libcurl is used at configure-time, compile-time, and run-time.
And which binaries are complaining? The only thing that should ever be
linked against libcurl is libpq-oauth-18.so.
Thanks,
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-06-12 19:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-20 10:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-06-23 15:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-07-02 12:45 ` Ivan Kush <[email protected]>
2025-07-02 14:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Ivan Kush @ 2025-07-02 12:45 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: pgsql-hackers; [email protected]
Thanks for the clarification! I thought linker flags should be installed
globally for all compilation targets.
Another question:
Why don't we set LIBS in the configure in "checking for curl_multi_init"
using LIBCURL_LIBS or LIBCURL_LDFLAGS?
https://github.com/postgres/postgres/blob/master/configure#L12734
Like this:
LIBS="$(LIBCURL_LDFLAGS) $(LIBCURL_LDLIBS)"
And set LIBS with -lcurl.
As I understand we need to check the properties of libcurl we are
compiling with?
It may be some local libcurl from /opt/my_libcurl. So LIBCURL_... may
contain a flag like -L/opt/my_libcurl
Without these LIBCURL... variables we will check a system libcurl, not
our local.
I mean why don't we set LIBS
current *configure*
$as_echo_n "checking for curl_multi_init in -lcurl... " >&6; }
....
else
ac_check_lib_save_LIBS=$LIBS
LIBS="-lcurl $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
https://github.com/postgres/postgres/blob/master/configure#L12734
For example, I've logged flags after this code sample and they don't
contain -L/opt/my_libcurl
IVK configure:13648: CFLAGS=-Wall -Wmissing-prototypes
-Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels
-Wmissing-format-attribute -Wformat-security -fno-strict-aliasing
-fwrapv -fexcess-precision=standard -pipe -O2
IVK configure:13649: LDFLAGS=-Wl,-z,relro -Wl,-z,now -flto=auto
-ffat-lto-objects -L/usr/lib/llvm-10/lib -L/usr/local/lib/zstd
IVK configure:13650: LIBS=-lcurl -lz -lreadline -lpthread -lrt
-ldl -lm
IVK configure:13651: LDLIBS=
On 25-06-23 18:32, Jacob Champion wrote:
> On Fri, Jun 20, 2025 at 3:08 AM Ivan Kush <[email protected]> wrote:
>> Hello!
>>
>> This patch fixes CPPFLAGS, LDFLAGS, LIBS when checking AsyncDNS libcurl
>> support in configure
> Hi Ivan, thanks for the report! Your patch puts new logic directly
> after an AC_MSG_ERROR() call, so any effect has to come from the fact
> that we're no longer restoring the old compiler and linker flags.
> That's not what we want -- Curl needs to be isolated from the rest of
> the build.
>
> Let's focus on the error you're seeing:
>
>> After compilation during testing some Postgres shared libraries or
>> binaries that was linked with libcurl showed an error "version
>> CURL_OPENSSL_3 not found (required by …/libcurl.so.4)"
> What's your configure line? You need to make sure that your custom
> libcurl is used at configure-time, compile-time, and run-time.
>
> And which binaries are complaining? The only thing that should ever be
> linked against libcurl is libpq-oauth-18.so.
>
> Thanks,
> --Jacob
--
Best wishes,
Ivan Kush
Tantor Labs LLC
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-06-12 19:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-20 10:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-06-23 15:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-02 12:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
@ 2025-07-02 14:46 ` Jacob Champion <[email protected]>
2025-07-09 17:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-07-02 14:46 UTC (permalink / raw)
To: Ivan Kush <[email protected]>; +Cc: pgsql-hackers; [email protected]
On Wed, Jul 2, 2025 at 5:45 AM Ivan Kush <[email protected]> wrote:
>
> Thanks for the clarification! I thought linker flags should be installed
> globally for all compilation targets.
Not for libcurl, since the libpq-oauth module split.
> Another question:
>
> Why don't we set LIBS in the configure in "checking for curl_multi_init"
> using LIBCURL_LIBS or LIBCURL_LDFLAGS?
> [...]
> Without these LIBCURL... variables we will check a system libcurl, not
> our local.
Ah, that's definitely a bug. I've tested alternate PKG_CONFIG_PATHs,
but I haven't regularly tested on systems that have no system libcurl
at all. So those header and lib checks need to be moved after the use
of LIBCURL_CPPFLAGS and LIBCURL_LDFLAGS to prevent a false failure.
Otherwise they're only useful for the LIBCURL_LDLIBS assignment.
I wonder if I should just get rid of those to better match the Meson
implementation... but the error messages from the checks will likely
be nicer than compilation failures during the later test programs. Hm.
(Thanks for the report!)
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-06-12 19:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-20 10:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-06-23 15:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-02 12:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-07-02 14:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-07-09 17:36 ` Tom Lane <[email protected]>
2025-07-09 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-07-09 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 2 replies; 194+ messages in thread
From: Tom Lane @ 2025-07-09 17:36 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Ivan Kush <[email protected]>; pgsql-hackers; [email protected]
Jacob Champion <[email protected]> writes:
> On Wed, Jul 2, 2025 at 5:45 AM Ivan Kush <[email protected]> wrote:
>> Why don't we set LIBS in the configure in "checking for curl_multi_init"
>> using LIBCURL_LIBS or LIBCURL_LDFLAGS?
>> [...]
>> Without these LIBCURL... variables we will check a system libcurl, not
>> our local.
> Ah, that's definitely a bug.
I just ran into a vaguely-related failure: on RHEL8, building
with --with-libcurl leads to failures during check-world:
../../../../src/interfaces/libpq/libpq.so: undefined reference to `dlopen'
../../../../src/interfaces/libpq/libpq.so: undefined reference to `dlclose'
../../../../src/interfaces/libpq/libpq.so: undefined reference to `dlerror'
../../../../src/interfaces/libpq/libpq.so: undefined reference to `dlsym'
collect2: error: ld returned 1 exit status
Per "man dlopen", you have to link with libdl to use these functions
on this platform. (Curiously, although RHEL9 still says that in the
documentation, it doesn't seem to actually need -ldl.) I was able
to resolve this by adding -ldl in libpq's Makefile:
-SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
+SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl -ldl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
It doesn't look like the Meson support needs such explicit tracking of
required libraries, but perhaps I'm missing something? I'm not able
to test that directly for lack of a usable ninja version on this
platform.
Apologies for not noticing this sooner. I don't think I'd tried
--with-libcurl since the changes to split out libpq-oauth.
regards, tom lane
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-06-12 19:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-20 10:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-06-23 15:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-02 12:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-07-02 14:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 17:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
@ 2025-07-09 17:46 ` Andres Freund <[email protected]>
1 sibling, 0 replies; 194+ messages in thread
From: Andres Freund @ 2025-07-09 17:46 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Jacob Champion <[email protected]>; Ivan Kush <[email protected]>; pgsql-hackers; [email protected]
Hi,
On 2025-07-09 13:36:26 -0400, Tom Lane wrote:
> It doesn't look like the Meson support needs such explicit tracking of
> required libraries, but perhaps I'm missing something?
It should be fine, -ldl is added to "os_deps" if needed, and os_deps is used
for all code in pg.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-06-12 19:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-20 10:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-06-23 15:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-02 12:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-07-02 14:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 17:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
@ 2025-07-09 17:59 ` Jacob Champion <[email protected]>
2025-07-09 18:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
1 sibling, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-07-09 17:59 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Ivan Kush <[email protected]>; pgsql-hackers; [email protected]
On Wed, Jul 9, 2025 at 10:36 AM Tom Lane <[email protected]> wrote:
> Per "man dlopen", you have to link with libdl to use these functions
> on this platform. (Curiously, although RHEL9 still says that in the
> documentation, it doesn't seem to actually need -ldl.) I was able
> to resolve this by adding -ldl in libpq's Makefile:
>
> -SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
> +SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl -ldl -lm, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
Hmm, okay. That analysis and fix look good to me. (It looks like none
of the RHEL animals are testing with Curl yet, and locally I was using
Rocky 9...)
I'll work up a patch to send through the CI. I can't currently test
RHEL8 easily -- Rocky 8 is incompatible with my Macbook? -- which I
will need to rectify eventually, but I can't this week.
Thanks!
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-06-12 19:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-20 10:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-06-23 15:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-02 12:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-07-02 14:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 17:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-07-09 18:13 ` Tom Lane <[email protected]>
2025-07-09 18:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Tom Lane @ 2025-07-09 18:13 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Ivan Kush <[email protected]>; pgsql-hackers; [email protected]
Jacob Champion <[email protected]> writes:
> I'll work up a patch to send through the CI. I can't currently test
> RHEL8 easily -- Rocky 8 is incompatible with my Macbook? -- which I
> will need to rectify eventually, but I can't this week.
No need, I already tested locally. Will push shortly.
regards, tom lane
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-06-12 19:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-20 10:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-06-23 15:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-02 12:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-07-02 14:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 17:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 18:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
@ 2025-07-09 18:39 ` Jacob Champion <[email protected]>
2025-07-09 19:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-07-09 18:39 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Ivan Kush <[email protected]>; pgsql-hackers; [email protected]
On Wed, Jul 9, 2025 at 11:13 AM Tom Lane <[email protected]> wrote:
> Jacob Champion <[email protected]> writes:
> > I'll work up a patch to send through the CI. I can't currently test
> > RHEL8 easily -- Rocky 8 is incompatible with my Macbook? -- which I
> > will need to rectify eventually, but I can't this week.
>
> No need, I already tested locally. Will push shortly.
Thank you very much!
Here is a draft patch for Ivan's reported issue; I still need to put
it through its paces with some more unusual setups, but I want to get
cfbot on it.
--Jacob
Attachments:
[application/octet-stream] WIP-oauth-run-Autoconf-tests-with-correct-compiler-f.patch (2.6K, ../../CAOYmi+ne9_EvK7Z-y78z_6VXnYgXoRbiumMQeWCcy8vHf5t-cw@mail.gmail.com/2-WIP-oauth-run-Autoconf-tests-with-correct-compiler-f.patch)
download | inline diff:
From 2186e74f79a1dea452f1e25b70e1e7bfdde72d8f Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Wed, 9 Jul 2025 11:33:30 -0700
Subject: [PATCH] WIP: oauth: run Autoconf tests with correct compiler flags
Reported-by: Ivan Kush <[email protected]>
Discussion: https://postgr.es/m/8a611028-51a1-408c-b592-832e2e6e1fc9%40tantorlabs.com
Backpatch-through: 18
---
config/programs.m4 | 15 +++++++++------
configure | 15 +++++++++------
2 files changed, 18 insertions(+), 12 deletions(-)
diff --git a/config/programs.m4 b/config/programs.m4
index 0ad1e58b48d..b667aec4458 100644
--- a/config/programs.m4
+++ b/config/programs.m4
@@ -284,6 +284,15 @@ AC_DEFUN([PGAC_CHECK_STRIP],
AC_DEFUN([PGAC_CHECK_LIBCURL],
[
+ # libcurl compiler/linker flags are kept separate from the global flags, so
+ # they have to be added back temporarily for the following tests.
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+
AC_CHECK_HEADER(curl/curl.h, [],
[AC_MSG_ERROR([header file <curl/curl.h> is required for --with-libcurl])])
AC_CHECK_LIB(curl, curl_multi_init, [
@@ -292,12 +301,6 @@ AC_DEFUN([PGAC_CHECK_LIBCURL],
],
[AC_MSG_ERROR([library 'curl' does not provide curl_multi_init])])
- pgac_save_CPPFLAGS=$CPPFLAGS
- pgac_save_LDFLAGS=$LDFLAGS
- pgac_save_LIBS=$LIBS
-
- CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
- LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
LIBS="$LIBCURL_LDLIBS $LIBS"
# Check to see whether the current platform supports threadsafe Curl
diff --git a/configure b/configure
index cfaf3757dd7..cf54332a799 100755
--- a/configure
+++ b/configure
@@ -12717,6 +12717,15 @@ fi
if test "$with_libcurl" = yes ; then
+ # libcurl compiler/linker flags are kept separate from the global flags, so
+ # they have to be added back temporarily for the following tests.
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+
ac_fn_c_check_header_mongrel "$LINENO" "curl/curl.h" "ac_cv_header_curl_curl_h" "$ac_includes_default"
if test "x$ac_cv_header_curl_curl_h" = xyes; then :
@@ -12774,12 +12783,6 @@ else
fi
- pgac_save_CPPFLAGS=$CPPFLAGS
- pgac_save_LDFLAGS=$LDFLAGS
- pgac_save_LIBS=$LIBS
-
- CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
- LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
LIBS="$LIBCURL_LDLIBS $LIBS"
# Check to see whether the current platform supports threadsafe Curl
--
2.34.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-06-12 19:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-20 10:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-06-23 15:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-02 12:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-07-02 14:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 17:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 18:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 18:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-07-09 19:07 ` Tom Lane <[email protected]>
2025-07-09 19:28 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Tom Lane @ 2025-07-09 19:07 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Ivan Kush <[email protected]>; pgsql-hackers; [email protected]
Jacob Champion <[email protected]> writes:
> Here is a draft patch for Ivan's reported issue; I still need to put
> it through its paces with some more unusual setups, but I want to get
> cfbot on it.
I'm confused about why this moves up the temporary changes of
CPPFLAGS and LDFLAGS, but not LIBS? Maybe that's actually correct,
but it looks strange (and perhaps deserves a comment about why).
regards, tom lane
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-06-12 19:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-20 10:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-06-23 15:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-02 12:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-07-02 14:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 17:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 18:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 18:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 19:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
@ 2025-07-09 19:28 ` Jacob Champion <[email protected]>
2025-07-09 19:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-07-09 19:28 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Ivan Kush <[email protected]>; pgsql-hackers; [email protected]
On Wed, Jul 9, 2025 at 12:07 PM Tom Lane <[email protected]> wrote:
> Jacob Champion <[email protected]> writes:
> > Here is a draft patch for Ivan's reported issue; I still need to put
> > it through its paces with some more unusual setups, but I want to get
> > cfbot on it.
>
> I'm confused about why this moves up the temporary changes of
> CPPFLAGS and LDFLAGS, but not LIBS? Maybe that's actually correct,
> but it looks strange (and perhaps deserves a comment about why).
Yeah, that's fair. It's because LIBCURL_LDLIBS isn't set until that
AC_CHECK_LIB test is run, and the test needs LIBCURL_LDFLAGS to be in
force.
(Upthread, I was idly wondering if those AC_CHECKs should just be
removed -- after all, PKG_CHECK_MODULES just told us where Curl was --
but I'm nervous that this might make more niche use cases like
cross-compilation harder to use in practice?)
Does the attached help clarify?
Thanks,
--Jacob
Attachments:
[application/octet-stream] v2-0001-WIP-oauth-run-Autoconf-tests-with-correct-compile.patch (3.2K, ../../CAOYmi+kps=uRhx==UjmE2smqgjvigTRemxK5W=WfQ1MCA0ReiQ@mail.gmail.com/2-v2-0001-WIP-oauth-run-Autoconf-tests-with-correct-compile.patch)
download | inline diff:
From 59d6df6fca487622ddb64b219b721f9990ac5809 Mon Sep 17 00:00:00 2001
From: Jacob Champion <[email protected]>
Date: Wed, 9 Jul 2025 11:33:30 -0700
Subject: [PATCH v2] WIP: oauth: run Autoconf tests with correct compiler flags
Reported-by: Ivan Kush <[email protected]>
Reviewed-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/8a611028-51a1-408c-b592-832e2e6e1fc9%40tantorlabs.com
Backpatch-through: 18
---
config/programs.m4 | 18 ++++++++++++------
configure | 18 ++++++++++++------
2 files changed, 24 insertions(+), 12 deletions(-)
diff --git a/config/programs.m4 b/config/programs.m4
index 0ad1e58b48d..c73d9307ea8 100644
--- a/config/programs.m4
+++ b/config/programs.m4
@@ -284,20 +284,26 @@ AC_DEFUN([PGAC_CHECK_STRIP],
AC_DEFUN([PGAC_CHECK_LIBCURL],
[
+ # libcurl compiler/linker flags are kept separate from the global flags, so
+ # they have to be added back temporarily for the following tests.
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+
AC_CHECK_HEADER(curl/curl.h, [],
[AC_MSG_ERROR([header file <curl/curl.h> is required for --with-libcurl])])
+
+ # LIBCURL_LDLIBS is determined here. Like the compiler flags, it should not
+ # pollute the global LIBS setting.
AC_CHECK_LIB(curl, curl_multi_init, [
AC_DEFINE([HAVE_LIBCURL], [1], [Define to 1 if you have the `curl' library (-lcurl).])
AC_SUBST(LIBCURL_LDLIBS, -lcurl)
],
[AC_MSG_ERROR([library 'curl' does not provide curl_multi_init])])
- pgac_save_CPPFLAGS=$CPPFLAGS
- pgac_save_LDFLAGS=$LDFLAGS
- pgac_save_LIBS=$LIBS
-
- CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
- LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
LIBS="$LIBCURL_LDLIBS $LIBS"
# Check to see whether the current platform supports threadsafe Curl
diff --git a/configure b/configure
index cfaf3757dd7..6d7c22e153f 100755
--- a/configure
+++ b/configure
@@ -12717,6 +12717,15 @@ fi
if test "$with_libcurl" = yes ; then
+ # libcurl compiler/linker flags are kept separate from the global flags, so
+ # they have to be added back temporarily for the following tests.
+ pgac_save_CPPFLAGS=$CPPFLAGS
+ pgac_save_LDFLAGS=$LDFLAGS
+ pgac_save_LIBS=$LIBS
+
+ CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+ LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
+
ac_fn_c_check_header_mongrel "$LINENO" "curl/curl.h" "ac_cv_header_curl_curl_h" "$ac_includes_default"
if test "x$ac_cv_header_curl_curl_h" = xyes; then :
@@ -12725,6 +12734,9 @@ else
fi
+
+ # LIBCURL_LDLIBS is determined here. Like the compiler flags, it should not
+ # pollute the global LIBS setting.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_multi_init in -lcurl" >&5
$as_echo_n "checking for curl_multi_init in -lcurl... " >&6; }
if ${ac_cv_lib_curl_curl_multi_init+:} false; then :
@@ -12774,12 +12786,6 @@ else
fi
- pgac_save_CPPFLAGS=$CPPFLAGS
- pgac_save_LDFLAGS=$LDFLAGS
- pgac_save_LIBS=$LIBS
-
- CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
- LDFLAGS="$LIBCURL_LDFLAGS $LDFLAGS"
LIBS="$LIBCURL_LDLIBS $LIBS"
# Check to see whether the current platform supports threadsafe Curl
--
2.34.1
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-06-12 19:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-20 10:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-06-23 15:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-02 12:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-07-02 14:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 17:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 18:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 18:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 19:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 19:28 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-07-09 19:42 ` Tom Lane <[email protected]>
2025-07-09 23:54 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: Tom Lane @ 2025-07-09 19:42 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: Ivan Kush <[email protected]>; pgsql-hackers; [email protected]
Jacob Champion <[email protected]> writes:
> On Wed, Jul 9, 2025 at 12:07 PM Tom Lane <[email protected]> wrote:
>> I'm confused about why this moves up the temporary changes of
>> CPPFLAGS and LDFLAGS, but not LIBS? Maybe that's actually correct,
>> but it looks strange (and perhaps deserves a comment about why).
> Does the attached help clarify?
Yes, thanks.
> (Upthread, I was idly wondering if those AC_CHECKs should just be
> removed -- after all, PKG_CHECK_MODULES just told us where Curl was --
> but I'm nervous that this might make more niche use cases like
> cross-compilation harder to use in practice?)
Nah, let's keep them. We do document for at least some libraries
how to manually specify the include and link options without
depending on pkg-config. If someone tries that with libcurl,
it'd be good to have sanity checks on the results.
regards, tom lane
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-06-12 19:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-20 10:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-06-23 15:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-02 12:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-07-02 14:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 17:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 18:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 18:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 19:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 19:28 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 19:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
@ 2025-07-09 23:54 ` Jacob Champion <[email protected]>
2025-07-10 14:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER [email protected]
0 siblings, 1 reply; 194+ messages in thread
From: Jacob Champion @ 2025-07-09 23:54 UTC (permalink / raw)
To: Tom Lane <[email protected]>; Ivan Kush <[email protected]>; +Cc: pgsql-hackers; [email protected]
On Wed, Jul 9, 2025 at 12:42 PM Tom Lane <[email protected]> wrote:
> Nah, let's keep them. We do document for at least some libraries
> how to manually specify the include and link options without
> depending on pkg-config. If someone tries that with libcurl,
> it'd be good to have sanity checks on the results.
Sounds good, thanks for the review!
On Wed, Jul 9, 2025 at 11:39 AM Jacob Champion
<[email protected]> wrote:
> Here is a draft patch for Ivan's reported issue; I still need to put
> it through its paces with some more unusual setups, but I want to get
> cfbot on it.
On HEAD, Rocky 9 fails to build with a custom Curl PKG_CONFIG_PATH and
no libcurl-devel installed. With this patch, that build now succeeds,
and it still succeeds after libcurl-devel is reinstalled, with the
compiler tests continuing to use the custom libcurl and not the
system's.
So I'll give Ivan a little time in case he'd like to test/review
again, but otherwise I plan to push it this week.
Thanks,
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-06-12 19:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-20 10:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-06-23 15:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-02 12:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-07-02 14:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 17:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 18:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 18:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 19:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 19:28 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 19:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 23:54 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
@ 2025-07-10 14:41 ` [email protected]
2025-07-11 17:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
0 siblings, 1 reply; 194+ messages in thread
From: [email protected] @ 2025-07-10 14:41 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers; [email protected]
I agree with the patch. Works in my OSes
On 7/10/25 2:54 AM, Jacob Champion <[email protected]> wrote:
> On Wed, Jul 9, 2025 at 12:42 PM Tom Lane <[email protected]> wrote:
> > Nah, let's keep them. We do document for at least some libraries
> > how to manually specify the include and link options without
> > depending on pkg-config. If someone tries that with libcurl,
> > it'd be good to have sanity checks on the results.
>
> Sounds good, thanks for the review!
>
> On Wed, Jul 9, 2025 at 11:39 AM Jacob Champion
> <[email protected]> wrote:
> > Here is a draft patch for Ivan's reported issue; I still need to put
> > it through its paces with some more unusual setups, but I want to get
> > cfbot on it.
>
> On HEAD, Rocky 9 fails to build with a custom Curl PKG_CONFIG_PATH and
> no libcurl-devel installed. With this patch, that build now succeeds,
> and it still succeeds after libcurl-devel is reinstalled, with the
> compiler tests continuing to use the custom libcurl and not the
> system's.
>
> So I'll give Ivan a little time in case he'd like to test/review
> again, but otherwise I plan to push it this week.
>
> Thanks,
> --Jacob
>
^ permalink raw reply [nested|flat] 194+ messages in thread
* Re: [PoC] Federated Authn/z with OAUTHBEARER
2025-06-12 19:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-20 10:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-06-23 15:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-02 12:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-07-02 14:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 17:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 18:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 18:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 19:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 19:28 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 19:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 23:54 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-10 14:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER [email protected]
@ 2025-07-11 17:33 ` Jacob Champion <[email protected]>
0 siblings, 0 replies; 194+ messages in thread
From: Jacob Champion @ 2025-07-11 17:33 UTC (permalink / raw)
To: [email protected]; +Cc: Tom Lane <[email protected]>; pgsql-hackers; [email protected]
On Thu, Jul 10, 2025 at 7:41 AM <[email protected]> wrote:
> I agree with the patch. Works in my OSes
Thanks Ivan! Committed.
--Jacob
^ permalink raw reply [nested|flat] 194+ messages in thread
end of thread, other threads:[~2025-08-08 16:23 UTC | newest]
Thread overview: 194+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-03-05 15:04 [PATCH v12 2/5] match a77315fdf2a197a925e670be2d8b376c4ac02efc Alvaro Herrera <[email protected]>
2020-04-02 00:57 [PATCH 4/8] BRIN bloom indexes Tomas Vondra <[email protected]>
2021-06-08 16:37 [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-18 04:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Michael Paquier <[email protected]>
2021-06-22 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-06-23 06:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Michael Paquier <[email protected]>
2021-06-18 08:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Heikki Linnakangas <[email protected]>
2021-06-22 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 18:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-25 22:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Zhihong Yu <[email protected]>
2021-08-25 23:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Zhihong Yu <[email protected]>
2021-08-26 16:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2021-08-26 16:20 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Zhihong Yu <[email protected]>
2021-08-27 02:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Michael Paquier <[email protected]>
2021-08-31 20:48 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-03-26 00:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-23 22:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-27 01:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-27 21:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-30 14:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-30 20:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-10-03 18:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-23 09:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-23 20:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-11-24 03:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-11-24 08:20 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-11-29 21:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-12-06 00:15 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-12-07 19:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-12-07 23:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-12-08 04:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-12-09 00:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-12-13 05:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-12-16 23:18 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-11-29 21:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-20 05:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER mahendrakar s <[email protected]>
2022-09-20 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-21 16:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-21 22:10 ` RE: [EXTERNAL] Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovskiy <[email protected]>
2022-09-21 22:31 ` Re: [EXTERNAL] Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2022-09-22 04:55 ` Re: [EXTERNAL] Re: [PoC] Federated Authn/z with OAUTHBEARER Andrey Chudnovsky <[email protected]>
2022-09-22 21:53 ` Re: [EXTERNAL] Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 00:35 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 05:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 17:30 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-07 21:51 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-07 23:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-17 11:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nazir Bilal Yavuz <[email protected]>
2025-03-17 15:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-19 04:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-04-03 18:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-03 19:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 04:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 04:34 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-19 13:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-19 04:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 13:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-19 13:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-19 20:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-19 21:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 22:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-19 22:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-19 22:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-19 22:28 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Thomas Munro <[email protected]>
2025-03-19 23:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-19 23:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 20:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-20 20:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-03-20 21:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-03-20 23:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-03-21 12:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andrew Dunstan <[email protected]>
2025-03-21 18:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-03-26 19:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-03-31 14:06 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-01 22:40 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-05 00:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-06 20:48 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 14:21 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 14:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 16:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-07 17:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 18:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 21:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-07 21:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-07 22:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 02:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 14:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 15:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 15:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 16:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 16:43 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 17:00 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 17:03 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 16:48 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 16:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 16:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 17:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 18:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-04-08 18:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 19:22 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 17:04 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 17:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 17:15 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Bruce Momjian <[email protected]>
2025-04-08 17:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 17:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 19:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-08 21:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 02:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 08:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 12:50 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-09 17:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-09 23:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-11 00:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-14 16:12 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-14 18:27 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Wolfgang Walther <[email protected]>
2025-04-14 19:14 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-14 20:17 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 00:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 16:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 12:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Peter Eisentraut <[email protected]>
2025-04-15 15:34 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-15 18:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 17:53 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-15 18:57 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-15 19:44 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 00:47 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-18 17:01 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-21 23:19 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-22 23:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 15:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 16:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-23 16:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-04-23 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-24 17:02 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-25 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-29 00:10 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-04-30 18:09 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-05-01 17:38 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 19:24 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-01 20:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:11 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 15:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 15:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-30 16:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-06-30 17:01 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-06-30 18:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-30 22:52 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-06-30 23:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-07-18 17:26 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-07-18 22:29 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Daniel Gustafsson <[email protected]>
2025-07-18 23:31 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-08-05 18:54 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-08-08 16:23 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:35 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-05-02 17:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Nathan Bossart <[email protected]>
2025-05-02 18:25 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-05-02 18:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 18:52 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-05-02 18:56 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 19:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-02 19:05 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-05-04 12:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Wolfgang Walther <[email protected]>
2025-05-06 17:49 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-05-03 14:54 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Christoph Berg <[email protected]>
2025-05-05 12:37 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Devrim Gündüz <[email protected]>
2025-04-15 19:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-12 19:58 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-06-20 10:08 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-06-23 15:32 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-02 12:45 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Ivan Kush <[email protected]>
2025-07-02 14:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 17:36 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 17:46 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Andres Freund <[email protected]>
2025-07-09 17:59 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 18:13 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 18:39 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 19:07 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 19:28 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-09 19:42 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Tom Lane <[email protected]>
2025-07-09 23:54 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
2025-07-10 14:41 ` Re: [PoC] Federated Authn/z with OAUTHBEARER [email protected]
2025-07-11 17:33 ` Re: [PoC] Federated Authn/z with OAUTHBEARER Jacob Champion <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox