18

I'm trying to write a simple script that requests some data from a tool on an internal network. Here is the code:

#!/usr/bin/node

var https = require('https');
var fs = require('fs');

var options = {
  host: '<link>',
  port: 443,
  path: '<path>',
  auth: 'username:password',
  ca: [fs.readFileSync('../.cert/newca.crt')]
};

https.get(options, function(res) {
  console.log("Got response: " + res.statusCode);
  res.on('data', function (d) {
    console.log('BODY: ' + d);
  });
}).on('error', function(e) {
  console.log("Got error: " + e.message);
});

Now the question is, how can I use a Kerberos ticket to authenticate rather than supplying my credentials in auth: in plain text?

mart1n
  • 5,969
  • 5
  • 46
  • 83

6 Answers6

8

from http://docs.oracle.com/cd/E24191_01/common/tutorials/authn_kerberos_service.html

Client Token Location for Message-Level Standards: The Kerberos Service ticket can either be sent in the Authorization HTTP header or inside the message itself, for example, inside a element. Alternatively, it may be contained within a message attribute. Select one of the following options:

so instead of your username:password you provide the ticket

alternatively you can as stated below that information put it in the message body or as a message attribute

var request = https.request(options, function(resource) {
  var chunks = [];
   resource.on('data', function (chunk) {
     chunks.push(chunk);
   });
   resource.on('end', function () {
     var data = chunks.join('');
     console.log(data);
   });
}

request.on('error',...)
request.send('<body-with-ticket>');
request.end();

EDIT:

the "" part was my example of where to use the ticket, put it in a multiytype body and send that, alternatively use the WWW-Authenticate header to send it

eg. add it to the options on https.request

options = {
    host: 'hostname',
    port: 443, 
    'WWW-Authenticate': 'Negotiate ' + ticketdata
};

google has some nice diagrams on how it works: https://developers.google.com/search-appliance/kb/secure/kerberos-diagram

Paul Scheltema
  • 1,993
  • 15
  • 25
  • I appreciate your solution, even I know that we can set custom headers of the HTTP request and send the kerberos credentials (precisely we need to set 'WWW-Authenticate' to the kerberos ticket. But the **problem** is to get the content from KRB5CCNAME file (kerberos ticket), this file content is encrypted. – Sriharsha Dec 19 '13 at 06:49
  • Basically I need utility to read the KRB5CCNAME file and set the auth header. I would write this utility myself, but have very little knowledge on Kerberos, I would appreciate if you could throw some light on! – Sriharsha Dec 19 '13 at 06:51
  • What do you mean by "you provide the ticket". Do I just point to the ticket file like `auth: 'ticket',`. Could you please post the code? – mart1n Feb 24 '14 at 15:30
  • I must be slow today, sorry; what is `ticketdata`? I understand from the Google link that it's the representation of the Kerberos ticket in a hash (correct?), but where do I get that from? – mart1n Feb 24 '14 at 16:24
6

I have gotten this to work using "kerberos" module, version 0.0.12. I have created a Gist with the working example:

https://gist.github.com/dmansfield/c75817dcacc2393da0a7

Basically, you use three methods to obtain the "Authorization" header data:

  • authGSSClientInit, which requires the service name, e.g. HTTP@somehost.com
  • authGSSClientStep, which requires the existence of a credentials cache (on Linux you get this by doing "kinit" and can verify it with "klist"), and actually gives you back the base64 stuff you need (without the leading "Negotiate " string)
  • authGSSClientClean, which frees all allocated memory structures

Then you create an "Authorization" header (NOT WWW-Authenticate as shown above, which is what the server sends back) and it should work.

Note also: typically, web browsers request a resource, get a 401 back with a WWW-Authenticate: Negotiate header in the response, then re-request the resource with the ticket data supplied in the "Authorization" header. This two-step dance happens for every resource. I'm not sure if it means anything or not.

dmansfield
  • 1,108
  • 10
  • 22
4

In Paul Scheltema's answer, you need to get ticketdata from depth of operating system. You (or a module on behalf of you) must use GSS-API to have ticketdata generated by Active Directory for you.

Such mechanism is present in Chrome, but it seems that it's not included in Node.js (only the javascript engine from Chrome), so you may need to add a module, for example:

To install/compile such module you may need to have Visual Studio.


To set up environment, - On all computers you must have tcp and udp enabled on ports 88 (Kerberos) and 53 (dns). - On Windows Server Active Directory must be running (ldap, dns, kdc) - On the page https://www.npmjs.org/package/passport-kerberos they use term REALM. It's a name of domain, written uppercase.

greenmarker
  • 1,599
  • 1
  • 21
  • 29
  • I understand this can be done with a module such as passport-kerberos but how does that module do it then? Can't I circumvent the module and simply implement the same logic, keeping the simple script without any dependencies? – mart1n Mar 04 '14 at 10:28
  • The logic: Use GSS-API/SSPI functions to form a request to a domain controller. This request must contain user authentication info (got from Kerberos cache, highly protected by operating system), and URL translated to a userPrincipal form (using dns). If Active Directory has such userPrincipal in LDAP and authentication data are correct, it generates Kerberos ticket. This ticket should be wrapped into SPNEGO, Base64encoded and this is the 'ticketdata' in Paul Scheltema's answer. Usually this is done by web-browser for us. When it's not present, in node.js, a module is needed. – greenmarker Mar 04 '14 at 12:38
  • Ok, I guess there really isn't a way to do this quickly and easy and there will have to be some module dependencies. Thanks for your answer! – mart1n Mar 06 '14 at 09:18
  • Having looked into Kerberos a few months ago, there will definitely be some module dependencies unless you want to dig into the source code of `passport-kerberos` (or even the Chromium source code) and re-implement it. I'll probably do this myself at some point (provided this particular project rises to the top of the pile), as the only Kerberos modules I saw in npm just allowed you to verify credentials against active directory (basically, allowing you to use Kerberos in a server context), they didn't let you actually negotiate with a remote server as a client – Jason Mar 06 '14 at 14:25
2

If you are on Windows, you can use the SSPI interface. It is exposed on Node with the project node-expose-sspi.

The SSPI interface allows you to write any client or server using SSO (NTLM and Kerberos).

https://github.com/jlguenego/node-expose-sspi

Note: I am the author of node-expose-sspi.

jlguenego
  • 1,192
  • 15
  • 23
2

2020 UPD:

The newer version of kerberos npm package has much fewer methods. But I made it work:

const kerberos = require('kerberos').Kerberos;
const fetch = require('node-fetch');

(async () => {
    const client = await kerberos.initializeClient("HTTP@site.internal.net", {
        mechOID: kerberos.GSS_MECH_OID_SPNEGO,
    })

    const ticket = await client.step("")

    const resp = await fetch("https://site.internal.net/api/v1/hello", {
        headers: {
            'Authorization': 'Negotiate ' + ticket
        }
    })

    console.log(await resp.json())
})();

Works perfectly at Windows and Linux, should work at macOS too.

  • idk why you got downvoted, this worked perfectly for me when I changed the mechOID to `GSS_MECH_OID_KRB5`. I'm on macOS Catalina 10.15.7 (19H15) – SgtPooki Dec 09 '20 at 23:27
  • Late to the party but one question here. while passing parameters how do we pass password ? I am trying to make this work in a rest env. Very ver confused as to how the token dispatching works. Some words of advise or any notes ? – Deepankar May 27 '21 at 11:26
  • 2
    This is SSO. OS considers you authorized, so it just provides SPNEGO token after system call (which this lib does). – Savely Krasovsky May 28 '21 at 00:47
  • what kind of client app is anticipated in this scenario?AFAIK we cant import it to react or angular or vue projects? – desperado06 Oct 26 '22 at 08:25
  • 1
    @desperado06 this question is about Node.js. You have to get SPNEGO token somehow and send it to protected site. If you write frontend you don't need implement Kerberos client authentication, it's entirely at browser side. Browser will obtain SPNEGO token from OS and send it with your frontend requests. – Savely Krasovsky Nov 07 '22 at 13:09
0

On Windows here. This answer seems to be correct, just might need couple of adjustments.

const client = await kerberos.initializeClient("HTTP/site.internal.net@DOMAIN.HERE", {
    mechOID: kerberos.GSS_MECH_OID_SPNEGO,
})

Works both with GSS_MECH_OID_KRB5 and GSS_MECH_OID_SPNEGO. Use klist from command line and you'll get an overview of existing kerberos tickets, their sites (e.g. site.internal.net) and domains (e.g. DOMAIN.HERE).

And kerberos flow is that you first get 401 with WWW-Authenticate: Negotiate header. Then you're supposed to issue second request to the url that responded with 401 (can be same url, can be redirect url) with 'Authorization': 'Negotiate ' + ticket as shown in example. Don't forget to reuse any Cookies from first request to maintain session.

Complete request might look something like this:

const kerberos = require('kerberos').Kerberos;
const fetch = require('node-fetch');

(async () => {
    const client = await kerberos.initializeClient("HTTP/site.internal.net@DOMAIN.HERE", {
        mechOID: kerberos.GSS_MECH_OID_SPNEGO, // or GSS_MECH_OID_KRB5
    })

    const ticket = await client.step("")

    // 401 here, get cookies
    const resp1 = await fetch("https://site.internal.net/api/v1/hello")

    // ... code to handle cookies ...

    const resp2 = await fetch("https://site.internal.net/api/v1/hello", {
        headers: {
            'Authorization': 'Negotiate ' + ticket,
            'Cookie': // ... add cookies from first request ..
        }
    })

    console.log(await resp2.json())
})();
kazagz
  • 1