Prevent Cloud Run from passing on Authorization header - go

I have Grafana hosted on Cloud Run. Grafana has anonymous access enabled while Cloud Run needs valid credentials.
Now I try mimic the first example of https://grafana.com/docs/grafana/latest/http_api/dashboard/. Thus I downloaded a service account key to /tmp/creds.json. This file I set as GOOGLE_APPLICATION_CREDENTIALS.
Using these credentials I then send a POST to /api/dashboards/db.
What I see is that I get through Google auth, it starts up Cloud Run instances and I get an error message from Grafana:
{"message":"invalid API key"}
This in turn then lets me believe that Google auth is not consuming the Authorization header but delegating it along.
So my question is how can I prevent Google auth from delegate this header?
Or as a workaround how can I make Grafana ignore the header?
Here the source I use to make the requests:
func TestCall(t *testing.T) {
r := strings.NewReader(`
{
"dashboard": {
"id": null,
"uid": null,
"title": "Production Overview",
"tags": [ "templated" ],
"timezone": "browser",
"schemaVersion": 16,
"version": 0,
"refresh": "25s"
},
"folderId": 0,
"folderUid": "l3KqBxCMz",
"message": "Made changes to xyz",
"overwrite": false
}
`)
os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "/tmp/creds.json")
ctx := context.TODO()
ts, err := idtoken.NewTokenSource(ctx, "https://grafana-123.run.app")
if err != nil {
if !strings.HasPrefix(err.Error(), "idtoken: credential must be service_account") {
panic(err)
}
creds, err := google.FindDefaultCredentials(ctx)
if err != nil {
panic(err)
}
ts = creds.TokenSource
}
tok, err := ts.Token()
if err != nil {
panic(err)
}
if tok.AccessToken == "" {
panic("Empty token!")
}
fmt.Printf("AT %s\n", tok.AccessToken)
client := &http.Client{}
req, err := http.NewRequest("POST", "https://grafana-123.run.app/api/dashboards/db", r)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+tok.AccessToken)
// This does not work at all: req.Header.Set("Proxy-Authorization", "Bearer "+tok.AccessToken)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
fmt.Printf("SC %d\n", resp.StatusCode)
bs, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
panic(string(bs))
}
}

The header Proxy-Authorization should work, but I have also been unsuccessful.
To clarify your problem, you are using IAP (OIDC Identity Token) to authorize access to Cloud Run. Your software (Grafana) is processing the HTTP Authorization header and assuming that you are passing it an API Key (which fails). I am not familiar with Grafana internals, but one possible solution is to write middleware that detects an OIDC token and removes that header. I am not aware of a Cloud Run method to remove the Authorization header from HTTP requests that your application receives.
IMHO this is a problem that should be resolved in Grafana's code. If Grafana has anonymous access enabled, then it should not be validating the HTTP Authorization header. Consider opening an issue with Grafana or fixing the code and submitting a patch.

Related

How to download calendar events from external user in a non-interactive manner?

I created this method, which essentially does what I need:
clientId := os.Getenv("CLIENT_ID")
tenantId := "common"
scopes := []string{"calendars.read", "calendars.readwrite"} // todo add offline_access
credential, err := azidentity.NewDeviceCodeCredential(&azidentity.DeviceCodeCredentialOptions{
ClientID: clientId,
TenantID: tenantId,
UserPrompt: func(ctx context.Context, message azidentity.DeviceCodeMessage) error {
fmt.Println(message.Message)
return nil
},
})
if err != nil {
log.Print(err)
return
}
authProvider, err := auth.NewAzureIdentityAuthenticationProviderWithScopes(credential, scopes)
if err != nil {
log.Print(err)
return
}
adapter, err := msgraphsdk.NewGraphRequestAdapter(authProvider)
if err != nil {
log.Print(err)
return
}
result, err := msgraphsdk.NewGraphServiceClient(adapter).Me().Calendar().Events().Get(context.Background(), nil)
if err != nil {
log.Print(err)
}
for _, event := range result.GetValue() {
log.Print(*event.GetICalUId())
log.Print(*event.GetSubject())
}
However, this one will print out https://microsoft.com/devicelogin and the code to authenticate interactively. I have the access token and refresh token from another resource. I would need to inject either access token or refresh token into something, to replace the logic that results in interactivity. I need that for a back-end service that handles calendar synchronization. What do I need to do? Do you have some pointers? I am using Golang and I am breaking my head for the last few days (nothing I tried worked). Please, help.
I went through all tutorials on Microsoft Graph for Go that I could find. I read tons of documentation. I suspect that publicClientApp, err := public.New("client_id", public.WithAuthority("https://login.microsoftonline.com/common")) and publicClientApp.AcquireTokenSilent(context.Background(), []string{"calendars.read", "calendars.readwrite"}, ...) could be the answer, but I cannot make it work. I have a working solution with curl, but I need to use Go SDK.

