How to configure SSL authentication - ssl

I have certificate.pem that I use to perform client authentication with a remote server. When I access the server, normally Chrome pops up, asks if I want to use that certificate, I say yes, then I'm authenticated. I'm trying to figure out why it's not sending the certificate with the dialer when I call it programmatically:
type DialerHelper func() (io.ReadWriter, error)
func DialIt(addr string, port uint16, config *tls.Config) (Dialer, error) {
address := fmt.Sprintf("%s:%d", addr, port)
return DialerHelper(func() (io.ReadWriter, error) {
return tls.Dial("tcp", address, config)
}), nil
}
caPool := x509.NewCertPool()
cert, err := ioutil.ReadFile("certificate.pem")
if err != nil {
panic(err)
}
ok := caPool.AppendCertsFromPEM(cert)
if !ok {
panic(ok)
}
tlsconfig := &tls.Config{
InsecureSkipVerify: true,
RootCAs: caPool, }
tlsconfig.BuildNameToCertificate()
DialIt("some.address.com", 443, tlsconfig)
I keep getting an error from the server saying there is no client certificate supplied. Am I sending the SSL certificate correctly to the remote server? I'm not an expert with SSL.
Edit: this is the functionality I'm trying to replicate: curl -k --cert /home/me/.ssh/certificate.pem

If the server is using a cert generated from your own Certificate Authority, then the following code will do the trick.
I've never tried Client Cert Authentication in an environment where the server cert is from a public CA, so I'm not sure how you'd achieve that. Perhaps just leaving out setting config.RootCAs.
func loadCertificates(caFileName, certFileName, keyFileName string) (tls.Certificate, *x509.CertPool, error) {
myCert, err := tls.LoadX509KeyPair(certFileName, keyFileName)
if err != nil {
return tls.Certificate{}, nil, err
}
ca, err := ioutil.ReadFile(caFileName)
if err != nil {
return tls.Certificate{}, nil, err
}
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(ca) {
return tls.Certificate{}, nil, errors.New("Failed appending certs")
}
return myCert, certPool, nil
}
func GetClientTlsConfiguration(caFileName, certFileName, keyFileName string) (*tls.Config, error) {
config := &tls.Config{}
myCert, certPool, err := loadCertificates(caFileName, certFileName, keyFileName)
if err != nil {
return nil, err
}
config.Certificates = make([]tls.Certificate, 1)
config.Certificates[0] = myCert
config.RootCAs = certPool
config.ClientCAs = certPool
return config, nil
}
tlsConfig, err := config.GetClientTlsConfiguration("ca.crt", "client.crt", "client.key")
if err != nil {
log.Fatalf("Error loading tls config - %v", err)
}
client := &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}}
client.Get(.....)

Related

Go how to use cookiejar for multiple requests?

I'm trying to make a cli for a site that has csrf, needing the csrf token to be sent on headers and on a form.
I can't seem to understand net/http.Client or net/http/cookieJar
This its even good practice? There's a better way of doing csrf login on Go ?
Thx in advance ^v^
This its my code:
package main
import (
"fmt"
"log"
"net/http"
"net/http/cookiejar"
"net/url"
"strings"
"time"
)
var (
httpClient = &http.Client{}
)
func main() {
jar, err := cookiejar.New(nil)
if err != nil {
log.Fatal(err)
}
httpClient = &http.Client{
Timeout: 30 * time.Second,
Jar: jar,
}
requestURL := "https://example.com/"
res, err := httpClient.Get(requestURL)
if err != nil {
log.Fatal(err)
}
log.Println(res.Cookies())
// stdout: cookie as expected
u := &url.URL{}
u.Parse(requestURL)
log.Println(httpClient.Jar.Cookies(u))
// stdout: []
form := make(url.Values)
/* ... */
req, err := http.NewRequest(http.MethodPost, requestURL, strings.NewReader(form.Encode()))
if err != nil {
fmt.Printf("client: could not create request: %s\n", err)
}
res, err = httpClient.Do(req)
if err != nil {
log.Fatal(err)
}
fmt.Println(req)
// stdout: cookie as expected
}

Go - Running cucumbers that uses an API

