Confirm TLS certificate while performing ReverseProxy in GoLang - ssl

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)

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.

How to properly register RabbitMQ exclusive-consumer

Summary
It seems pretty simple to implement an exclusive consumer in RabbitMQ. You just need to enable the exclusive flag while starting the consumption or that's what I thought. I tried this approach but for some reason, it's creating multiple exclusive consumers which actually contradicts with it's own definition.
Setup
I am using following setup -
Exchange: default
Queue: quorum, durable
Messages: durable
Here is the full code that I am using to register exclusive consumer -
package main
import (
"fmt"
amqp "github.com/rabbitmq/amqp091-go"
"sync"
"time"
)
type Config struct {
Schema string
Host string
Port string
Username string
Password string
Vhost string
}
type Rabbit struct {
config Config
connection *amqp.Connection
lock sync.Mutex
}
// NewRabbit returns a Rabbit instance.
func NewRabbit() *Rabbit {
// setup appropriate values
config := Config{
Host: "",
Username: "",
Password: "",
Port: "",
Vhost: "",
Schema: "",
}
return &Rabbit{
config: config,
}
}
// Connect connects to RabbitMQ server.
func (r *Rabbit) Connect() error {
r.lock.Lock()
defer r.lock.Unlock()
// Check if connection is already available
if r.connection == nil || r.connection.IsClosed() {
// Try connecting
con, err := amqp.DialConfig(fmt.Sprintf(
"%s://%s:%s#%s:%s/%s",
r.config.Schema,
r.config.Username,
r.config.Password,
r.config.Host,
r.config.Port,
r.config.Vhost,
), amqp.Config{})
if err != nil {
return err
}
r.connection = con
}
return nil
}
func (r *Rabbit) StartConsumer(queueName string) error {
chn, err := r.connection.Channel()
if err != nil {
return err
}
// Make sure we process 1 message at a time
if err := chn.Qos(1, 0, false); err != nil {
return err
}
_, err = chn.QueueDeclare(
queueName,
true,
false,
false,
false,
amqp.Table{"x-queue-type": "quorum"}) // This will ensure that the created queue is quorum-queue
if err != nil {
fmt.Printf("Error creating queue with name: %s, err: %s", queueName, err.Error())
return err
}
messages, err := chn.Consume(
queueName,
queueName+"-consumer",
false,
true,
false,
false,
nil,
)
if err != nil {
fmt.Printf("Unable to start consumer for webhook queue: %s, err: %s", queueName, err.Error())
return err
}
go func() {
// This for-loop will wait indefinitely or until channel is closed
for msg := range messages {
fmt.Printf("Message: %v", msg.Body)
if err = msg.Ack(false); err != nil {
fmt.Printf("Unable to acknowledge the message, err: %s", err.Error())
}
}
}()
return nil
}
Here is the main.go
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
interrupt := make(chan os.Signal)
signal.Notify(interrupt, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(interrupt)
rabbit := NewRabbit()
if err := rabbit.Connect(); err != nil {
fmt.Printf("Can't connect to RabbitMQ server, err: %s", err.Error())
} else {
fmt.Println("Successfully connected to RabbitMQ server")
}
err := rabbit.StartConsumer("test-queue")
if err != nil {
fmt.Printf("Error: %s", err.Error())
}
select {
case <-interrupt:
fmt.Println("Interrupt signal received")
break
}
fmt.Println("Application is about to close")
}
Here is go.mod
module ExclusiveRabbitMQConsumer
go 1.17
require github.com/rabbitmq/amqp091-go v1.3.4
And here is what I see after running 3 instances of this application -
RabbitMQ Management UI
All 3 consumers are shown as exclusive. The consumer tags are same however that doesn't matter according to RabbitMQ documentation.
I'm not sure what's wrong here.

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)

How to configure SSL authentication

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(.....)