Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

- Enable passing token data for Token Authentication #337

Merged
merged 7 commits into from
May 18, 2024

Conversation

sudarshan12s
Copy link
Collaborator

@sudarshan12s sudarshan12s commented May 8, 2024

Description

For Token Based Authentication, the connect string can be alias as mentioned here. This PR adds support to programatically supply token information; token and privateKey data. For IAM , token and privateKey needs to be provided. For OAUTH, token is enough.

In case of pool config(standAloneConnection=0), callback needs to be provided along with context, which is invoked when the token information to connect database has expired passing the context as input argument.
The callback is supposed to fill the refresh token. The C memory allocated for token data passed to odpi-c is freed on next invocation of callback. Hence the context passed by godror to odpi-c has token, privateKey returned by previous invocation of callback. The cgo.handle is used to pass the go accessTokenCB value after wrapping it into void * .

The context argument for the callback is taken as context.Context type to just allow variable key/value pairs. Should this be interface{} instead?

Note:

  • Token Authentication is only supported with tcps(tls)

  • If privateKey is not given but only token is given, OAUTH based Authentication is internally tried.

  • For pool config (standAloneConnection=0), token based authentication, both homogeneous and externalAuth fields in the dpiPoolCreateParams structure must be set to 1. Incase of StandAlone (standAloneConnection=1) externalAuth must be set to 1.

  • token and privateKeys are not considered as part of pool key and hence single pool will be created for multiple sql.open calls with different token/privateKeys

  • privateKey format should not have the comments ( -----BEGIN OPENSSH PRIVATE KEY-----
    , -----END OPENSSH PRIVATE KEY-----
    ) It has to be removed and passed to the driver.

  • ConnectionParams.StringWithPassword only enables printing token/pvtkey

  • example of token and privatekey passed in sql.open for standalone=1

