Skip to content

Instantly share code, notes, and snippets.

@czyt
Forked from acmacalister/README.md
Created January 1, 2023 02:41
Show Gist options
  • Save czyt/2408752b275742952657ca9a0a51b139 to your computer and use it in GitHub Desktop.
Save czyt/2408752b275742952657ca9a0a51b139 to your computer and use it in GitHub Desktop.
WebSocket through Cloudflare Access

Overview

These are instructions on setting up Access in front of a origin serving WebSockets. This example contains client code for a client that authenticates through Access using mTLS.

Access

In the Cloudflare Dashboard, configure an Access policy that contains a service token. The developer docs has steps on how to do this:

https://developers.cloudflare.com/access/service-auth/mtls-testing/ https://developers.cloudflare.com/access/setting-up-access/configuring-access-policies/

A easy way to test this is using Argo Tunnel. This can be configured following the developer docs here:

https://developers.cloudflare.com/access/setting-up-access/argo-tunnel/

Assuming that is complete, after starting the test WebSocket server both on the default localhost:8000, Argo Tunnel can serve traffic using this command:

cloudflared tunnel --hostname <your-domain-here> --url localhost:8000

Client

running go run client.go will start the client. For this example, a client certificate is used to authenicate through the Access policy. The default client certificate name is client-ca.pem, but can be modified using the -cert flag. The client certificate generated in the docs open may need to be concatenated to provide the full client certificate to the request.

Server

The server can be run with go run main.go. It will serve WebSocket traffic on port 8000. An addr flag can change the address if desired.

// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ignore
package main
import (
"crypto/tls"
"crypto/x509"
"flag"
"io/ioutil"
"log"
"net/url"
"os"
"os/signal"
"time"
"github.com/gorilla/websocket"
)
var (
addr = flag.String("addr", "ssh.austincherry.me", "for connecting to WebSocket server")
cert = flag.String("cert", "client-ca.pem", "client certificate for Access")
)
func main() {
flag.Parse()
log.SetFlags(0)
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo"}
log.Printf("connecting to %s", u.String())
caCert, err := ioutil.ReadFile(*cert)
if err != nil {
log.Fatal(err)
}
caCertPool, err := x509.SystemCertPool()
if err != nil {
log.Fatal(err)
}
caCertPool.AppendCertsFromPEM(caCert)
d := &websocket.Dialer{TLSClientConfig: &tls.Config{
RootCAs: caCertPool,
}}
c, _, err := d.Dial(u.String(), nil)
if err != nil {
log.Fatal("dial:", err)
}
defer c.Close()
done := make(chan struct{})
go func() {
defer close(done)
for {
_, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
return
}
log.Printf("recv: %s", message)
}
}()
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-done:
return
case t := <-ticker.C:
err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
if err != nil {
log.Println("write:", err)
return
}
case <-interrupt:
log.Println("interrupt")
// Cleanly close the connection by sending a close message and then
// waiting (with timeout) for the server to close the connection.
err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil {
log.Println("write close:", err)
return
}
select {
case <-done:
case <-time.After(time.Second):
}
return
}
}
}
package main
import (
"flag"
"log"
"net/http"
"github.com/gorilla/websocket"
)
var addr = flag.String("addr", "localhost:8000", "http service address")
var upgrader = websocket.Upgrader{CheckOrigin: (func(r *http.Request) bool {
return true
}),
}
func echo(w http.ResponseWriter, r *http.Request) {
log.Println("hi there request...", r.Host)
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
for {
mt, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
log.Printf("recv: %s", message)
err = c.WriteMessage(mt, message)
if err != nil {
log.Println("write:", err)
break
}
}
}
func main() {
flag.Parse()
log.SetFlags(0)
http.HandleFunc("/", echo)
log.Fatal(http.ListenAndServe(*addr, nil))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment