Authenticating a service account for Firebase Realtime Database in Go - go

I'm trying to use the new Firebase Realtime Database for a simple logging application. All interraction with the database will be from my server, so I only need one account that can read/write anything.
As far as I can tell (the documentation is awful - there's plenty of it but it contradicts itself and half of it is for the 'old' Firebase, and often it is in some random language that you aren't using), I need to create a service account and then create a JWT token using OAuth. Fortunately Go has some nice built in libraries for this. Here is my code:
const firebasePostUrl = "https://my-product-logging.firebaseio.com/tests.json"
// Obtained from the Google Cloud API Console
var firebaseServiceAccount map[string]string = map[string]string{
"type": "service_account",
"project_id": "my-product-logging",
"private_key_id": "1c35ac0c501617b8f1610113c492a5d3321f4318",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoblahblahhwWlteuRDrsxmRq+8\ncDGMKcXyDHl3nWdIrWqJcDw=\n-----END PRIVATE KEY-----\n",
"client_email": "log-user#my-product-logging.iam.gserviceaccount.com",
"client_id": "101403085113430683797",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/log-user%40my-product-logging.iam.gserviceaccount.com",
}
func firebaseClient() *http.Client {
jwtConfig := jwt.Config{
// Email is the OAuth client identifier used when communicating with
// the configured OAuth provider.
Email: firebaseServiceAccount["client_email"],
// PrivateKey contains the contents of an RSA private key or the
// contents of a PEM file that contains a private key. The provided
// private key is used to sign JWT payloads.
// PEM containers with a passphrase are not supported.
// Use the following command to convert a PKCS 12 file into a PEM.
//
// $ openssl pkcs12 -in key.p12 -out key.pem -nodes
//
PrivateKey: []byte(firebaseServiceAccount["private_key"]),
// PrivateKeyID contains an optional hint indicating which key is being
// used.
PrivateKeyID: firebaseServiceAccount["private_key_id"],
// Subject is the optional user to impersonate.
Subject: "",
// Scopes optionally specifies a list of requested permission scopes.
Scopes: []string{
"https://www.googleapis.com/auth/devstorage.readonly",
},
// TokenURL is the endpoint required to complete the 2-legged JWT flow.
TokenURL: firebaseServiceAccount["token_uri"],
// Expires optionally specifies how long the token is valid for.
Expires: 0,
}
ctx := context.Background()
return jwtConfig.Client(ctx)
}
func firebaseFunc() {
authedClient := firebaseClient()
msg := map[string]string{
"hello": "there",
"every": "one",
}
data, err := json.Marshal(msg)
if err != nil {
log.Fatal("JSON Marshall Error: ", err)
continue
}
resp, err := authedClient.Post(firebasePostUrl, "application/json", bytes.NewReader(data))
if err != nil {
log.Fatal("Firebase Error: ", err)
continue
}
log.Print("Firebase Response Code: ", resp.StatusCode)
}
The problem is, I always get this error:
{
"error" : "invalid_scope",
"error_description" : "https://www.googleapis.com/auth/devstorage.readonly is not a valid audience string."
}
I assume it is a type that the error is invalid_scope, and the description says it is an invalid audience (I assume the JWT aud parameter).
What do I use as my scope to allow me to read/write the Firebase Database (using the default "auth != null" rules)?
Edit: I found the answer here finally:
https://www.googleapis.com/auth/firebase
However now it gives me a 403 response when actually doing the post.
{
"error" : "Permission denied."
}

Ugh, I found the undocumented answer here. Currently you also need this scope:
https://www.googleapis.com/auth/userinfo.email

Related

Generate a JWT from GitHub App PEM private key in Go

I'm trying to use GitHub App and I need to generate a JWT for authenticating (https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#generating-a-private-key)
I'm trying to do that using Goland.
How can I generate a JWT from PEM private key in Go??
The jwt-go library has all the tools you need, and is fairly well documented. You can find it at https://github.com/golang-jwt/jwt.
Assuming you understand what JWTs are and how they're structured, and that you can get that PEM key as a []byte, the process is roughly:
Add "github.com/golang-jwt/jwt/v4" to your imports.
Create a set of claims, which can include the RegisteredClaims type and any custom claims you may need.
Create the token with jwt.NewWithClaims() - you'll need to provide the appropriate signing method. I've primarily used RS256.
Create the JWT string from the token with token.SignedString().
In practice, it will look something like this:
imports "github.com/golang-jwt/jwt/v4"
type MyCustomClaims struct {
*jwt.RegisteredClaims
FooClaim int
BarClaim string
}
func CreateJWT(pemKey []byte) string {
// expires in 60 minutes
expiration := time.Now().Add(time.Second * 3600)
claims := MyCustomClaims{
RegisteredClaims: &jwt.RegisteredClaims{
Issuer: "Example Code Inc.",
ExpiresAt: jwt.NewNumericDate(expiration),
Subject: "JWT Creation",
},
FooClaim: 123,
BarClaim: "bar",
}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
privateKey, _ := jwt.ParseRSAPrivateKeyFromPEM(pemKey)
myJWT, _ := jwt.SignedString(privateKey)
return myJWT
}
I suggest reading code from this repository:
https://github.com/bradleyfalzon/ghinstallation
I don't know why, but the code in the answer from #JesseB above didn't work for me - it always throws: 401 Unauthorized. Although this repository does use golang-jwt package internally

400 Bad request when generating the Google API access token using Go iamcredentials client API

I am trying to implement iamcredentials Go API client to generate an Access Token to access some Google APIs via REST API, I am using this code
package main
import (
"context"
"log"
"google.golang.org/api/iamcredentials/v1"
)
func main() {
iamcredentialsService, err := iamcredentials.NewService(context.Background())
if err != nil {
log.Println("error initialize iamcredential Service ", err)
return
}
accessTokenCall := iamcredentialsService.Projects.ServiceAccounts.GenerateAccessToken(
"projects/-/serviceAccounts/some-sa#some-project-id.iam.gserviceaccount.com:generateAccessToken",
&iamcredentials.GenerateAccessTokenRequest{
Scope: []string{
iamcredentials.CloudPlatformScope,
},
},
)
iamResp, err := accessTokenCall.Do()
if err != nil {
log.Println("error generate access token", err)
return
}
log.Println(iamResp)
}
But when I tried to run the above snippet, I got this message
go run main.go
error generate access token googleapi: Error 400: Request contains an invalid argument., badRequest
Is there any way to check which one is causing the above response? I am not sure since there isn't any good example of implementation. Any help would be appreciated, Thanks.
Notes :
I have checked following documentation on this topic https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials and this https://pkg.go.dev/google.golang.org/api/iamcredentials/v1#pkg-overview
I have already setup the Service account using Service Account Token Creator role on IAM and also enabled the IAM API from the console
Also I have added GOOGLE_APPLICATION_CREDENTIALS to the environment variables as suggested
#DanielFarrell is right, you need to remove the :generateAccessToken at the end. Here the documentation in the code. Don't hesitate to explore it, it's open source ;)
// GenerateAccessToken: Generates an OAuth 2.0 access token for a
// service account.
//
// - name: The resource name of the service account for which the
// credentials are requested, in the following format:
// `projects/-/serviceAccounts/{ACCOUNT_EMAIL_OR_UNIQUEID}`. The `-`
// wildcard character is required; replacing it with a project ID is
// invalid.
func (r *ProjectsServiceAccountsService) GenerateAccessToken(name string, generateaccesstokenrequest *GenerateAccessTokenRequest) *ProjectsServiceAccountsGenerateAccessTokenCall {
c := &ProjectsServiceAccountsGenerateAccessTokenCall{s: r.s, urlParams_: make(gensupport.URLParams)}
c.name = name
c.generateaccesstokenrequest = generateaccesstokenrequest
return c
}

Github secret token verification

I'm making a little API using Go. I would like to be able to handle Github webhooks with secret token. I set up the secret on my webhook Github which is "azerty".
Now I try to verify that the incoming webhook has the correct secret token. I've read Github documentation which say the algorithm use HMAC with SHA1. But I can't verify the secret from the incoming Github webhook ...
func IsValidSignature(r *http.Request, key string) bool {
// KEY => azerty
gotHash := strings.SplitN(r.Header.Get("X-Hub-Signature"), "=", 2)
if gotHash[0] != "sha1" {
return false
}
defer r.Body.Close()
b, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Cannot read the request body: %s\n", err)
return false
}
hash := hmac.New(sha1.New, []byte(key))
if _, err := hash.Write(b); err != nil {
log.Printf("Cannot compute the HMAC for request: %s\n", err)
return false
}
expectedHash := hex.EncodeToString(hash.Sum(nil))
log.Println("EXPECTED HASH:", expectedHash)
log.Println("GOT HASH:", gotHash[1])
return gotHash[1] == expectedHash
}
EXPECTED HASH: 10972179a3b0efc337f79ec41847062bc598bb04
GOT HASH: 36de72e0d386e36e2c7b034c85cd3b3889594992
To test, I copy the payload of the Github webhook in Postman with the right headers. I don't know why I get two different hash ... I've checked my key is non-empty with the correct value and my body is non-empty too.
Do I miss something?
I copy the payload of the Github webhook in Postman with the right header.
I've checked my key is non-empty with the correct value and my body is non-empty too.
The crypto is correct, except few minor issues. Obviously your body does not match the same body you have got from Github. Could be formatting, trailing newlines, etc. It must exactly byte-to-byte match the original body.
If this code works with Github and does not work with copy in Postman just replace X-Hub-Signature in your fixture with that "wrong" hash.
Some extras:
Use hmac.Equal for secure comparison
hash.Write never returns errors