I'm using the Godog library to implement some cucumbers tests for my api code, right now I'm only testing one endpoint but I'm hitting an error where it looks like it's expecting to have a server open. I created a httptest server that listens to port 8080 but the tests are failing with a 404.
If I run my cucumber in debug mode they work but if I use the run test command they fail cos the expect an open port dial tcp localhost:8080. Could someone point me to the right direction since I quite don't know where I'm failing.
This is my godog_test
`
func mockServer() *httptest.Server {
router := mux.NewRouter()
u, _ := url.Parse("http://localhost:8080")
l, _ := net.Listen("tcp", u.Host)
server := httptest.NewUnstartedServer(router)
_ = server.Listener.Close()
server.Listener = l
server.Start()
return server
}
func killMockServer(server *httptest.Server) {
server.Close()
}
func TestFeatures(t *testing.T) {
suite := godog.TestSuite{
TestSuiteInitializer: InitializeTestSuite,
ScenarioInitializer: InitializeScenario,
Options: &godog.Options{
Format: "pretty",
Paths: []string{"features"},
TestingT: t,
},
}
if suite.Run() != 0 {
t.Fatal("non-zero status returned, failed to run feature tests")
}
}
func InitializeTestSuite(ctx *godog.TestSuiteContext) {
var server *httptest.Server
ctx.BeforeSuite(func() {
server = mockServer()
})
ctx.AfterSuite(func() {
fmt.Println("shutting down everything")
killMockServer(server)
})
}
`
Post step that I'm testing
`
func iCallPOSTTo(path string) error {
req, err := json.Marshal(reqBody)
if err != nil {
return err
}
request, err := http.NewRequest(
http.MethodPost,
endpoint+path,
bytes.NewReader(reqBody),
)
res, err := http.DefaultClient.Do(request)
if err != nil {
return err
}
resBody, err := io.ReadAll(res.Body)
if err != nil {
return err
}
res.Body.Close()
[REDACTED]
return nil
}
`
I tried using a mock server to open port 8080 since at first I was receiving a connection refused error, after that I'm getting a 404 which means that my test is not reaching my actual function that processes the post request. I'm not sure if the mock server is the correct approach on this case.

400 Bad Request for frappe.cloud API

