To give some context, I am trying to verify a JWT signature using Public Key. The public key expires after some amount of hrs which could change. Now the problem is that if the validation fails, I don't know if it failed because of an invalid Token, or the Public Key got expired.
To solve this, I am doing the following:
Fetch a new key from some URL when there is a validation error
Use the updated key to validate the token
If it still fails, then the JWT validation fails
Code
I have a HTTP Handler inside which a new instance of tokenValidator is created and then validate() is called on it.
keystore that is used to create the tokenValidator is initialized outside the handler
type tokenValidator struct {
keyStore *keystore.KeyStore
}
func (t *tokenValidator) validate(){
// get token
.....
// validate token
var err error
err = t.validateSignature(token, publicKeyName)
if err != nil {
t.keyStore.UpdateKeys() // fetch new keys and try again
err = t.validateSignature(token, publicKeyName)
if err != nil {
return nil, fmt.Errorf("pf token signature validation failed: %w", err)
}
}
}
Keystore
Now, I don't want all the failed validation requests to fetch the key from the URL instead I want to use the updated key for all the requests waiting to acquire the lock.
So, for example, if there are 100 parallel requests, I want the request that acquires lock to update the key. All the others should use the updated key.
type KeyStore struct {
Keys jwks.JWKPublicKeys // map[string]rsa.PublicKey
mu sync.Mutex
isKeyUpdated bool
}
func (k *KeyStore) UpdateKeys() error {
k.isKeyUpdated = false
k.mu.Lock()
defer k.mu.Unlock()
var err error
if !k.isKeyUpdated {
keysMap, err := retrieveKeysFromURL()
if err == nil {
k.Keys = *keysMap // update the Keys Map
k.isKeyUpdated = true
}
}
return err
}
I am new to concurrency-related topics and was wondering if this could be improved. Or if there is some other better solution to this problem?
Thanks
The idea that you reload keys when token validation fails is a big security risk. Someone can bombard your API with invalid keys to launch a DoS attack by forcing you to reload keys in a never-ending loop. Ideally, you should find a way to determine if a key has expired or not.
You can keep a cache using:
cache map[string]*Key
where
type Key struct {
once sync.Once
key rsa.PublicKey
}
When you expire a key, you can simply do:
cache.cache[id]=&Key{}
which will insert an empty key in the cache. To get the key value from the cache:
func GetPublicKey(id string) rsa.PublicKey {
cache.Lock()
k:=cache.cache[id]
if k==nil {
cache.cache[id]=&Key{}
}
cache.Unlock()
k.once.Do(func() {
k.key=loadPublicKey()
})
return k.key
}
This way, everybody will wait until one of the goroutines initializes the key.
Related
I want to automate OTP submission on a bank page. I will get the OTP in my database only after webdriver have clicked confirm on the bank page. After confirming, I need to fetch OTP from the db and then automate OTP submission.
ctx, cancel := chromedp.NewContext(context.Background(), chromedp.WithDebugf(log.Printf))
defer cancel()
// run chromedp tasks
err := chromedp.Run(ctx,
chromedp.Navigate(bankUrl),
chromedp.WaitVisible(`#username`),
chromedp.SendKeys(`#username`, `usernameXXX`),
chromedp.WaitVisible(`#label2`, ),
chromedp.SendKeys(`#label2`, `passwordxxx` ),
chromedp.Click(`//input[#title="Login"]`),
chromedp.WaitVisible(`#Go`),
chromedp.Click(`#Go`),
chromedp.WaitVisible(`#confirmButton`),
chromedp.Click(`#confirmButton`),
chromedp.WaitVisible(`//input[#type="password"]`),
// perform fetch OTP below, this raise error
otp := fetchOTPFromDb()
chromedp.SendKeys(`//input[#type="password"]`, otp),
chromedp.WaitVisible(`#confirmButton`),
chromedp.Click(`#confirmButton`))
if err != nil {
log.Fatal(err)
}
The problem is that chromedp.Run expects all args to be of the type chromedp.Tasks, so I can't call custom functions there and I get error while fetching the OTP from db. How do I go around this?
The solution is to wrap the otp fetch within an Action.Do call, then return the result of a call to chromdp.SendKeys to set the HTML input value.
It is required to work that way because the One Time Password does not exist until the page was fetched, thus, its read must happen while manipulating the resource.
Like this
package main
import "context"
type OTPAction struct {
// DB ....
}
func (a OTPAction) Do(ctx context.Context) error {
// fetch OTP here
otp := "otp test"
return chromedp.SendKeys(`//input[#id="user-message"]`, otp).Do(ctx)
}
I have a very simple piece of code. One single middleware is applied to all routes. In this middleware, the header field 'x-sentinel-tenant' is read. If it is blank, an error is returned. If it has some value, then that value is to be extracted and set in the request context for later use.
Here's my middleware code
// VerifyTenant ensures that the user has added tenant information in the header
func VerifyTenant(c *gin.Context) {
requestTenant := c.Request.Header.Get("x-tenant")
if requestTenant == "" {
c.AbortWithStatusJSON(
http.StatusBadRequest,
views.GenerateErrorResponse(
http.StatusBadRequest,
"Please add tenant in the header.",
c.Request.URL.Path,
),
)
return
}
c.Request = c.Request.WithContext(context.WithValue(c, tenant, requestTenant))
}
When I try to access this context in a later part of my code, here's how I get the data when hovered over the ctx value.
I am not sure what am I doing wrong. It's a fairly straight forward piece of code which is behaving very funny. Any leads will be appreciated. Thank you
When I update the context, I expect "tenant" to be added as another field. Which is not happening. Can multiple values not coexist in the same context?
My question - how to correctly set multiple values in gin request context
Here's how I am trying to access tenant
return ctx.Value("Context").(context.Context).Value("tenant").(string)
https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc#Context.Set
func (*Context) Set does exactly what you want. an example is:
func VerifyTenant(ctx *gin.Context) {
requestTenant := ctx.GetHeader("x-tenant")
if requestTenant == "" {
// Abort
return
}
ctx.Set("x-tenant", requestTenant)
}
func Test(ctx *gin.Context){
// when you need to get the value of "x-tenant"
requestTenant := ctx.GetString("x-tenant")
}
I reuse the http client connection to make external calls to a single endpoint. An excerpt of the program is shown below:
var AppCon MyApp
func New(user, pass string, platformURL *url.URL, restContext string) (*MyApp, error) {
if AppCon == (MyApp{}) {
AppCon = MyApp{
user: user,
password: pass,
URL: platformURL,
Client: &http.Client{Timeout: 30 * time.Second},
RESTContext: restContext,
}
cj, err := cookiejar.New(nil)
if err != nil {
return &AppCon, err
}
AppCon.cookie = cj
}
return &AppCon, nil
}
// This is an example only. There are many more functions which accept *MyApp as a pointer.
func(ma *MyApp) GetUser(name string) (string, error){
// Return user
}
func main(){
for {
// Get messages from a queue
// The message returned from the queue provide info on which methods to call
// 'm' is a struct with message metadata
c, err := New(m.un, m.pass, m.url)
go func(){
// Do something i.e c.GetUser("123456")
}()
}
}
I now have the requirement to set up a client connections with different endpoints/credentials received via queue messages.
The problem I foresee is I can't just simply modify AppCon with the new endpoint details since a pointer to MyApp is returned, resulting in resetting c. This can impact a goroutine making a HTTP call to an unintended endpoint. To make matters non trivial, the program is not meant to have awareness of the endpoints (I was considering using a switch statement) but rather receive what it needs via queue messages.
Given the issues I've called out are correct, are there any recommendations on how to solve it?
EDIT 1
Based on the feedback provided, I am inclined to believe this will solve my problem:
Remove the use of a Singleton of MyApp
Decouple the http client from MyApp which will enable it for reuse
var httpClient *http.Client
func New(user, pass string, platformURL *url.URL, restContext string) (*MyApp, error) {
AppCon = MyApp{
user: user,
password: pass,
URL: platformURL,
Client: func() *http.Client {
if httpClient == nil {
httpClient = &http.Client{Timeout: 30 * time.Second}
}
return httpClient
}()
RESTContext: restContext,
}
return &AppCon, nil
}
// This is an example only. There are many more functions which accept *MyApp as a pointer.
func(ma *MyApp) GetUser(name string) (string, error){
// Return user
}
func main(){
for {
// Get messages from a queue
// The message returned from the queue provide info on which methods to call
// 'm' is a struct with message metadata
c, err := New(m.un, m.pass, m.url)
// Must pass a reference
go func(c *MyApp){
// Do something i.e c.GetUser("123456")
}(c)
}
}
Disclaimer: this is not a direct answer to your question but rather an attempt to direct you to a proper way of solving your problem.
Try to avoid a singleton pattern for you MyApp. In addition, New is misleading, it doesn't actually create a new object every time. Instead you could be creating a new instance every time, while preserving the http client connection.
Don't use constructions like this: AppCon == (MyApp{}), one day you will shoot in your leg doing this. Use instead a pointer and compare it to nil.
Avoid race conditions. In your code you start a goroutine and immediately proceed to the new iteration of the for loop. Considering you re-use the whole MyApp instance, you essentially introduce a race condition.
Using cookies, you make your connection kinda stateful, but your task seems to require stateless connections. There might be something wrong in such an approach.
I am using https://github.com/kataras/iris Go web framework. I have:
User Registered
User Verified & Logged in
Session created and set with key username with user (table & struct) username
Now, here is my code for logged in user:
// Loaded All DB and other required value above
allRoutes := app.Party("/", logThisMiddleware, authCheck) {
allRoutes.Get("/", func(ctx context.Context) {
ctx.View("index.html");
});
}
In authcheck middleware
func authcheck(ctx context.Context) {
// Loaded session.
// Fetched Session key "isLoggedIn"
// If isLoggedIn == "no" or "" (empty)
// Redirected to login page
// else
ctx.Next()
}
My Session function
func connectSess() *sessions.Sessions {
// Creating Gorilla SecureCookie Session
// returning session
}
Now, my problem is, how do I share Logged User value to all routes in template. My Current option is:
// Loaded all DB and required value
allRoutes := app.Party("/", logThisMiddleware, authCheck) {
allRoutes.Get("/", func(ctx context.Context) {
// Load Session again
// Fetch username stored in session
// Run Query against DB
// Share the user struct value.
// Example ctx.ViewData("user", user)
ctx.View("index.html");
});
allRoutes.Get("dashboard", func(ctx context.Context) {
// Load Session again
// Fetch username stored in session
// Run Query against DB
// Share the user struct value.
// Example ctx.ViewData("user", user)
ctx.View("index.html");
});
}
But problem with above code is, I will have to write session for each route and run query again for each route I run and than share.
I feel, there must be better way of doing it , rather than loading session twice for each route one in authCheck middleware and second inside allRoutes.Get route.
I need ideas on how this can be optimised and user data can be shared to template by just writing code one time and not repeating below for each route
// Load Session again
// Fetch username stored in session
// Run Query against DB
// Share the user struct value.
// Example ctx.ViewData("user", user)
it's easy you can use the ctx.Values().Set/Get to make something shareable between your route's handlers or middleware(s).
// load session manager once
sess := connectSess()
func authCheck(ctx context.Context) {
session := sess.Start(ctx)
// Load your user here.
// [...]
// Save the returning user to the local storage of this handlers chain, once.
ctx.Values().Set("user", user) // <-- IMPORTANT
}
app.Get("/", func(ctx context.Context) {
// Get the user from our handlers chain's local storage.
user := ctx.Values().Get("user") // <-- IMPORTANT
// Bind the "{{.user}}" to the user instance.
ctx.ViewData("user", user)
// Render the template file.
ctx.View("index.html")
})
app.Get("dashboard", func(ctx context.Context) {
// The same, get the user from the local storage...
user := ctx.Values().Get("user") // <-- IMPORTANT
ctx.ViewData("user", user)
ctx.View("index.html")
})
That's all, pretty simple, right?
But I have some notes for you, read them if you have more time.
When you're on root "/" you don't have to create a party for it(.Party) in order to add middlewares (begin(Use) or finish(Done)), use just the iris.Application instance, app.Use/Done.
Don't write this:
allRoutes := app.Party("/", logThisMiddleware, authCheck) {
allRoutes.Get("/", myHandler)
}
Do that instead:
app.Use(logThisMiddleware, authCheck)
app.Get("/", myHandler)
It's easier to read and understand.
I've also noticed that you're using ; at the end of your functions, your editor and gocode tool will remove those, when you write a program using the Go Programming Language you shouldn't do that, remove all ;.
Last, please read the documentation and the examples, we have many of them at https://github.com/kataras/iris/tree/master/_examples , hopes you the best!
I'm using the oauth package "code.google.com/p/goauth2/oauth" with revel and the it creates a few structures with quite a bit of information in it. I need this information to be persistent throughout the session but sessions can only be type string. Is there a better way of doing this than the following?
c.Session["AccessToken"] = t.Token.AccessToken
c.Session["RefreshToken"] = t.Token.RefreshToken
...
If not how do I reassign the strings to create another structure to call Client.Get() ?
You can use the json package to "convert" structs to string and vice versa. Just know that only exported fields are serialized this way.
Since oauth.Token has only exported fields, this will work:
if data, err := json.Marshal(t.Token); err == nil {
c.Session["Token"] = string(data)
} else {
panic(err)
}
And this is how you can reconstruct the token from the session:
if err := json.Unmarshal([]byte(c.Session["Token"]), &t.Token); err != nil {
panic(err)
}
Instead of that you can try to save some string ID to Session and the object you need to Cache:
c.Session["id"] = id
go cache.Set("token_"+id, t.Token, 30*time.Minute)
And then use it as follows:
var token oauth.Token
id := c.Session["id"]
if err := cache.Get("token_"+id, &token); err != nil {
// TODO: token not found, do something
}
// TODO: use your token here...
The advantage of this approach is you do not have to work with json package explicitly and cache has a few different back-ends out of the box: memory, redis, memcached. Moreover, you do not have a limitation of 4K as in case of Cookie based Session.
https://revel.github.io/manual/cache.html