March 9, 2023
|
10
min read

Introducing ngrok-go: Ingress to Your Go Apps as a net.Listener

Alan Shreve

Today, we're excited to announce ngrok-go, our idiomatic open-source Go package for embedding secure ingress directly into your Go applications. If you’ve used ngrok before, you can think of ngrok-go as the ngrok agent packaged as a Go library.

ngrok-go lets developers serve Go apps on the internet in a single line of code without setting up low-level network primitives like IPs, certificates, load balancers and even ports! Applications using ngrok-go listen on ngrok’s global ingress network but they receive the same interface any Go app would expect (<code>net.Listener</code>) as if it listened on a local port by calling <code>net.Listen()</code>. This makes it effortless to integrate ngrok-go into any application that uses Go's <code>net</code> or <code>net/http</code> packages.

Try it out

Check out how easy it is to run the classic Go hello world web app with ngrok-go:

package main
import (
   "context"
   "fmt"
   "log"
   "net/http"
   "golang.ngrok.com/ngrok"
   "golang.ngrok.com/ngrok/config"
)

func main() {
   ctx := context.Background()
   l, err := ngrok.Listen(ctx, 
       config.HTTPEndpoint(), 
       ngrok.WithAuthtokenFromEnv(),
   )
   if err != nil {
       log.Fatal(err)
   }
   fmt.Println("ngrok ingress url: ", l.Addr())
   http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
       fmt.Fprintln(w, "Hello from your ngrok-delivered Go app!")
   }))
}

Add your ngrok authtoken to your environment as <code>NGROK_AUTHTOKEN</code> and run the above code. Your app is on the internet and ready to receive requests with a simple <code>ngrok.Listen()</code>!

It is that simple. There is no additional configuration or code required. In this example, we didn’t request a specific domain, so your app is assigned a URL (e.g. https://myapp.ngrok.io) which is available as <code>l.Addr()</code>.

How ngrok-go works

The example app above doesn’t listen on any ports, how is that possible? When you call <code>ngrok.Listen()</code>, ngrok-go initiates a secure and persistent outbound TLS connection to ngrok’s ingress-as-a-service platform and transmits your configuration requirements — i.e. URL, authentication, webhook verification, and IP restrictions. The ngrok service sets up your configuration across all of our global points of presence in seconds and returns a URL for your application.

"How ngrok-go creates ingress"
How ngrok-go creates ingress

After your ingress is set up, ngrok receives HTTP requests at the closest region to the requester and enforces the middleware policies defined by your application. Unauthorized requests are blocked at the edge and only valid requests reach your ngrok-go app via the persistent TLS connection.

"How requests are handled and delivered to your go app"
How requests are handled and delivered to your go app

Why we built ngrok-go

Ingress should be a high-level abstraction

Creating ingress today is a frustrating exercise of wrangling a slew of disparate low level networking primitives. Developers must manage a number of technologies at different layers of the network stack like DNS, TLS Certificates, network-level CIDR policies, IP and subnet routing, load balancing, VPNs and NATs, just to name a few. In short, developers are being forced to work with the assembly language of networking.

We built ngrok-go to empower application developers to declare ingress policies at a high layer of abstraction without sacrificing security and control. As an example, here’s how ngrok-go allows developers to specify ingress with declarative options by removing the burden of working with low-level details:

l, err := ngrok.Listen(ctx, config.HTTPEndpoint(
    	config.WithDomain("app.my-domain.com"),
    	config.WithAllowCIDRString("200.2.0.0/16"),
    	config.WithCircuitBreaker(0.8),
    	config.WithCompression(),
    	config.WithOAuth("google", config.WithAllowOAuthDomain("example.com")),
    ),
    ngrok.WithAuthtokenFromEnv(),
)

A complete list of supported middleware configurations can be found in the ngrok-go API reference.

Ingress should be environment-independent

Traditionally, ingress is tightly coupled to the environment where your app is deployed. For example, the same app deployed to your own datacenter, an EC2 instance, or a Kubernetes cluster requires wildly different networking setups. Running your app in those three different environments means you need to manage ingress in three different ways.

ngrok-go decouples your app’s ingress from the environment where it runs.

When your application uses ngrok-go, you can run it anywhere and it will receive traffic the same way. From an ingress standpoint, your application becomes portable: it does not matter whether it runs on bare metal, VMs, AWS, Azure, in Kubernetes, serverless platforms like Heroku or Render, a racked data-center server, a Raspberry Pi, or on your laptop.

Ingress shouldn’t require sidecars

Developers often distribute the ngrok agent alongside their own applications to create ingress for their IoT devices, SaaS offerings, and CI/CD pipelines. It can be challenging to bundle and manage the ngrok agent as a sidecar process. ngrok-go eliminates the agent, simplifying distribution and management as well as enabling developers to easily deliver private label experiences.

How we designed ngrok-go

ngrok-go was designed with developer ergonomics top of mind and to seamlessly fit into the Go ecosystem:

  • Compatible with any code that accepts a net.Listener: ngrok-go returns the standard library’s <code>net.Listener</code> interface. Developers can easily integrate ngrok-go into existing Go network code like <code>http.Server</code> without changes.
  • Works in one line: ngrok-go can give you ingress with a simple <code>ngrok.Listen(ctx, config.HTTPEndpoint())</code> call.
  • Idiomatic functional options: ngrok-go enables developers to declare behaviors like authentication in a simple way, e.g. <code>config.WithOAuth("google")</code>.
  • Batteries included: ngrok-go includes logging adapters for the most common Go libraries like <code>zap</code> and <code>logrus</code>.

We validated ngrok-go’s design by dogfooding it in the ngrok agent and our soon-to-be-released Kubernetes ingress controller. We also worked with fellow gophers and customers like Airplane and Kubefirst for additional feedback on the design.

Will ngrok support other programming languages?

Absolutely! We’re actively building libraries for other programming languages. Rust (ngrok-rs) and JavaScript (ngrok-js) are in active development and you can follow along on GitHub. Expect other languages like Java, C#, Python and Ruby to follow. We’re eager for your feedback on which language to prioritize next.

Get started with ngrok-go

To get started and stay up to date with ngrok-go, try the following resources:

Share this post
Alan Shreve
Alan Shreve is no stranger to building distributed systems at scale. He organically grew ngrok from zero to 5 million users before raising a $50 million Series A. He’s dedicated to empowering developers to build a safer and more secure internet.