Service to Service TLS
article.featured

Service to Service TLS in Development

Ben Burkert Profile Pic
Ben Burkert
November 1, 2023

Anchor makes it quick and easy to add TLS encryption between internal services. Running a private X.509 certificate authority (CA) is difficult and tricky, ask any developer who’s had the misfortune of needing one. But Anchor changes that; see how easy it is to secure services with internal TLS using Anchor.

When you sign up for Anchor, a dedicated private and fully managed CA is automatically provisioned for your development environment (for free!). You can use your CA with any standard ACME client (most languages have one) to provision server certificates for your services in development. For the service’s clients, we build language and OS packages so that clients are configured to trust the server certificates.

In this article we’ll cover setting up TLS between a Ruby on Rails client application and a RESTful Go API application. At the end, the apps will communicate via encrypted HTTPS instead of unencrypted HTTP requests. All entirely within our local development environment.

Getting Started

Before diving straight into service-to-service TLS, we recommend working through our “Getting Started with Anchor for Local Development” article. It only takes a few minutes, and it covers everything you need to use HTTPS/TLS in your local development environment with lcl.host subdomains. And for simplicity, the rails-demo app will be re-used as the client for this service-to-service TLS example.

The example services will be a Ruby on Rails frontend app talking to a Go API backend app, but any combination of supported languages work just as well. Check out our docs for instructions on how to use other languages with Anchor.

Service Setup

The first step is to create the new backend service: sign in to the Anchor dashboard, then click the New Service button to jump to the service form. Fill in the service name go-api and language Go. In the Clients section, add a new client named rails-demo and set the type as Ruby. Stick with the default values for the rest, and click the Create Service button.

Service Setup

Server Step

Next up is the Server Setup instructions, but first we need to create the go-api app on our development machine. Start by creating a new Go project:

$ mkdir go-api && cd go-api $ go mod init

And add the following main.go program:

package main

import (
  "crypto/tls"
  "encoding/base64"
  "log"
  "net/http"
  "os"
  "time"

  "golang.org/x/crypto/acme"
  "golang.org/x/crypto/acme/autocert"
)

func main() {
  // setup the API server
  srv := &http.Server{
    Handler: http.HandlerFunc(api),
  }

  // load the secret portion of the ACME EAB token
  acmeKey, err := base64.RawURLEncoding.DecodeString(os.Getenv("ACME_HMAC_KEY"))
  if err != nil {
    log.Fatal(err)
  }

  // configure TLS via ACME provisioned certificates
  cfg := &tls.Config{
    GetCertificate: (&autocert.Manager{
      Prompt:      autocert.AcceptTOS,
      HostPolicy:  autocert.HostWhitelist(os.Getenv("HOST")),
      RenewBefore: 336 * time.Hour, // 14 days

      Client: &acme.Client{
        DirectoryURL: os.Getenv("ACME_DIRECTORY_URL"),
      },

      ExternalAccountBinding: &acme.ExternalAccountBinding{
        KID: os.Getenv("ACME_KID"),
        Key: acmeKey,
      },
    }).GetCertificate,
  }

  // provision a certificate and create the TLS listener
  ln, _ := tls.Listen("tcp", os.Getenv("ADDR"), cfg)
  if err != nil {
    log.Fatal(err)
  }

   // Start the https server
   log.Fatal(srv.Serve(ln))
}

func api(w http.ResponseWriter, r *http.Request) {
  w.Write([]byte("pong!\n"))
}

Then run go get to install the dependencies:

$ go get .

Skip to section 2: Server Setup of the Setup Guide, generate an EAB token set, export the environment variables, and start the go-api server.

Anchor Setup2

Open the API endpoint in your browser, you should see the pong! message.

Pong!

If you see a “Not Secure” warning in the browser, most likely your local development machine has not been set up with the Anchor CLI toolchain. Step 4: Anchor Toolchain of the Setup Guide has instructions to download and install the toolchain.

Client Step

To configure the rails app as a client, start by downloading the ruby package to the rails-demo directory using the Download Package button. Then use the gem and bundler commands to add it as dependency:

$ gem install ./path/to/username-localhost-pki-0.1.0.gem $ bundler add username-localhost-pki --group development

Be sure to swap username with your GitHub handle!

Generate a new controller and /ping endpoint in the rails app:

$ rails generate controller ping

And add a get ‘/ping’, to: ‘ping#index’ route to config/routes.rb:

Rails.application.routes.draw do get '/ping', to: 'ping#index' # ... end

In the new PingController class, add an index action based on section 3: Ruby client setup of the Setup Guide:

class PingController < ApplicationController def index client = Net::HTTP.start('go-api.lcl.host', 'PORT', use_ssl: true, cert_store: Anchor.cert_store) render plain: client.get('/ping').body end end

Start the rails server, and in the browser load the /ping endpoint. The pong! response has been proxied through the rails app from the go-api app.

Pong2

The cert_store: Anchor.cert_store parameter configures the HTTP client to trust the go-api.lcl.host server cert. Removing it causes the client to raise a “self-signed certificate” error.

Next Steps

Your rails app is now connected to the Go API using internal TLS for encryption with your development X.509 CA hosted by Anchor. Try adding a second API client in a different language, and check out our docs for more info. Or take an existing application and set up TLS/HTTPS in local development. The best part about switching your app to use internal TLS in development is that your app now ready for internal TLS in your staging & production environments.

If you have any comments, run into trouble, or are interested in joining our early access program for staging & production environments, please get in touch via email or through our discord community.