googleapi: Error 403: Request had insufficient authentication scopes. More details: Reason: insufficientPermissions, Message: Insufficient Permission

I am trying to send an email with Gmail API. But I get this error
googleapi: Error 403: Request had insufficient authentication scopes.
More details:
Reason: insufficientPermissions, Message: Insufficient Permission
I think it might bee related to config, I also followed google's quickstart for Go
here is the getClient func:
func getClient(config *oauth2.Config) *http.Client {
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
tokFile := "token.json"
tok, err := tokenFromFile(tokFile)
if err != nil {
tok = getTokenFromWeb(config)
saveToken(tokFile, tok)
}
return config.Client(context.Background(), tok)
}
here is the code send:
case "pass":
templateData := struct {
VerPass string
}{
VerPass: cont1,
}
emailBody, err := parseTemplate("ver.html", templateData)
if err != nil {
fmt.Println("Parse Err")
return false, err
}
var message gmail.Message
emailTo := "To: " + to + "\r\n"
subject := "Subject: " + sub + "\n"
mime := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
msg := []byte(emailTo + subject + mime + "\n" + emailBody)
message.Raw = base64.URLEncoding.EncodeToString(msg)
// Send the message
fmt.Println("OOOOOYYYYY")
//here is the problem
b, err := ioutil.ReadFile("credentials.json")
if err != nil {
log.Fatalf("Unable to read client secret file: %v", err)
}
// If modifying these scopes, delete your previously saved token.json.
config, err := google.ConfigFromJSON(b, gmail.MailGoogleComScope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
client := getClient(config)
srv, err := gmail.New(client)
if err != nil {
log.Fatalf("Unable to retrieve Gmail client: %v", err)
}
//GmailService
res, err := srv.Users.Messages.Send("me", &message).Do() // change me
if err != nil {
fmt.Println("Res Err")
fmt.Println(err)
return false, err
}
fmt.Println(res)
I tried for config, err := google.ConfigFromJSON(b, gmail.MailGoogleComScope), I tried using GmailReadonlyScope and gmail.GmailSendScope, but I got the same error.
Request had insufficient authentication scopes.
Means that the user who has authorized your application to access their data has not granted your application enough permissions in order to do what you are trying to do.
You apear to be using the user.message.send method. If you check the documentation you will find that that method requires that the user be authorized with one of the following scopes.
If you did follow Googles quick start for go then you used gmail.GmailReadonlyScope as a scope which will only give you read only access.
However your code now apears to contain the mail.MailGoogleComScope scope which should work however i am guessing you neglected to reauthorize the application. And didn't see the comment in the tutorial
// If modifying these scopes, delete your previously saved token.json.
I suggest you deleted token.json and then the application will require that you authorize it again and your code should work with the elevated permissions.

Cookie not being returned with subsequent REST calls to Go Server

I am exploring OAuth2 authentication and set up a server that authenticates with Github. I followed this example, and was able to get it working. I wanted to continue on with some of the suggestions and implement a basic session token system and make the Github calls from my server as opposed to sending the Authorization token to the client.
Here is my slightly modified /oauth/redirect handler
func oauthRedirectHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("/oauth/redirect")
err := r.ParseForm()
if err != nil {
fmt.Fprintf(os.Stdout, "could not parse query: %+v", err)
w.WriteHeader(http.StatusBadRequest)
}
code := r.FormValue("code")
reqURL := fmt.Sprintf("https://github.com/login/oauth/access_token?client_id=%s&client_secret=%s&code=%s", clientID, clientSecret, code)
req, err := http.NewRequest(http.MethodPost, reqURL, nil)
if err != nil {
fmt.Fprintf(os.Stdout, "could not create HTTP request: %v", err)
w.WriteHeader(http.StatusBadRequest)
}
req.Header.Set("accept", "application/json")
res, err := httpClient.Do(req)
if err != nil {
fmt.Fprintf(os.Stdout, "could not send HTTP request: %+v", err)
w.WriteHeader(http.StatusInternalServerError)
}
defer res.Body.Close()
var t oAuthAccessResponse
if err := json.NewDecoder(res.Body).Decode(&t); err != nil {
fmt.Fprintf(os.Stdout, "could not parse JSON response: %+v", err)
w.WriteHeader(http.StatusBadRequest)
}
newSession := sessionTracker{
AccessToken: accessToken,
TimeOut: time.Now().Add(time.Minute * 15),
}
sessionToken := uuid.New().String()
sessionTrackerCache[sessionToken] = newSession
http.SetCookie(w, &http.Cookie{
Name: sessionTokenConst,
Value: sessionToken,
Expires: newSession.TimeOut,
Domain: "localhost",
})
http.Redirect(w, r, "/welcome.html", http.StatusFound)
}
It redirects to the welcome page with an attached cookie that includes my SessionToken id.
Here is my welcomeHandler
func welcomeHandler(w http.ResponseWriter, req *http.Request) {
fmt.Println("/welcome")
cookie, err := req.Cookie(sessionTokenConst)
if err != nil {
fmt.Fprintf(os.Stdout, "no cookie attached: %+v", err)
w.WriteHeader(http.StatusBadRequest)
return
}
dat, err := ioutil.ReadFile("./public/welcome.html")
if err != nil {
fmt.Fprintf(os.Stdout, "could not read welcome page: %+v", err)
w.WriteHeader(http.StatusInternalServerError)
}
fmt.Fprintf(w, string(dat))
}
Observing my browser's network tab, I authenticate with Github and my server is able to see the authorization token. The redirect response at the end of the oauthRedirectHandler contains the cookie with the SessionID. My issue lies in the fact that the browser does not seem to attach the token on the GET call to /welcome.html. I can confirm this in both the browser and in the welcomeHandler.
This is all hosted locally.
I'm not sure if this is a issue with my server, my browser, or if my understanding that cookies are applied by the browser to any future client requests until the cookie expiration date is wrong.
Any help is appreciated!
Browsers default the cookie path to the request path. Set the path to "/" to make the cookie available across the site.
Do not set the domain unless you specifically have a reason to do so (thank you Volker for noting that).
http.SetCookie(w, &http.Cookie{
Name: sessionTokenConst,
Value: sessionToken,
Path: "/", // <--- add this line
Expires: newSession.TimeOut,
// Domain: "localhost", <-- Remove this line
})

