# Beta NAT'ed Facility

**URL:** <https://forum.mergetb.net/t/beta-nated-facility/162>\
**Category:** Uncategorized\
**Created:** [January 13, 2026, 9:00pm UTC](https://forum.mergetb.net/t/beta-nated-facility/162 "2026-01-13T21:00:09Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![christra](https://forum.mergetb.net/letter_avatar_proxy/v4/letter/c/b77776/32.png) [@christra](https://forum.mergetb.net/u/christra)\
**Post date:** [January 13, 2026, 9:00pm UTC](https://forum.mergetb.net/t/beta-nated-facility/162/1 "2026-01-13T21:00:10Z")

</div>

This is current working in beta with manually applied k8s configs.

The short of it is that we have a wireguard container on the portal, exposed externally via ipvs and exposed internally via iptable forwarding rules and coredns dns rewrites.

# Internal Wireguard Configuration

## Wireguard Deployment

First, we have a wireguard deployment, configured via a configmap, which accepts Wireguard traffic on its port and forwards all other incoming traffic from the k8s side to the single remote IP address.

Right now, this only supports 1 remote NAT’ed facility, but I think it’s possible to differentiate between different hosts via IP addresses.

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nat-wg-gateway
  namespace: merge
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nat-wg-gateway
  template:
    metadata:
      labels:
        app: nat-wg-gateway
    spec:
      containers:
        - name: wireguard
          image: linuxserver/wireguard:latest
          securityContext:
            capabilities:
              add: ["NET_ADMIN", "SYS_MODULE"] # SYS_MODULE help with kernel hooks
          env:
            - name: PUID
              value: "1000"
            - name: PGID
              value: "1000"
            - name: TZ
              value: "UTC"
          volumeMounts:
            - name: nat-wg-config-volume
              mountPath: /config/wg_confs/wg0.conf
              subPath: wg0.conf
      volumes:
        - name: nat-wg-config-volume
          configMap:
            name: nat-wg-config
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: nat-wg-config
  namespace: merge
data:
  wg0.conf: |
    [Interface]
    Address = 10.138.0.1/24
    ListenPort = 31820
    PrivateKey = 

    # TODO: support multiple NAT points
    # handle TCP/UDP traffic
    PostUp = iptables -t nat -A PREROUTING -i eth0 -p tcp -j DNAT --to-destination 10.138.0.10
    PostUp = iptables -t nat -A PREROUTING -i eth0 -p udp ! --dport 31820 -j DNAT --to-destination 10.138.0.10

    # forwarding and return path masquerading
    PostUp = iptables -A FORWARD -i eth0 -o wg0 -j ACCEPT
    PostUp = iptables -A FORWARD -i wg0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT
    PostUp = iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE

    # Clean up rules when interface goes down
    PreDown = iptables -t nat -D PREROUTING -i eth0 ! --dport 31820 -j DNAT --to-destination 10.138.0.10
    PreDown = iptables -D FORWARD -i eth0 -o wg0 -j ACCEPT
    PreDown = iptables -t nat -D POSTROUTING -o wg0 -j MASQUERADE

    [Peer]
    PublicKey = 
    AllowedIPs = 10.138.0.10/32

```

## Services

```yaml
apiVersion: v1
kind: Service
metadata:
  name: nat-wg-external
  namespace: mergev1
spec:
  type: NodePort
  selector:
    app: nat-wg-gateway
  ports:
    - name: wg-handshake
      protocol: UDP
      port: 31820
      targetPort: 31820
      nodePort: 31820
---
apiVersion: v1
kind: Service
metadata:
  name: nat-api-connector
  namespace: mergev1
spec:
  clusterIP: None
  selector:
    app: nat-wg-gateway
  ports:
    - port: 6001
      name: grpc

```

Then, we expose it, once externally for `31820`via a NodePort and once internally via the `none` cluster IP. It’s probably possible to use a cluster ip, so we can differential between different facility destinations.

## DNS Configuration

In coredns’s configmap (`kubectl -n kube-system get cm coredns`), we add the line:

```auto
rewrite stop name exact api.csuautomotive.facility.local nat-api-connector.mergev1.svc.cluster.local answer auto 

```

This is so that when `api.csuautomotive.facility.local` , it gets translated to the service IP.

# External Wireguard Configuration

This is pretty much done with ipvsadm, were we use round robin selection and list each destination server.

```bash
sudo ipvsadm -C

# the "bind"
sudo ipvsadm -A -u 206.117.31.241:31820 -s rr

# round robin on them
# server sw1
sudo ipvsadm -a -u 206.117.31.241:31820 -r 10.0.0.19:31820 -m
...

```

# Future Work

The main steps that needs to be done is to:

- Put the facility’s private key into the model when requesting NAT, so that the portal can generate these configs and apply them
- Install these things via helm instead.
- Unify the management of external forwarding for GRPC, HTTPS, and Wireguard under Nginx Server.
- Support multiple NAT’d facility destinations.

---

<div class="post-metadata">

**Author:** ![jdbarnes](https://forum.mergetb.net/user_avatar/forum.mergetb.net/jdbarnes/32/57_2.png) [@jdbarnes](https://forum.mergetb.net/u/jdbarnes)\
**Post date:** [January 14, 2026, 1:13am UTC](https://forum.mergetb.net/t/beta-nated-facility/162/2 "2026-01-14T01:13:55Z")

</div>

it **SEEMS** like this would be possible to extrapolate out to certain services or something and some actual url/port with a bit of work. awesome first pass.
