porsager / porsager/postgres

Support GSSAPI Authentication/encryption

Open
#1,095 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
8.7k
Forks
374
Avg merge
11d 16h
Merged PRs (30d)
1

Description

Postgres supports authentication and optionally encryption using GSSAPI, allowing authentication using Kerberos, SSPI and the like. I'm currently trying to create a PR using https://www.npmjs.com/package/kerberos which is used by mongodb-js, and poking around it seems like it should be possible to implement, though I've run into a bit of a roadblock when it comes to sending raw bytes as required by the GSSAPI authentication process.

WIP code diff
Index: src/connection.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/connection.js b/src/connection.js
--- a/src/connection.js	(revision 32feb259a3c9abffab761bd1758b3168d9e0cebc)
+++ b/src/connection.js	(date 1752946656209)
@@ -11,6 +11,8 @@
 import { Query, CLOSE } from './query.js'
 import b from './bytes.js'
 
+import {Kerberos} from "kerberos" // todo: make optional
+
 export default Connection
 
 let uid = 1
@@ -18,6 +20,7 @@
 const Sync = b().S().end()
     , Flush = b().H().end()
     , SSLRequest = b().i32(8).i32(80877103).end(8)
+    , GSSRequest = b().i32(8).i32(80877104).end(8)
     , ExecuteUnnamed = Buffer.concat([b().E().str(b.N).i32(0).end(), Sync])
     , DescribeUnnamed = b().D().str('S').str(b.N).end()
     , noop = () => { /* noop */ }
@@ -104,6 +107,8 @@
     , nonce = null
     , query = null
     , final = null
+    , kerberosClient = null
+    , kerberosEncClient = null
 
   const connection = {
     queue: queues.closed,
@@ -327,7 +332,6 @@
     terminated = false
     backendParameters = {}
     socket || (socket = await createSocket())
-
     if (!socket)
       return
 
@@ -353,7 +357,57 @@
     setTimeout(connect, closedDate ? closedDate + delay - performance.now() : 0)
   }
 
-  function connected() {
+  async function connectedGss() {
+    let gssToken
+    try {
+      kerberosEncClient = await Kerberos.initializeClient("postgres@" + host)
+      gssToken = await kerberosEncClient.step('')
+    } catch (e) {
+      return false
+    }
+
+    write(GSSRequest)
+    const canGSS = await new Promise(r => socket.once('data', x => r(x[0] === 71))) // G
+    if (!canGSS) {
+      return false
+    }
+    try {
+      while (!kerberosEncClient.contextComplete) {
+        let buffer = Buffer.from(gssToken, 'base64')
+        write(b().raw(buffer).end())
+        let ret = await new Promise(r => socket.once('data', x => r(x)))
+
+      }
+    } catch (e) {
+      console.log(e)
+      return false
+    }
+    const s = StartupMessage()
+    write(s)
+
+    statements = {}
+    needsTypes = options.fetch_types
+    statementId = Math.random().toString(36).slice(2)
+    statementCount = 1
+    lifeTimer.start()
+
+    socket.on('data', data)
+
+    keep_alive && socket.setKeepAlive && socket.setKeepAlive(true, 1000 * keep_alive)
+  }
+
+  async function connected() {
+    if (Kerberos !== null) {
+      try {
+        await connectedGss()
+      } catch (e) {
+        error(e)
+      }
+    }
     try {
       statements = {}
       needsTypes = options.fetch_types
@@ -654,6 +708,8 @@
     (
       type === 3 ? AuthenticationCleartextPassword :
       type === 5 ? AuthenticationMD5Password :
+      Kerberos !== null && type === 7 ? AuthenticationGss :
+      Kerberos !== null && type === 8 ? AuthenticationGssContinue :
       type === 10 ? SASL :
       type === 11 ? SASLContinue :
       type === 12 ? SASLFinal :
@@ -684,6 +740,22 @@
     )
   }
 
+  async function AuthenticationGss() {
+    console.assert(Kerberos !== null)
+
+    kerberosClient = await Kerberos.initializeClient("postgres/" + host) //todo
+    b().p().str('GSSAPI' + b.N)
+    const i = b.i
+    write(b.inc(1).str('p=' + kerberosClient.response).i32(b.i - i - 1, i).end())
+  }
+
+  async function AuthenticationGssContinue(x) {
+    console.assert(Kerberos !== null)
+    console.assert(kerberosClient !== null)
+
+
+  }
+
   async function SASL() {
     nonce = (await crypto.randomBytes(18)).toString('base64')
     b().p().str('SCRAM-SHA-256' + b.N)

The above code fails at write(b().raw(buffer).end()), and I'm not sure if returning data using ret = await new Promise(r => socket.once('data', x => r(x))) is correct.

ref:

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/connection.js, especially connectedGss, AuthenticationGss, AuthenticationGssContinue, and the write call using b().raw(buffer). Review the linked PostgreSQL GSSAPI protocol references and the kerberos package behavior. Done means the client can complete the GSSAPI authentication and encryption flow, including the raw-byte exchange.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, nodejs, postgresql
Domain
authentication, backend, databases, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.