I'm getting 400 Bad Request for frappe.cloud API, when I'm trying to call it using golang code using http.NewRequest, this API is working fine when I check it using postman. following is the API
https://xxxx.frappe.cloud/api/resource/Item?fields=["name","item_name","item_group","description"]&filters=[["Item","item_group","=","xxx Product"]]
If I use the same golang code to call same API with out filters it works fine. following is the working API
https://xxxx.frappe.cloud/api/resource/Item?fields=["name","item_name","item_group","description"]
code as follows
func FetchProperties(dataChannel models.DataChannel) (map[string]interface{}, error) {
thisMap := make(map[string][]map[string]interface{})
client := &http.Client{}
req, err := http.NewRequest("GET", dataChannel.APIPath, nil)
if err != nil {
commons.ErrorLogger.Println(err.Error())
return nil, err
}
eds, err := GetDecryptedEDSByEDSID(dataChannel.EDSId)
if err != nil {
commons.ErrorLogger.Println(err.Error())
return nil, &commons.RequestError{StatusCode: 400, Err: err}
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", eds.DataSource.Auth.Token)
response, err := client.Do(req)
if err != nil {
commons.ErrorLogger.Println(err.Error())
return nil, err
}
data, err := ioutil.ReadAll(response.Body)
if err != nil {
commons.ErrorLogger.Println(err.Error())
return nil, &commons.RequestError{StatusCode: 400, Err: err}
}
if response.StatusCode == 200 {
err = json.Unmarshal(data, &thisMap)
if err != nil {
commons.ErrorLogger.Println(err.Error())
return nil, &commons.RequestError{StatusCode: 400, Err: err}
}
return thisMap["data"][0], err
} else {
return nil, &commons.RequestError{StatusCode: response.StatusCode, Err: errors.New("getting " + strconv.Itoa(response.StatusCode) + " From Data channel API")}
}
Postman has an option to convert request to programming language equivalent.
Here is a working go code for sending the request. package main
import (
"fmt"
"strings"
"net/http"
"io/ioutil"
)
func main() {
url := "https://xxx.frappe.cloud/api/resource/Item?fields=%5B%22name%22,%22item_name%22,%22item_group%22,%22description%22%5D&filters=%5B%5B%22Item%22,%22item_group%22,%22=%22,%22xxx%20Product%22%5D%5D%0A"
method := "GET"
payload := strings.NewReader(`{
"payload": {},
"url_key": "",
"req_type": ""
}`)
client := &http.Client {
}
req, err := http.NewRequest(method, url, payload)
if err != nil {
fmt.Println(err)
return
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Cookie", "full_name=foo; sid=secret_sid; system_user=yes; user_id=foobar; user_image=")
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}

How to use TLS in Go ReverseProxy?

I have sites https://a-b-c.com and https://www.x-y-z.com running behind a reverse proxy on ports 4444 and 5555 respectively.
I had them configured to use letsencrypt tls certificates, but now I get an error when using the reverse proxy and I think I need to use a &tls.config{} that includes their certificates but I don't know how to set it up.
My ReverseProxy looks like:
director := func(req *http.Request) {
log.Println(req.Host)
switch req.Host {
case "a-b-c-.com":
req.URL.Host = "localhost:4444"
req.URL.Scheme = "https"
case "x-y-z.com":
req.URL.Host = "localhost:5555"
req.URL.Scheme = "https"
}
proxy := &httputil.ReverseProxy{Director: director}
proxy.Transport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
log.Fatalln(http.ListenAndServe(":443", proxy))
You don't have to set up your reverse proxy. What you need to do is to set up HTTP server that you attach your reverse proxy to.
Configure TLS listener by passing two pairs of cert/keys:
abcCrt, err := tls.LoadX509KeyPair("a-b-c.crt", "a-b-c.key")
if err != nil {
panic(err)
}
xyzCrt, err := tls.LoadX509KeyPair("x-y-z.crt", "z-y-z.key")
if err != nil {
panic(err)
}
tlsConfig := &tls.Config{Certificates: []tls.Certificate{abcCrt, xyzCrt}}
ln, err := tls.Listen("tcp", ":443", tlsConfig)
if err != nil {
panic(err)
}
http.Serve(ln, proxy)

Confirm TLS certificate while performing ReverseProxy in GoLang

In Go I'm using NewSingleHostReverseProxy to perform a reverse proxy, however I need to confirm the SSL certificates of the host site, to make sure I have the correct secure certificate... any ideas how I should do this? Should I be doing this with the handler or transport? I'm new to Go and still getting my head around it.
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
Scheme: "https",
Host: "sha256.badssl.com",
})
http.ListenAndServe("127.0.0.1:80", proxy)
To access the certificate, you will have get access to the ConnectionState. The easiest way to do that is to provide your own version of DialTLS. In there you connect to the server using net.Dial, do the TLS handshake and then you are free to verify.
package main
import (
"crypto/tls"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
)
func main() {
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
Scheme: "https",
Host: "sha256.badssl.com",
})
// Set a custom DialTLS to access the TLS connection state
proxy.Transport = &http.Transport{DialTLS: dialTLS}
// Change req.Host so badssl.com host check is passed
director := proxy.Director
proxy.Director = func(req *http.Request) {
director(req)
req.Host = req.URL.Host
}
log.Fatal(http.ListenAndServe("127.0.0.1:3000", proxy))
}
func dialTLS(network, addr string) (net.Conn, error) {
conn, err := net.Dial(network, addr)
if err != nil {
return nil, err
}
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
cfg := &tls.Config{ServerName: host}
tlsConn := tls.Client(conn, cfg)
if err := tlsConn.Handshake(); err != nil {
conn.Close()
return nil, err
}
cs := tlsConn.ConnectionState()
cert := cs.PeerCertificates[0]
// Verify here
cert.VerifyHostname(host)
log.Println(cert.Subject)
return tlsConn, nil
}
To tweak the SSL to the Reverse Host, it is possible to set the transport
options. So if you want to skip the verify you can set it like this.
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
Scheme: "https",
Host: "sha256.badssl.com",
})
proxy.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
http.ListenAndServe("127.0.0.1:80", proxy)