Custom certificates for TLS termination in layer4 app

Howdy @rcm25, welcome to the Caddy community.

Doesn’t look like you can do this with a Caddyfile, or if you can, it’s not documented. Might be worth opening a feature request at Issues · mholt/caddy-l4 · GitHub.

I felt like seeing if I could do this in JSON, though, and I can see that layer4 provides a connection_policies field identical to the HTTP server’s tls_connection_policies.

That means we can use much the same logic as the HTTP Caddyfile but port it over to JSON.

So, I took your Caddyfile config (by the way, it didn’t validate for me with localhost at the top because Caddy treats that as a site address for the obviously global option block below it) and adapted it to JSON:

Caddyfile adapted to JSON
{
  "apps": {
    "layer4": {
      "servers": {
        "srv0": {
          "listen": [
            ":1883"
          ],
          "routes": [
            {
              "handle": [
                {
                  "handler": "proxy",
                  "upstreams": [
                    {
                      "dial": [
                        "mosquitto:1883"
                      ]
                    }
                  ]
                }
              ]
            }
          ]
        },
        "srv1": {
          "listen": [
            ":8883"
          ],
          "routes": [
            {
              "handle": [
                {
                  "handler": "tls"
                },
                {
                  "handler": "proxy",
                  "upstreams": [
                    {
                      "dial": [
                        "mosquitto:1883"
                      ]
                    }
                  ]
                }
              ]
            }
          ]
        }
      }
    },
    "pki": {
      "certificate_authorities": {
        "local": {
          "install_trust": false
        }
      }
    },
    "tls": {
      "automation": {
        "policies": [
          {
            "issuers": [
              {
                "module": "internal"
              }
            ]
          }
        ]
      }
    }
  }
}

Then I took a simple Caddyfile:

example.com
tls cert key

And adapted that too:

tls cert key adapted to JSON
{
  "apps": {
    "http": {
      "servers": {
        "srv0": {
          "listen": [
            ":443"
          ],
          "routes": [
            {
              "match": [
                {
                  "host": [
                    "example.com"
                  ]
                }
              ],
              "terminal": true
            }
          ],
          "tls_connection_policies": [
            {
              "match": {
                "sni": [
                  "example.com"
                ]
              },
              "certificate_selection": {
                "any_tag": [
                  "cert0"
                ]
              }
            },
            {}
          ]
        }
      }
    },
    "tls": {
      "certificates": {
        "load_files": [
          {
            "certificate": "cert",
            "key": "key",
            "tags": [
              "cert0"
            ]
          }
        ]
      }
    }
  }
}

So we can see it does a few specific things.

Under the tls field we see it loads the file and gives it a tag:

      "certificates": {
        "load_files": [
          {
            "certificate": "cert",
            "key": "key",
            "tags": [
              "cert0"
            ]
          }
        ]
      }

Then, we can see under the http app that we’re specifying this tagged cert for certificate selection:

          "tls_connection_policies": [
            {
              "match": {
                "sni": [
                  "example.com"
                ]
              },
              "certificate_selection": {
                "any_tag": [
                  "cert0"
                ]
              }
            },
            {}
          ]

I don’t think you need to match on SNI here since it seems like you just want this port for the one upstream, so you can just skip match and use certificate_selection.

So if we take the certificates > load files as well as the connection_policies adpated from the tls cert key Caddyfile and put them in the JSON we adapted from your Caddyfile, we get something that looks like:

{
  "apps": {
    "layer4": {
      "servers": {
        "srv0": {
          "listen": [
            ":1883"
          ],
          "routes": [
            {
              "handle": [
                {
                  "handler": "proxy",
                  "upstreams": [
                    {
                      "dial": [
                        "mosquitto:1883"
                      ]
                    }
                  ]
                }
              ]
            }
          ]
        },
        "srv1": {
          "listen": [
            ":8883"
          ],
          "routes": [
            {
              "handle": [
                {
                  "handler": "tls",
                  "connection_policies": [
                    {
                      "certificate_selection": {
                        "any_tag": [
                          "cert0"
                        ]
                      }
                    }
                  ]
                },
                {
                  "handler": "proxy",
                  "upstreams": [
                    {
                      "dial": [
                        "mosquitto:1883"
                      ]
                    }
                  ]
                }
              ]
            }
          ]
        }
      }
    },
    "pki": {
      "certificate_authorities": {
        "local": {
          "install_trust": false
        }
      }
    },
    "tls": {
      "certificates": {
        "load_files": [
          {
            "certificate": "/etc/ssl/tls.crt",
            "key": "/etc/ssl/tls.key",
            "tags": [
              "cert0"
            ]
          }
        ]
      },
      "automation": {
        "policies": [
          {
            "issuers": [
              {
                "module": "internal"
              }
            ]
          }
        ]
      }
    }
  }
}
Or a YAML variant for use with github.com/abiosoft/caddy-yaml
apps:
  layer4:
    servers:
      srv0:
        listen:
        - ":1883"
        routes:
        - handle:
          - handler: proxy
            upstreams:
            - dial:
              - mosquitto:1883
      srv1:
        listen:
        - ":8883"
        routes:
        - handle:
          - handler: tls
            connection_policies:
            - certificate_selection:
                any_tag:
                - cert0
          - handler: proxy
            upstreams:
            - dial:
              - mosquitto:1883
  pki:
    certificate_authorities:
      local:
        install_trust: false
  tls:
    certificates:
      load_files:
      - certificate: "/etc/ssl/tls.crt"
        key: "/etc/ssl/tls.key"
        tags:
        - cert0
    automation:
      policies:
      - issuers:
        - module: internal

Which I imagine might just make it work for you.

4 Likes