I am developing a backend service which exposes APIs. I have decided to use vault to store the tokens to access these APIs.
Right now I am storing and rotating the keys manually in vault. This is my sample code to read secrets from vault.
func (v *vImpl) readSecret (name string) {
secret, err := v.client.Logical().Read(path)
if err != nil {
return nil, err
}
/* process secrets*/
}
While reading the secret from vault, I would like to check if the key has been stored in vault for the past 6 months. If so, I would like to rotate it.
Is there a way to check when the key was added in vault?
Related
I want to import users from an external database to firebase.
The password were hashed with a sha256 function with the password prepended by a salt (which is a UUID).
For example:
password = "123qwerty!"
salt = "cb60eb29-95a2-418e-be2a-c1c107fb1add"
hash = sha256(salt+password)
# 54ccb21d42c6961aa1b666b7cb0485f85aab2f2323399fb2959ea5e4e9f6f595
Now to import this to firebase I would do the following:
users = []*auth.UserToImport
users = append(users, (&auth.UserToImport{}).
UID("some-uid").
Email("jon.foo#example.com").
PasswordHash([]byte("54ccb21d42c6961aa1b666b7cb0485f85aab2f2323399fb2959ea5e4e9f6f595")).
PasswordSalt([]byte("cb60eb29-95a2-418e-be2a-c1c107fb1add")).
DisplayName("Jon FOO"))
h := hash.SHA256{
Rounds: 1,
InputOrder: hash.InputOrderSaltFirst,
}
res, err := cl.ImportUsers(ctx, users, auth.WithHash(h))
if err != nil {
log.Fatal(err)
}
The user is well imported in firebase (I can see it in the console), but when I try to login, I have this error The password is invalid or the user does not have a password.
I cannot see what is wrong with my way, maybe the Rounds parameter should be updated, but to what value?
Thanks!
I finally found my issue.
In my case I was giving as the PasswordHash the hex representation of the password:
PasswordHash([]byte("54ccb21d42c6961aa1b666b7cb0485f85aab2f2323399fb2959ea5e4e9f6f595")).
It turns out I have to decode first the password, like the following:
decoded, err := hex.DecodeString("54ccb21d42c6961aa1b666b7cb0485f85aab2f2323399fb2959ea5e4e9f6f595")
if err != nil {
return err
}
user := (&auth.UserToImport{}).
PasswordHash(decoded).
PasswordSalt([]byte("cb60eb29-95a2-418e-be2a-c1c107fb1add")). // the salt stays the same
...
// call ImportUsers with the same hash configuration (Rounds: 1, InputOrder: SaltFirst)
After updating this I ran the code and I could now authenticate with my imported user.
Quick note: as mentionned in the comment, the node SDK does not have the option to specify the input order (salt or password first), this seems to be an important missing feature.
Following the AWS documentation to the letter on how to download an object from S3, I'm getting the The provided token is malformed or otherwise invalid error.
I'm running my code through the AWS SAM CLI.
My code is:
sess, _ := session.NewSession(&aws.Config{
Region: aws.String(endpoints.UsWest2RegionID),
})
svc := s3.New(sess)
aak := os.Getenv("AWS_ACCESS_KEY")
ask := os.Getenv("AWS_SECRET_KEY")
fmt.Println("aak", aak, "ask", ask) // both of these correctly show my keys are being passed in
resp, err := svc.GetObject(&s3.GetObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
})
if err != nil {
fmt.Println(err)
}
I'm running it with:
sam local invoke LambdaMyFunction --debug -e test/event.json
I verified that the AWS Access and Secret keys are correct. I verified that I can download the object through CLI:
aws s3api get-object --bucket "mybucket-dev" --key "mydir/mykey_test.json" result.txt
result.txt is populated with the contents of mykey_test.json, so I know my creds have access to the file. I'm assuming the issue has to do with the role that lambda uses not having access to the file? But I can't find enough info to verify that possibility, or how to solve the problem testing locally.
Turns out the AWS_SESSION_TOKEN was being passed in string (null), which is definitely not a valid session token. So my code looks like this:
os.Setenv("AWS_SESSION_TOKEN", "")
sess, _ := session.NewSession()
And now I'm able to successfully download the file.
Since we don't yet know how this will be passed in through actual lambda, I set a check on it that looks like this:
// this is STUPID!!! But necessary.
if os.Getenv("AWS_SESSION_TOKEN") == "(null)" {
os.Setenv("AWS_SESSION_TOKEN", "")
}
sess, _ := session.NewSession()
If your SSO,, the, for windows, in a command prompt.. aws sso login --profile then used sam local invoke 'HelloWorldFunction' --profile and it works (no Invalid Token error...
I am trying to generate a JWT token using Go and I created the following function. I need to add the email address in jwt but as I do this I get an error saying key is of invalid type
func GenerateUserToken(expiryHours time.Duration, email string, secretKey string) (string, error) {
// Create a new token object, specifying signing method and the claims
// you would like it to contain.
token := jwt.New(jwt.SigningMethodES256)
claims := token.Claims.(jwt.MapClaims)
claims["exp"] = time.Now().Add(time.Hour * expiryHours).Unix()
claims["email"] = email
tokenStr, err := token.SignedString([]byte(secretKey))
if err != nil {
return "", err
}
return tokenStr, nil
}
What could be the reason for this? What mistake am I making?
JWT supports many signing algorithms, and that's a challenge for this particular API: depending on the signing algorithm, it expects to see a key matching that algorithm.
If you take a look at the API docs for this particular library:
https://godoc.org/github.com/dgrijalva/jwt-go
You'll see SigningMethodXXX types. These are signers selected by the signing method you pick. For ES256, it uses SigningMethodECDSA:
https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodECDSA
If you look at the Sign method docs, you'll see that it says:
For this signing method, key must be an ecdsa.PrivateKey struct
which you can parse from a PEM file using:
https://godoc.org/github.com/dgrijalva/jwt-go#ParseECPrivateKeyFromPEM
For example:
pk, err:= jwt.ParseECPrivateKeyFromPEM(pemData)
tokenStr, err := token.SignedString(pk)
This should give you a signed token with ES256.
So, you have to first figure out what kind of key you have. If you have a PEM encoding of a ECDSA key in a string, then use this method to parse it and pass the resulting private key to the signer.
If however you simply have a string secret key (like a password) and you'll share this secret key with the users of the JWT, then you can use a HMAC key. A HMAC key is simply a byte array secret that you share with your users so they can validate that the JWT was signed by you. Simply change the SigningMethod to one of the constants in:
https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodHMAC
Then, your code as it is will work with the exception that you have to change the signing method to something like jwt.New(jwt.SigningMethodHS256)
I want to query GSuite Admin SDK Directory API to return all users in a group, in Go, and authenticated as a GCP service account (the script will be executed in a Google Compute Engine VM or as a Google Cloud Function).
The service account I use (let's call it my-service-account#my-project.iam.gserviceaccount.com) have been granted necessary scopes in GSuite :
https://www.googleapis.com/auth/admin.directory.group.member.readonly
https://www.googleapis.com/auth/admin.directory.group.member
https://www.googleapis.com/auth/admin.directory.group.readonly
https://www.googleapis.com/auth/admin.directory.group
At my disposal, I also have a GSuite Admin account (let's call it my-admin#my-domain.com). This account will be used for delegation (see the docs on delegation).
I am able to return all users in a group with the following code (based on the code in the link provided above, and I have removed most of error handling to make the code shorter) :
package main
import (
"io/ioutil"
"log"
"os"
"golang.org/x/net/context"
"golang.org/x/oauth2/google"
admin "google.golang.org/api/admin/directory/v1"
)
func main() {
srv := createAdminDirectoryService(
os.Getenv("SERVICE_ACCOUNT_FILE_PATH"),
os.Getenv("GSUITE_ADMIN_USER_EMAIL"),
)
members := listUsersInGroup(srv, os.Args[1])
log.Println(members)
}
func createAdminDirectoryService(serviceAccountFilePath,
gsuiteAdminUserEmail string) *admin.Service {
jsonCredentials, _ := ioutil.ReadFile(serviceAccountFilePath)
config, _ := google.JWTConfigFromJSON(
jsonCredentials,
admin.AdminDirectoryGroupMemberReadonlyScope,
)
config.Subject = gsuiteAdminUserEmail
ctx := context.Background()
client := config.Client(ctx)
srv, _ := admin.New(client)
return srv
}
func listUsersInGroup(srv *admin.Service, groupEmail string) []string {
members, err := srv.Members.List(groupEmail).Do()
if err != nil {
log.Fatal(err)
}
membersEmails := make([]string, len(members.Members))
for i, member := range members.Members {
membersEmails[i] = member.Email
}
return membersEmails
}
As you can see, that code requires to have a JSON key file of my-service-account#my-project.iam.gserviceaccount.com. This is the only way I have found to create a jwt.Config object.
Moreover, note that delegation is done with the line config.Subject = gsuiteAdminUserEmail; without that, I got the error :
googleapi: Error 403: Insufficient Permission: Request had insufficient authentication scopes., insufficientPermissions
Anyway, running :
SERVICE_ACCOUNT_FILE_PATH=/path/to/json/key/of/my/service/account \
GSUITE_ADMIN_USER_EMAIL=my-admin#my-domain.com \
go run main.go my-group#my-domain.com
prints with success all users in my-group#my-domain.com.
However, since this code will be run from a Google Compute Engine VM (or a Google Cloud Function) with my-service-account#my-project.iam.gserviceaccount.com used as service account, it seems ridiculous to need a JSON key of that service account to authenticate (since I am already authenticated as my-service-account#my-project.iam.gserviceaccount.com, on the VM or inside the GCF).
I have tried to replace the content of createAdminDirectoryService() with the following code to authenticate as the user who launched the script (with default credentials) :
func createAdminDirectoryService() *admin.Service {
ctx := context.Background()
client, _ := google.DefaultClient(ctx, scopes...)
srv, _ := admin.New(client)
return srv
}
But listing users returns an error :
googleapi: Error 403: Insufficient Permission: Request had insufficient authentication scopes., insufficientPermissions
As this is the same error I have got when I removed the delegation, I think this is related. Indeed, I did not provide my GSuite Admin account anywhere during the admin.Service creation.
Can anyone help about one of these points :
How can I authenticate directly with the user running the go script on a Google Compute Engine VM, or a Google Cloud Function?
Do I really need a JSON key file to generate a jwt.Config object?
I have looked into the source code of golang.org/x/oauth2/google, I could get a oauth2.Config instead of jwt.Config, but it does not seem possible to "delegate" with a oauth2 token. How else can I perform the delegation?
This post is somewhat old, but hope this can help:
I had the same problem and made it work by changing the OAuth scopes in the Compute Instance (by default, the scopes only include https://www.googleapis.com/auth/cloud-platform). In my case, I had to add the https://www.googleapis.com/auth/admin.directory.group.readonly scope. It can be done using the gcloud compute instances set-service-account command. That way the service account bearer token received from the metadata server has the correct scopes to access the Admin Directory API.
Unfortunately, it cannot be done for Cloud Functions as the Cloud Functions OAuth scopes do not seem to be customizable.
See this post for more details.
Btw it is also possible now to give a service account access to the Admin Directory API without domain-wide delegation.
r, err := srv.Users.List().
ViewType("domain_public").
Customer("my_customer").
OrderBy("email").
Do()
Try using this.
I am using golang as my backend.I am storing some token values in redis.I m setting the values HSET and getting the values in HGETALL.I would like to know if there is any function to set the expiry for the keys that i m storing in the redis database.i want the token and its data to be deleted after 1hour. I m using Redigo package for redis. Thanks.Appreciate any help.
I use this to set the struct with has token as key
redisCon.Do("HMSET", redis.Args{}.Add(hashToken).AddFlat(&dataStruct)...)
For those who use go-redis library, you can set expiration by calling
_, err = redisClient.Expire("my:redis:key", 1 * time.Hour).Result()
Alternatively, you can do that upon insertion
_, err = redisClient.Set("my:redis:key", "value", 1 * time.Hour).Result()
Redis documentation does not support a command like "HMSETEX".
"HMSET" modifies the hashkeys and not the root key. TTL is supported at root key level and not at the hash key level. Hence, in your case you must be doing something like this in a separate call:
redisCon.Do("EXPIRE", key, ttl)
Which client are you using to connect to redis?
For redigo you can use this - https://github.com/yadvendar/redigo-wrapper
In that use call
func Expire(RConn *redigo.Conn, key string, ttl int)
For goredis - https://godoc.org/gopkg.in/redis.v5#Client.TTL
In this use:
func (c *Client) TTL(key string) *DurationCmd