db, err := sql.open("godror", `user= password= connectString=token_auth_pgm 
        enableEvents=1 externalAuth=1  standaloneConnection=1 privateKey="MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC0XRkacj1w6PD7dAR/wCdUILJGDgHfQ92rDWZq7fS7TM+8HsZZu2hL9c1jFsMMVbK1XGk5+70+bZk+WN7Z7/p7SLnVKdwio4Eauxo7jYuwdf/fiDRjE5T2IcgA3Npl1mJb7qIBIbb6FLuSR3lsXH1UeVviGC7KSrlHulP799tQx11J9iamRhM5vSyjuxSOtlL5r7eiH93ZTU4wrALU48QAjkW/xRzP5ws+MAChLUGgE3hAuhY6YF7AIhh6I3Vm1BlMefTbd+GI1pz9Mm2mIC9zw7zh7SYoQectiU2ixCUAzmQgyniY96hKNOkgcCO8zzkVI+oE9aStDqmEhay8BSpdAgMBAAECggEAV4v0+mxHPH4lMrjO0j+wF1rDjdOQvxGPEE8xzmdwalXPY63Ac5/O8Uf/iMBsrpkOZI/Vl8qEwj+qqaOzkC+28o6LfVeTPdEFgrcc9ZkwG9g6+gKAAzNnE82z6g9JhzL3jU4YntoBmgPkRA2jd6CxSQabPfSlCZYZtyJPT7ewYdWC7THLZeaYQsVLzD8R1yPY/oVpxsHf/L00t3UHdWxaD8eu6yzGj4ms0ir0l2epIcrkZ52/+4CEF4IjidynSG+b3aUWHtHB0rahteTTiJ4Y9dObiFJvsQpOjmpNdbBhiUFrCt5Z9QwRUU+kLKSOuN8TFrK8R2/me/LIN4l+WlNawQKBgQDW7U1/R0ovzNzlLOVTmSduIeOrAxz9iax6ztEPPKzHkDbsuiQ6ne8JT73GRjznvxc4lA/oFUzvlvGcLH+LHQ2NQpR5p8UTGo06Q+yOkDBpHzT2furUHKV8jf4MBIIeagzeY9Pp0Wgs94swEJ+M6JeJt7o6yU7vRGEcqpgvflcwbQKBgQDW1OITghZa/AmEpzvgT4pGB69E2Q2GcHf5CAPC4RYyUOS8u2aGZwxjmPeV5JsfmFDiDa8+0TmEtkTQMNQeZS2kOj0CEj7w3tX03Na1Gy/DmEHMJCyYFEUpDd5jaOuPrnbfAdCK58x2hgXMgJhVgPOMAYDen5/N8nED7ZL0lEgLsQKBgQCyUJ18XMQzFk+qn+3/xtBM8jb2OhYCUAfWt+IBN0DOLVs0WlcWftPEMPFtH/cF+qekXEs6LPnwyZXZEZ4b59XHfha7PDMoX14Omi4YNY7EmIyTeccQhlfSF+hPRipCW5AjrkUx93fr3tEO5qvI92xKaTFL9prTrjK32t16geKKnQKBgQCZNG4ZfW8V6aGcEWs492Bjur06exQTKQfV9+o+wyiCL4BAO+DMvpZuPLtsEQCzUnt0ClBMmwbK5vVCB2BuYLdg5At3+60ZN8Ebg5Y2x7GTanSZ8b4/ok0EDxjmif9bkw7A0Nl5Bf+hEsj140s/xtton/XYTbu4Mkp4g6eGdmy+sQKBgQDIK7cDWl07V9JvnS7i2F030F64tUx+f56eOx8dDGuMritiBtyoTJe2WyG9i93WNoF/jJ93zQhxigZFhvYcJZPpZB5QcbyCo/v2llZdomdGeKh59d/mYIPeFKHYjB2VG+YvixcFprT+37nJ5PsMCelSb/Z8qgiCFPVFWrvsQ/ShTw=="  token=eyJraWQiOiJzY29wZWRfc2tfZGJfdG9rZW5faWFkX3lvMmwiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJvY2lkMS51c2VyLm9jMS4uYWFhYWFhYWE3dHdnNjV4b2d2aTJrdGEzaXF0dWd1c2lyc3FrYXQ1YzZyd3l5dXJvaGZ4ZXRsMzVycGpxIiwiaXNzIjoiYXV0aFNlcnZpY2Uub3JhY2xlLmNvbSIsInB0eXBlIjoidXNlciIsInNlc3NfZXhwIjoiV2VkLCAwOCBNYXkgMjAyNCAxMTo1ODowMCBVVEMiLCJkYlVzZXJOYW1lIjoibXlpYW11c2VyIiwidXNlck5hbWUiOiJNeUlBTVVzZXIiLCJhdWQiOiJ1cm46b3JhY2xlOmRiOm9jaWQxLnRlbmFuY3kub2MxLi5hYWFhYWFhYXhwbmdieTc0bnR1ZGt5NHNmcG1idGthdHZkN294eW15Y2dsc3Rqdml0cmptdms1eWJydnEiLCJwc3R5cGUiOiJuYXR2IiwidHR5cGUiOiJzY29wZWRfYWNjZXNzIiwic2NvcGUiOiJ1cm46b3JhY2xlOmRiOjppZDo6KiIsImV4cCI6MTcxNTE1MTQ4MCwiaWF0IjoxNzE1MTQ3ODgwLCJqdGkiOiI5MTA5YjlmMS1jODVjLTRkY2MtYWUxNy01MjZlYjg3OTY5MjciLCJ0ZW5hbnQiOiJvY2lkMS50ZW5hbmN5Lm9jMS4uYWFhYWFhYWF4cG5nYnk3NG50dWRreTRzZnBtYnRrYXR2ZDdveHlteWNnbHN0anZpdHJqbXZrNXlicnZxIiwiandrIjoie1wia2lkXCI6XCJvY2lkMS50ZW5hbmN5Lm9jMS4uYWFhYWFhYWF4cG5nYnk3NG50dWRreTRzZnBtYnRrYXR2ZDdveHlteWNnbHN0anZpdHJqbXZrNXlicnZxXCIsXCJuXCI6XCJ0RjBaR25JOWNPanctM1FFZjhBblZDQ3lSZzRCMzBQZHF3MW1hdTMwdTB6UHZCN0dXYnRvU19YTll4YkRERld5dFZ4cE9mdTlQbTJaUGxqZTJlXzZlMGk1MVNuY0lxT0JHcnNhTzQyTHNIWF8zNGcwWXhPVTlpSElBTnphWmRaaVctNmlBU0cyLWhTN2trZDViRng5VkhsYjRoZ3V5a3E1UjdwVC1fZmJVTWRkU2ZZbXBrWVRPYjBzbzdzVWpyWlMtYS0zb2hfZDJVMU9NS3dDMU9QRUFJNUZ2OFVjei1jTFBqQUFvUzFCb0JONFFMb1dPbUJld0NJWWVpTjFadFFaVEhuMDIzZmhpTmFjX1RKdHBpQXZjOE84NGUwbUtFSG5MWWxOb3NRbEFNNWtJTXA0bVBlb1NqVHBJSEFqdk04NUZTUHFCUFdrclE2cGhJV3N2QVVxWFFcIixcImVcIjpcIkFRQUJcIixcImt0eVwiOlwiUlNBXCIsXCJhbGdcIjpcIlJTMjU2XCIsXCJ1c2VcIjpcInNpZ1wifSJ9.R3aB-0A6LpMvK5y0Zjbpee1o5j6ScmxcV4udvWh1DSxyk_xn1Bae5Ll4RnOWZNTl-4barnr4GX8eAmZtGfVBuRkerjAlcpgT9ENEh7oYqT0kFyX9OVAYhYglaQ82JQkIgNL6sk7yrCzPt7p_In24pBbJKKiAI3l8eBLsnJprPX0jq4vZTqK4YiKJpUF3sLsIhzvAPhqxXNuazjAj2VfPsMY5HtOzwEXvxhvh492HZ_WioeXU90fZcqLVAcwyBpFdZ0TNpjFVitjgUboj0lur7oLY6KYMZi5D_cGUZDHa7ciyY52G-ajuIACK5wwsnpkxzZa2-kObnxxc_vJz9nQw_Q`

Testing:

IAM and OAUTH testing for standalone and pool is done.
Token callback triggering more than once is tested manually by code change

* - Token Auth changes

* - copyright correction

* - modify test

* - modify tests

* - test modifications

* - test change

* - minor changes

* - Dont print token and privatekey in string conversion

* - print token, pvtonly only if stringwithpasswd selected

* -minor changes

* - correct comments

* - renaming and free the token mem from handler

* - minor changes

* - oauth testing fixes

* - minor changes
@sudarshan12s sudarshan12s requested a review from tgulacsi May 8, 2024 12:59
@sudarshan12s sudarshan12s changed the title - Token Auth changes (#2) - Token Auth changes May 8, 2024
@sudarshan12s sudarshan12s changed the title - Token Auth changes - Enable passing token data for Token Authentication May 8, 2024
Copy link
Contributor

@tgulacsi tgulacsi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use runtime/cgo.Handle for passing values between C and Go,
and try to name same variables the same.

drv.go Outdated
@@ -167,6 +167,18 @@ func ParseDSN(dataSourceName string) (P ConnectionParams, err error) {

func NewPassword(s string) Password { return dsn.NewPassword(s) }

func freeAccessToken(cAccessToken *C.dpiAccessToken) {
if cAccessToken != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if cAssessToken == nil { return }

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

token.go Outdated
//
//export TokenCallbackHandler
func TokenCallbackHandler(ctx unsafe.Pointer, accessTokenC *C.dpiAccessToken) {
log.Printf("CB %p %+v", ctx, accessTokenC)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if logger != nil && logger.Enabled(ctx, slog.LevelDebug) {
logger.Debug("TokenCallbackHandler", "ctx", fmt.Sprintf("%p", ctx), "accessToken", cAccessToken)
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

token.go Outdated
// tokenCallbackHandler is the callback for C code on token expiry.
//
//export TokenCallbackHandler
func TokenCallbackHandler(ctx unsafe.Pointer, accessTokenC *C.dpiAccessToken) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In drv.go it is called cAccessToken;
Here, accessTokenC.

Please, rename both to accessToken!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to accessToken

token.go Outdated
func TokenCallbackHandler(ctx unsafe.Pointer, accessTokenC *C.dpiAccessToken) {
log.Printf("CB %p %+v", ctx, accessTokenC)
accessTokenCBsMu.Lock()
acessTokenCB := accessTokenCBs[*((*uint64)(ctx))]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it is :=, then it overrides (shadows) the global accessTokenDBs, so no locking needed.

But I think you want to remove the :.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't understand the comment fully.. I want to modify the value(ptr) in the map with the token and private key C strings returned in this callback. These C strings are freed on next invocation of this callback.

I also modified to use RW lock instead of simple mutex. It looks ok?

token.go Outdated

import (
"context"
"github.com/godror/godror/dsn"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move it to the end of the imports, spearated by an empty line.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

drv.go Outdated
@@ -844,6 +900,9 @@ func (d *drv) createPool(P commonAndPoolParams) (*connPool, error) {
(**C.dpiPool)(unsafe.Pointer(&dp)),
)
}); err != nil {
if tokenCBID != 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't really need this check.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed it.

token.go Outdated
// this map entry
var (
accessTokenCBsMu sync.Mutex
accessTokenCBs = make(map[uint64]*accessTokenCB)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use of runtime/cgo.Handle may be easier.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried with cgo.Handle to store the callback context and hence avoid global map, accessTokenCBs . It is causing panic during C.dpiPool_create. looks like i am missing something..
panic: runtime error: cgo argument has Go pointer to unpinned Go pointer

I am able to simulate this panic with simple program. Below program will panic but when we comment this line
var h cgo.Handle so that we use global handle h , it works fine. So it looks this global bookkeeping might still be needed for each pool with cgo.Handle ?

package main

/*
struct dt {
  void *context;
};
typedef struct dt dt;

extern void MyGoPrint(void *context);
static inline void myprint(struct dt *a1) {
    MyGoPrint(a1->context);
}
*/
import "C"
import (
	"context"
	"runtime/cgo"
	"unsafe"
)

//export MyGoPrint
func MyGoPrint(context unsafe.Pointer) {
	h := *(*cgo.Handle)(context)
	val := h.Value().(accessTokenCB)
	println(val.id)
	h.Delete()
}

type At struct {
	Tok string
}

// AccessToken Callback information.
type accessTokenCB struct {
	ctx         context.Context
	callback    func(context.Context, *At) error
	id          uint64
	ctoken      *C.char
	cprivateKey *C.char
}

func getH() cgo.Handle {
	cb := func(ctx context.Context, tok *At) error {
		tok.Tok = "123"
		return nil
	}
	val := accessTokenCB{callback: cb, ctx: context.Background(), id: 32, ctoken: nil, cprivateKey: nil}
	h := cgo.NewHandle(val)
	return h

}

var h cgo.Handle

func main() {
	var h cgo.Handle
	h = getH()
	var poolCt C.dt
	poolCt.context = unsafe.Pointer(&h)
	C.myprint(&poolCt)
	// Output: 32
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

package main

/*
extern void MyGoPrint(void *p);
static inline void myprint(void *p) {
    MyGoPrint(p);
}
*/
import "C"
import (
	"context"
	"runtime/cgo"
	"unsafe"
)

//export MyGoPrint
func MyGoPrint(p unsafe.Pointer) {
	h := *(*cgo.Handle)(p)
	val := h.Value().(accessTokenCB)
	println(val.id)
	h.Delete()
}

type At struct {
	Tok string
}

// AccessToken Callback information.
type accessTokenCB struct {
	ctx         context.Context
	callback    func(context.Context, *At) error
	id          uint64
	ctoken      *C.char
	cprivateKey *C.char
}

func getH() cgo.Handle {
	cb := func(ctx context.Context, tok *At) error {
		tok.Tok = "123"
		return nil
	}
	val := accessTokenCB{
		callback: cb, ctx: context.Background(),
		id: 32, ctoken: nil, cprivateKey: nil}
	h := cgo.NewHandle(val)
	return h

}

func main() {
	var h cgo.Handle
	h = getH()
	C.myprint(unsafe.Pointer(&h))
	// Output: 32
}

works (no fancy structs - that'd need C helper).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finding https://groups.google.com/g/golang-nuts/c/PLSIN5jmzpM

The rewritten code is even simpler (treat cgo.Handle as a number (C.uintptr_t)):

package main

/*
#include <stdint.h>

extern void MyGoPrint(uintptr_t p);
static inline void myprint(uintptr_t p) {
    MyGoPrint(p);
}
*/
import "C"
import (
	"context"
	"runtime/cgo"
)

//export MyGoPrint
func MyGoPrint(handle C.uintptr_t) {
	h := cgo.Handle(handle)
	val := h.Value().(accessTokenCB)
	println(val.id)
	h.Delete()
}

type At struct {
	Tok string
}

// AccessToken Callback information.
type accessTokenCB struct {
	ctx         context.Context
	callback    func(context.Context, *At) error
	id          uint64
	ctoken      *C.char
	cprivateKey *C.char
}

func getH() cgo.Handle {
	cb := func(ctx context.Context, tok *At) error {
		tok.Tok = "123"
		return nil
	}
	val := accessTokenCB{
		callback: cb, ctx: context.Background(),
		id: 32, ctoken: nil, cprivateKey: nil}
	h := cgo.NewHandle(val)
	return h

}

func main() {
	C.myprint(C.uintptr_t(getH()))
	// Output: 32
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In our case , we need to pass poolCreateParams struct which has void * argument , accessTokenCallbackContext to C.dpiPool_create call. Hence i cant use cgo.Handle then?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

package main

/*
#include <stdint.h>
#include <stdlib.h>

struct hw {
  uintptr_t handle;
};
typedef struct hw hw;

extern void MyGoPrint(uintptr_t handle);

// Called in C with void*, calls Go function with uintptr_t.
static inline void MyWrappedGoPrint(void *context) {
	hw *a1 = (hw*)context;
	MyGoPrint(a1->handle);
}

static inline void *wrap_handle(uintptr_t handle) {
	hw *a1 = malloc(sizeof(hw));
	a1->handle = handle;
	return (void *)a1;
}

static inline void myprint(void *context) {
	MyWrappedGoPrint(context);
}

static inline void wrapped_myprint(uintptr_t handle) {
	myprint(wrap_handle(handle));
}
*/
import "C"

import (
	"context"
	"runtime/cgo"
)

//export MyGoPrint
func MyGoPrint(handle C.uintptr_t) {
	h := (cgo.Handle)(handle)
	val := ((cgo.Handle)(h)).Value().(accessTokenCB)
	println(val.id)
	h.Delete()
}

type At struct {
	Tok string
}

type accessTokenCB struct {
	ctx         context.Context
	callback    func(context.Context, *At) error
	id          uint64
	ctoken      *C.char
	cprivateKey *C.char
}

func getH() cgo.Handle {
	cb := func(ctx context.Context, tok *At) error {
		tok.Tok = "123"
		return nil
	}
	val := accessTokenCB{callback: cb, ctx: context.Background(), id: 32, ctoken: nil, cprivateKey: nil}
	h := cgo.NewHandle(val)
	return h

}

func main() {
	h := getH()
	C.wrapped_myprint((C.uintptr_t)(h))
	// Output: 32
}

just create enough wrappers :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks . I made the changes by wrapping it now..

@tgulacsi tgulacsi merged commit 11d6b28 into godror:main May 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants