Go HTTP Request with Basic Auth returning a 401 instead of a 301 redirect - redirect

Using Go 1.5.1.
When I try to make a request to a site that automatically redirects to HTTPS using Basic Auth I would expect to get a 301 Redirect response, instead I get a 401.
package main
import "net/http"
import "log"
func main() {
url := "http://aerolith.org/files"
username := "cesar"
password := "password"
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Println("error", err)
}
if username != "" || password != "" {
req.SetBasicAuth(username, password)
log.Println("[DEBUG] Set basic auth to", username, password)
}
cli := &http.Client{
}
resp, err := cli.Do(req)
if err != nil {
log.Println("Do error", err)
}
log.Println("[DEBUG] resp.Header", resp.Header)
log.Println("[DEBUG] req.Header", req.Header)
log.Println("[DEBUG] code", resp.StatusCode)
}
Note that curl returns a 301:
curl -vvv http://aerolith.org/files --user cesar:password
Any idea what could be going wrong?

A request to http://aerolith.org/files redirects to https://aerolith.org/files (note change from http to https). A request to https://aerolith.org/files redirects to https://aerolith.org/files/ (note addition of trailing /).
Curl does not follow redirects. Curl prints the 301 status for the redirect from http://aerolith.org/files to https://aerolith.org/files/.
The Go client follows the two redirects to https://aerolith.org/files/. The request to https://aerolith.org/files/ returns with status 401 because the Go client does not propagate the authorization header through the redirects.
Requests to https://aerolith.org/files/ from the Go client and Curl return status 200.
If you want to follow the redirects and auth successfully, set auth header in a CheckRedirect function:
cli := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
req.SetBasicAuth(username, password)
return nil
}}
resp, err := cli.Do(req)
If you want to match what Curl does, use a transport directly. The transport does not follow redirects.
resp, err := http.DefaultTransport.RoundTrip(req)
The application can also use the client CheckRedirect function and a distinguished error to prevent redirects as shown in an answer to How Can I Make the Go HTTP Client NOT Follow Redirects Automatically?. This technique seems to be somewhat popular, but is more complicated than using the transport directly.
redirectAttemptedError := errors.New("redirect")
cli := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return redirectAttemptedError
}}
resp, err := cli.Do(req)
if urlError, ok := err.(*url.Error); ok && urlError.Err == redirectAttemptedError {
// ignore error from check redirect
err = nil
}
if err != nil {
log.Println("Do error", err)
}

Related

pgx tls connection throws client cert invalid error for valid cert

I'm trying to use pgx to make a TLS connection to a postgres 10 db.
My connection string is similar to: "host='my-host.com' port='5432' dbname='my-db' user='my-db-user' sslmode='verify-full' sslcert='/path/to/db_user.crt' sslkey='/path/to/db_user.key' sslrootcert='/path/to/ca_roots.pem'"
When I run this directly with psql on the command-line, it works, so the cert and key files must be valid. db_user.crt and db_user.key are both PEM files. (the command-line also works with sslmode='verify-full', so the rootcert should also be ok)
But when I initialize a pgx pool with that connection string, it fails with:
FATAL: connection requires a valid client certificate (SQLSTATE 28000)
Is go expecting something other than PEM? Or is there a different way ssl cert and key pair is supposed to be initialized with pgx?
Code
import (
"context"
"fmt"
"os"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/pgxpool"
)
type mockLogger struct{}
func (ml *mockLogger) Log(ctx context.Context, level pgx.LogLevel, msg string, data map[string]interface{}) {
fmt.Printf("[%s] %s : %+v\n", level.String(), msg, data)
}
func connect() error {
connStr := "host='my-host.com' port='5432' dbname='my-db' user='my-db-user' sslmode='verify-full' sslcert='/path/to/db_user.crt' sslkey='/path/to/db_user.key' sslrootcert='/path/to/ca_roots.pem'"
poolCfg, err := pgxpool.ParseConfig(connStr)
if err != nil {
return err
}
poolCfg.ConnConfig.Logger = &mockLogger{}
poolCfg.ConnConfig.LogLevel = pgx.LogLevelTrace
fmt.Printf("using connection string: \"%s\"\n", poolCfg.ConnString())
connPool, err := pgxpool.ConnectConfig(context.TODO(), poolCfg)
if err != nil {
return err
}
connPool.Close()
return nil
}
func main() {
if err := connect(); err != nil {
fmt.Printf("%+v\n", err)
os.Exit(1)
}
}
Output from calling connect():
using connection string: "host='my-host.com' port='5432' dbname='my-db' user='my-db-user' sslmode='require' sslcert='/path/to/db_user.crt' sslkey='/path/to/db_user.key' sslrootcert='/path/to/ca_roots.pem'"
[info] Dialing PostgreSQL server : map[host:my-host.com]
[error] connect failed : map[err:failed to connect to `host=my-host.com user=my-db-user database=my-db`: server error (FATAL: connection requires a valid client certificate (SQLSTATE 28000))]
failed to connect to `host=my-host.com user=my-db-user database=my-db`: server error (FATAL: connection requires a valid client certificate (SQLSTATE 28000))
Summary
Turns out for go, the cert pointed to by sslcert needed to contain the full client cert chain.
When /path/to/db_user.crt contained the client cert followed by client cert chain, the pgx connection worked.
Whereas the psql command worked in both cases:
when sslcert was just the leaf client cert without the chain
when sslcert contained client cert + chain
Not sure why psql was fine without the full chain, but it works now.
Details
Under-the-hood, pgx uses the pgconn module to create the connection. That, in turn, is just calling tls.X509KeyPair on the contents of the sslcert and sslkey files.
pgconn/config.go:
func configTLS(settings map[string]string, thisHost string, parseConfigOptions ParseConfigOptions) ([]*tls.Config, error) {
[...]
sslcert := settings["sslcert"]
sslkey := settings["sslkey"]
[...]
if sslcert != "" && sslkey != "" {
[...]
certfile, err := ioutil.ReadFile(sslcert)
if err != nil {
return nil, fmt.Errorf("unable to read cert: %w", err)
}
cert, err := tls.X509KeyPair(certfile, pemKey)
if err != nil {
return nil, fmt.Errorf("unable to load cert: %w", err)
}
tlsConfig.Certificates = []tls.Certificate{cert}

401 Error when consuming an API from Go Code, while cURL Works well

I've written a simple go code that, sends a GET request to an API and in response I receive a 401 error. However when I use cURL, I receive the desired response. I also get expected response using API Tester. So, I believe, there has to be something wrong with my code and that, I'm unable to find out.
Below is my Go code, that, responds with 401 Error
func main() {
clusterId := os.Getenv("CLUSTER_ID")
apiUrl := "https://api.qubole.com/api/v1.3/clusters/"+clusterId+"/state"
auth_token := os.Getenv("X_AUTH_TOKEN")
fmt.Println("URL - ",apiUrl)
req, err := http.NewRequest("GET", apiUrl, nil)
if(err != nil){
fmt.Println("ERROR in getting rest response",err)
}
req.Header.Set("X-Auth-Token", auth_token)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if(err != nil){
fmt.Println("Error: No response received", err)
}
defer res.Body.Close()
//print raw response body for debugging purposes
body, _ := ioutil.ReadAll(res.Body)
fmt.Println(string(body))
}
Extract of Response/Error I get, is as follows:
URL - https://api.qubole.com/api/v1.3/clusters/presto-bi/state
{"error":{"error_code":401,"error_message":"Invalid Token"}}
Now, Following is the cURL command that, returns me the desired response
curl -X GET -H "X-AUTH-TOKEN:$X_AUTH_TOKEN" -H "Content-Type: application/json" -H "Accept: application/json" "https://us.qubole.com/api/v1.3/clusters/presto-bi/state"
below is the stdout I receive, which is as expected: {"state":"DOWN"}%
Need check api hostname at golang and curl again. Thanks!
The error is because, the documentation of API provider states the host WRONG (API Documentation) . But, since the portal login is us.qubole.com (PORTAL Login URL), cURL command was written considering that in mind.
I'm just guessing here without enough information. I'm assuming clusterId := os.Getenv("CLUSTER_ID") is presto-bi. If that is the case, then you are just missing "clusters" in your path.
apiUrl := "https://api.qubole.com/api/v1.3/clusters/"+clusterId+"/state"
Also, shouldn't you use us.qubole.com/api instead of api.qubole.com?

Golang HTTP POST succeeds, but it doesn't invoke a docker action

Problem: Although I can easily issue GET and POST commands from curl on my local docker socket, when I try to do the POST equivalent in Golang for a docker pull action, using net.Dial, I see no action taken on Docker's behalf.
Note that, meanwhile, the GET action works just fine using docker sockets via the golang client.
For example, when running the code at the bottom of this post, I see:
2018/01/05 14:16:33 Pulling http://localhost/v1.24/images/create?fromImage=fedora&tag=latest ......
2018/01/05 14:16:34 Succeeded pull for http://localhost/v1.24/images/create?fromImage=fedora&tag=latest &{200 OK 200 HTTP/1.1 1 1 map[Docker-Experimental:[true] Ostype:[linux] Server:[Docker/17.09.0-ce (linux)] Api-Version:[1.32] Content-Type:[application/json] Date:[Fri, 05 Jan 2018 19:16:34 GMT]] 0xc42010a100 -1 [chunked] false false map[] 0xc420102000 <nil>}
The code for attempting to do a pull via a POST action:
// pullImage is the equivalent of curl --unix-socket /var/run/docker.sock -X POST http://localhost/images/create?fromImage=alpine
func pullImage(img string, tag string) (err error) {
fd := func (proto, addr string) (conn net.Conn, err error) {
return net.Dial("unix", "/var/run/docker.sock")
}
tr := &http.Transport{
Dial: fd,
}
client := &http.Client{Transport: tr}
imageUrl := fmt.Sprintf("http://localhost/v1.24/images/create?fromImage=%s&tag=%s", img, tag)
log.Printf("Pulling %s ...... ", imageUrl)
resp, err := client.Post(imageUrl, "application/json", nil)
if resp.StatusCode == 200 && err == nil {
log.Printf("Succeeded pull for %s %v",imageUrl, resp)
} else {
log.Printf("FAILED pull for %s , ERROR = (( %s )) ",imageUrl , err)
}
return err
Question:
What does curl --unix-socket /var/run/docker.sock -X POST http://localhost/v1.24/images/create?fromImage=fedora do differently then the Golang code in the above snippet?
I figured this out. Actually, my prolem was that I wasn't completing the response body.
Adding
defer resp.Body.Close()
after the rest, err := client... clause was enough to finish triggering an entire pull. Not quite sure why closing the response body effects the ability of docker to start pulling the images down, but in any case, that was the case.

when does Go http.Get reuse the tcp connection?

in GO net/http Response Body annotation says:
It is the caller's responsibility to close Body. The default HTTP client's Transport does not attempt to reuse HTTP/1.0 or HTTP/1.1 TCP connections ("keep-alive") unless the Body is read to completion and is
closed.
It's mean: if I use http.Get and don't call resp.Body.Close() then it will not resue HTTP/1.0 or HTTP/1.1 TCP connections ("keep-alive") yeah?
so I write some code:
package main
import (
"time"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
resp, err := http.Get("http://127.0.0.1:8588")
if err != nil {
panic(err)
}
_, err = ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
resp2, err := http.Get("http://127.0.0.1:8588")
if err != nil {
panic(err)
}
_, err = ioutil.ReadAll(resp2.Body)
if err != nil {
panic(err)
}
fmt.Println("before time sleep")
time.Sleep(time.Second * 35)
}
and I only see ONE tcp connection build in wireshark, why?
I don't close res.Body so the http client should't be reuse the tcp connection.
this problem has been solved in
https://github.com/golang/go/issues/22954.
You have read it till the end in first occurence of line:
_, err = ioutil.ReadAll(resp.Body)
So the connection is ready to be resused. Try not to read and run again.

Sending mail on localhost smtp does not work

I am trying to send an email to the localhost stmp server. I am using fakesmtp program to receive email from localhost.
Look at following code snippet
package mail
import (
"encoding/base64"
"fmt"
"log"
"net/mail"
"net/smtp"
"strings"
)
func encodeRFC2047(String string) string {
// use mail's rfc2047 to encode any string
addr := mail.Address{String, ""}
return strings.Trim(addr.String(), " <>")
}
func Send() {
// Set up authentication information.
smtpServer := "127.0.0.1:2525"
auth := smtp.PlainAuth(
"",
"admin",
"admin",
smtpServer,
)
from := mail.Address{"example", "info#example.com"}
to := mail.Address{"customer", "customer#example.com"}
title := "Mail"
body := "This is an email confirmation."
header := make(map[string]string)
header["From"] = from.String()
header["To"] = to.String()
header["Subject"] = encodeRFC2047(title)
header["MIME-Version"] = "1.0"
header["Content-Type"] = "text/plain; charset=\"utf-8\""
header["Content-Transfer-Encoding"] = "base64"
message := ""
for k, v := range header {
message += fmt.Sprintf("%s: %s\r\n", k, v)
}
message += "\r\n" + base64.StdEncoding.EncodeToString([]byte(body))
// Connect to the server, authenticate, set the sender and recipient,
// and send the email all in one step.
err := smtp.SendMail(
smtpServer,
auth,
from.Address,
[]string{to.Address},
[]byte(message),
//[]byte("This is the email body."),
)
if err != nil {
log.Fatal(err)
}
}
When I executed the send function, I've got the error unencrypted connection. Why?
Most likely the server does not allow you to use plain-text authentication over an unencrypted connection, which is a sensible default for almost any MTA out there. Either change authentication info to e.g. digest, or enable SSL/TLS in you client code.
Remember to use tcpdump or wireshark to check what is actually transmitted.