-1

I have a HTTP REST service written in golang demonstrating what I'm attempting.

I want GET requests insecure and all other REST requests secured with MTLS. My implementation already uses the gin web server library so I'd like to stick with that if possible.

My issue is that I have only been able to apply the tlsConfig to both groups or neither. I've been unable to find a way to apply this at the group level.

package main

import (
    "crypto/tls"
    "crypto/x509"
    "errors"
    "fmt"
    "io/ioutil"
    "net/http"

    "log"

    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()

    // Unprotected public router for GET requests
    public := router.Group("/")

    // Private router with MTLS
    private := router.Group("/")

    public.GET("/insecure-ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "insecure pong",
        })
    })

    private.POST("/secure-ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "secure pong",
        })
    })

    // Get the SystemCertPool, continue with an empty pool on error
    rootCAs, err := x509.SystemCertPool()
    if err != nil {
        log.Fatal(err)
    }
    if rootCAs == nil {
        rootCAs = x509.NewCertPool()
    }

    // Create a CA certificate pool and add cacert.pem to it
    caCert, err := ioutil.ReadFile("cacert.pem")
    if err != nil {
        log.Fatal(err)
    }

    if ok := rootCAs.AppendCertsFromPEM(caCert); !ok {
        err := errors.New("failed to append CA cert to local system certificate pool")
        log.Fatal(err)
    }

    server := http.Server{
        Addr:    fmt.Sprintf(":%v", 8080),
        Handler: router,
    }

    server.TLSConfig = &tls.Config{
        RootCAs: rootCAs,
    }

    err = server.ListenAndServeTLS("certificate.crt", "privateKey.key")
    if err != nil {
        log.Fatal(err)
    }
}

James
  • 199
  • 6
  • 17

1 Answers1

0

Just create two Server instances and run them both, one with ListenAndServe and one with ListenAndServeTLS, configured with the same routes. Because HTTP and HTTPS operate on different ports, they have to have different listeners, but both listeners can use the same (or different) handlers. For example:

publicRouter := gin.Default()

// Unprotected public router for GET requests
public := publicRouter.Group("/")

public.GET("/insecure-ping", func(c *gin.Context) {
    c.JSON(200, gin.H{
        "message": "insecure pong",
    })
})

server := http.Server{
    Addr:    fmt.Sprintf(":%v", 8081), // Or whatever
    Handler: publicRouter,
}

go func() {
    err = tlsServer.ListenAndServe()
    if err != nil {
        log.Fatal(err)
    }
}()

// Private router with MTLS
router := gin.Default()
private := router.Group("/")

private.POST("/secure-ping", func(c *gin.Context) {
    c.JSON(200, gin.H{
        "message": "secure pong",
    })
})

// Get the SystemCertPool, continue with an empty pool on error
rootCAs, err := x509.SystemCertPool()
if err != nil {
    log.Fatal(err)
}
if rootCAs == nil {
    rootCAs = x509.NewCertPool()
}

// Create a CA certificate pool and add cacert.pem to it
caCert, err := ioutil.ReadFile("cacert.pem")
if err != nil {
    log.Fatal(err)
}

if ok := rootCAs.AppendCertsFromPEM(caCert); !ok {
    err := errors.New("failed to append CA cert to local system certificate pool")
    log.Fatal(err)
}

tlsServer := http.Server{
    Addr:    fmt.Sprintf(":%v", 8080),
    Handler: router,
}

tlsServer.TLSConfig = &tls.Config{
    RootCAs: rootCAs,
}

err = tlsServer.ListenAndServeTLS("certificate.crt", "privateKey.key")
if err != nil {
    log.Fatal(err)
}
Adrian
  • 42,911
  • 6
  • 107
  • 99
  • I intend to `ListenAndServeTLS` for both public and private endpoints, I just want to the rootCA to differ without the port changing (ideally) so that the server checks the client is authorised for private but not public. – James Oct 07 '21 at 20:31
  • That's fine, just add the public routes to the private router as well. – Adrian Oct 07 '21 at 20:57
  • But then the route CA would be applied to both endpoints, right? I want it only checked for the private one. – James Oct 07 '21 at 21:32
  • The CA isn't applied to the routes, it's applied to the server. In my example code there is one secure server and one insecure server. – Adrian Oct 08 '21 at 13:18