IBM Watson speech to text WebSocket authorization with IAM API key

I am trying to connect to IBM Watson's speech to text WebSocket with a new account, that has IAM authorization only.
My WS endpoint is wss://stream-fra.watsonplatform.net/text-to-speech/api/v1/recognize and I get an access token via https://iam.bluemix.net/identity/token. Now when I open the socket connection with Authorization header with value Bearer token I get a bad handshake response: websocket: bad handshake, Unauthorized 401. Language is Go.
Am I doing something wrong or it is not possible to connect to Watson's speech to text WebSocket without username/password authentication i.e. the deprecated watson-token?
EDIT:
Code to open WebSocket:
headers := http.Header{}
headers.Set("Authorization", "Bearer " + access_token)
conn, resp, err := websocket.DefaultDialer.Dial("wss://stream-fra.watsonplatform.net/text-to-speech/api/v1/recognize", headers)
I have also tried basic authorization with apikey:**api_key** and the result is the same: 401.
EDIT 2:
Code to get the access token (based on the Watson Swift and Python SDKs) which succeeds and returns access and refresh tokens:
func getWatsonToken(apiKey string) (string, error) {
// Base on the Watson Swift and Python SDKs
// https://github.com/watson-developer-cloud/restkit/blob/master/Sources/RestKit/Authentication.swift
// https://github.com/watson-developer-cloud/python-sdk/blob/master/watson_developer_cloud/iam_token_manager.py
const tokenUrl = "https://iam.bluemix.net/identity/token"
form := url.Values{}
form.Set("grant_type", "urn:ibm:params:oauth:grant-type:apikey")
form.Set("apikey", apiKey)
form.Set("response_type", "cloud_iam")
// Token from simple "http.PostForm" does not work either
//resp, err := http.PostForm(tokenUrl, form)
req, err := http.NewRequest(http.MethodPost, tokenUrl, nil)
if err != nil {
log.Printf("could not create HTTP request to get Watson token: %+v", err)
return "", nil
}
header := http.Header{}
header.Set("Content-Type", "application/x-www-form-urlencoded")
header.Set("Accept", "application/json")
// "Yng6Yng=" is "bx:bx"
header.Set("Authorization", "Basic Yng6Yng=")
req.Header = header
req.Body = ioutil.NopCloser(bytes.NewReader([]byte(form.Encode())))
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Printf("problem executing HTTP request to get Watson token: %+v", err)
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return "", errors.New(fmt.Sprintf("failed to get Watson token: %d", resp.StatusCode))
}
jsonBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("problem reading Watson token from response body: %+v", err)
}
tokenResponse := &struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
ExpiresIn int64 `json:"expires_in"`
Expiration int64 `json:"expiration"`
}{}
err = json.Unmarshal(jsonBody, tokenResponse)
if err != nil {
log.Printf("could not parse Watson token response: %+v", err)
return "", err
}
return tokenResponse.AccessToken, err
}
I made an error in the endpoint and used the text-to-speech instead of the speech-to-text. With the correct URL the WebSocket API works.
wss://stream-fra.watsonplatform.net/text-to-speech/api/v1/recognize should be wss://stream-fra.watsonplatform.net/speech-to-text/api/v1/recognize

ebay API client credentials authorization

I want to implement an Ebay Browse API client credentials grant flow in Go using the oauth2 package. Here is my conf struct:
conf := clientcredentials.Config{
ClientID: "My Client ID",
ClientSecret: "My client Secret",
Scopes: []string{"https://api.ebay.com/oauth/api_scope"},
TokenURL: "https://api.sandbox.ebay.com/identity/v1/oauth2/token",
EndpointParams: url.Values{"redirect_uri": {"the RuName"}},
}
Then I tried to make a request by creating an *http.Request:
req, err := http.NewRequest("GET", "https://api.sandbox.ebay.com/buy/browse/v1/item/v1|202117468662|0?fieldgroups=COMPACT", nil)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
client := conf.Client(ctx)
res, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
But then I get:
{
"errors" : [ {
"errorId" : 1003,
"domain" : "OAuth",
"category" : "REQUEST",
"message" : "Token type in the Authorization header is invalid:Application",
"longMessage" : "Token type in the Authorization header is invalid:Application"
} ]
}
It turned out, that when making the request to the resource server Go's package sets the Authorization header as:
Authorization: Application Access Token token_string
So, I tried to set the token type to Bearer manually by doing:
tok, err := conf.Token(ctx)
if err != nil {
log.Fatal(err)
}
client := conf.Client(ctx)
req, err := http.NewRequest("GET", "https://api.sandbox.ebay.com/buy/browse/v1/item/v1|202117468662|0?fieldgroups=COMPACT", nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Authorization", "Bearer " tok.AccessToken)
res, err := client.Do(req)
But I continue getting the same error.
So, there is a problem (bug?) with the Ebay's implementation of the OAuth2.0 spec and I had to reimplement the TokenSource of the oauth2/clientcredentials package to set the token type to Bearer not Application Access Token.

Resources