Scopes Not Present in JWT Claim in Golang and Goa

I am using the excellent goa package to generate my API in Go.
However, I am having an issue with its security middleware and when I submit a bearer token to my controllers, I get "authorization failed" because of "scopes:null". I am using Auth0 for authentication and it is generating the bearer token. The exact error in Postman is:
{"id":"xOUR882s","code":"jwt_security_error","status":401,"detail":"authorization failed: required 'scopes' not present in JWT claim","meta":{"required":["read:meta"],"scopes":null}}
However, my token does include the required scope read:meta. jwt.io gives back the following decoded bearer:
{
"iss": "https://learnlogic.au.auth0.com/",
"sub": "exJMkK7hXX56lrLwoTqna3s0jh7Gq67e#clients",
"aud": "https://api.learn-logic.com",
"exp": 1494855336,
"iat": 1494768936,
"scopes": "read:meta"
}
I am hoping someone can help me workout why, as I don't really understand what is going on in middleware/jwt.go in the goa project which may be found here. My only thought is that something about the Auth0 formatted bearer is not compatible with the parseClaimScopes function in middleware/jwt.go, but I don't know what.
I have the following main.go code:
b, err := ioutil.ReadFile("util/jwt.key")
if err != nil {
return
}
block, _ := pem.Decode([]byte(b))
var cert *x509.Certificate
cert, _ = x509.ParseCertificate(block.Bytes)
rsaPublicKey := cert.PublicKey.(*rsa.PublicKey)
fmt.Println(rsaPublicKey.N)
fmt.Println(rsaPublicKey.E)
fmt.Println(cert)
var keyx = []jwt.Key{rsaPublicKey}
var jwtResolver = jwt.NewSimpleResolver(keyx)
app.UseJWTMiddleware(service, jwt.New(jwtResolver, nil, app.NewJWTSecurity()))
The certificate I am reading in is the same one used in jwt.io to decode the bearer token.
Any help is much appreciated.
According to this ticket https://github.com/goadesign/goa/issues/1228, the problem was due to the fact there was only a support for a claim with a singular name "scope" and not the plural "scopes". This PR https://github.com/goadesign/goa/pull/1399, added the plural version.

Send email through unencrypted connection

I have a SMTP account that does not use encrypted connection. I can use the same account to send emails from C# and Python without problems but with Go I get the error:
unencrypted connection
This is the code I am using:
package main
import (
"log"
"net/smtp"
)
func main() {
// Set up authentication information.
auth := smtp.PlainAuth(
"",
"user#example.com",
"password",
"mail.example.com",
)
// Connect to the server, authenticate, set the sender and recipient,
// and send the email all in one step.
err := smtp.SendMail(
"mail.example.com:25",
auth,
"sender#example.org",
[]string{"recipient#example.net"},
[]byte("This is the email body."),
)
if err != nil {
log.Fatal(err)
}
}
The issue here is that smtp.PlainAuth refuses to send your password over an unencrypted connection. This is for your own protection. Something like smtp.CRAMMD5Auth would be a much better choice. When using CRAM-MD5, even over an unencrypted connection, your password is not exposed.
If you want to use plain authentication anyways, you would need to make your own version of smtp.PlainAuth. Luckily, this is a very easy thing to do. Just copy the 20 lines or so from the standard library and remove:
if !server.TLS {
return "", nil, errors.New("unencrypted connection")
}
http://golang.org/src/pkg/net/smtp/auth.go?s=1820:1882#L41 contains the code.
If you do not wish to copy code, you can reuse the standard library implementation by wrapping the smtp.Auth returned by the function in your own type. This way you intercept the *smtp.ServerInfo and trick the actual Auth mechanism (from the standard library) that there is an encrypted connection. Make sure to heavily comment to make it clear why you are doing what you are doing. Something like this (untested):
type unencryptedAuth struct {
smtp.Auth
}
func (a unencryptedAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
s := *server
s.TLS = true
return a.Auth.Start(&s)
}
auth := unencryptedAuth {
smtp.PlainAuth(
"",
"user#example.com",
"password",
"mail.example.com",
),
}

Resources