ziti-tunnel/

directory
v0.26.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 14, 2022 License: Apache-2.0

README

ziti-tunnel

ziti-tunnel intercepts outbound transport layer packets to Ziti service addresses and proxies them through Ziti.

The services that are proxied are determined by the app-wan membership of the identity that is defined in the configuration file.

Configuration File

A configuration file is created when an identity is successfully enrolled with a Ziti edge controller. Here's an example of a configuration file that ziti-tunnel accepts:

{
   "ztAPI": "https://ziti-dev-controller01.localhost:1080/",
   "versions": {
      "api": "1.0.0",
      "enrollmentApi": "1.0.0"
   },
   "id": {
      "cert": "file:///root/device01.3rdparty.client.cert.pem",
      "key": "file:///root/device01.3rdparty.client.key.pem",
      "ca": "file:///root/ziti-dev-ingress01.external.chain.cert.pem"
   }
}

Usage

ziti-tunnel authenticates with the edge controller in the config file, and periodically queries the controller for services that the identity is allowed to use. Whenever a new service is found, ziti-tunnel invokes the selected intercept mode with the details of the service.

ziti-tunnel supports several intercept modes, which are specified with a sub-command. Typically you will run ziti-tunnel with the run sub-command, which determines the preferred intercept mode based on the kernel drivers that are available on the host.

$ sudo ziti-tunnel run ziti.json
[   0.000]    INFO ziti/tunnel/intercept/tproxy.New: tproxy listening on 127.0.0.1:33641
[   0.006]    INFO ziti/tunnel/cmd/ziti-tunnel/subcmd.run: using tproxy interceptor
...

When started on a host that lacks iptables, the tproxy intercept mode initializer fails and ziti-tunnel attempts to use the tun intercept mode:

$ sudo ziti-tunnel run ziti.json
[   0.001]    INFO ziti/tunnel/intercept/tproxy.New: tproxy listening on 127.0.0.1:37313
[   0.001]    INFO ziti/tunnel/cmd/ziti-tunnel/subcmd.run: tproxy initialization failed: failed to initialize iptables handle: exec: "iptables": executable file not found in $PATH
[   0.009]    INFO ziti/tunnel/cmd/ziti-tunnel/subcmd.run: using tun interceptor

ziti-tunnel fails to start if no intercept modes can be successfully initialized:

$ sudo ziti-tunnel run ziti.json
[   0.000]    INFO ziti/tunnel/cmd/ziti-tunnel/subcmd.run: tproxy initialization failed: failed to initialize iptables handle: exec: "iptables": executable file not found in $PATH
[   0.001]    INFO ziti/tunnel/cmd/ziti-tunnel/subcmd.run: tun initialization failed: failed to open tun interface (name='', mtu=0): ioctl failed with 'invalid argument'
[   0.001]   FATAL ziti/tunnel/cmd/ziti-tunnel/subcmd.run: failed to initialize an interceptor

Intercept Modes

tproxy

tproxy is the preferred intercept mode when running on a Linux kernel that has the ip_tables kernel module installed.

$ lsmod | grep ip_tables
ip_tables              32768  5 iptable_filter,iptable_security,iptable_raw,iptable_nat,iptable_mangle

ziti-tunnel manipulates routing tables and firewall rules when using the tproxy intercept mode. The NET_ADMIN Linux capability is required for these actions. The usage example here runs ziti-tunnel with sudo as a simple way to obtain NET_ADMIN:

$ sudo ziti-tunnel --identity ziti.json tproxy
[   0.000]    INFO ziti/tunnel/intercept/tproxy.New: tproxy listening on 127.0.0.1:33355
[   0.010]    INFO ziti/tunnel/dns.NewDnsServer: starting dns server...
[   2.018]    INFO ziti/tunnel/dns.NewDnsServer: dns server running at 127.0.0.1:53
[   2.018]    INFO ziti/tunnel/dns.(*resolver).AddHostname: adding ziti-tunnel.resolver.test = 19.65.28.94 to resolver
[   2.033]    INFO ziti/tunnel/dns.(*resolver).RemoveHostname: removing ziti-tunnel.resolver.test from resolver
[   2.096]    INFO ziti/tunnel/cmd/ziti-tunnel/subcmd.updateServices: starting tunnel for newly available service wttr.in
[   2.290]    INFO ziti/tunnel/dns.(*resolver).AddHostname: adding wttr.in = 5.9.243.187 to resolver
[   2.300]    INFO ziti/tunnel/cmd/ziti-tunnel/subcmd.updateServices: service wttr.in not hostable
[   2.300]    INFO ziti/tunnel/cmd/ziti-tunnel/subcmd.updateServices: starting tunnel for newly available service ssh-scarey
[   2.570]    INFO ziti/tunnel/dns.(*resolver).AddHostname: adding scarey.io = 169.254.1.1 to resolver

The tproxy intercept mode creates a network listener that accepts connections at a randomly selected port on the loopback interface. Intercepted ziti service traffic directed to the listener by two mechanisms:

  • Firewall Rules (iptables)

    The TPROXY iptables target is the primary intercept mechanism used by the tproxy intercept mode. The TPROXY target essentially sends packets to a local listener without actually modifying the packet's destination address fields. See https://www.kernel.org/doc/Documentation/networking/tproxy.txt and iptables-extensions(8) for more details on the TPROXY target.

    First, the tproxy interceptor links a new iptables chain to the PREROUTING chain:

      $ sudo iptables -nt mangle -L PREROUTING | grep NF-INTERCEPT
      NF-INTERCEPT  all  --  0.0.0.0/0            0.0.0.0/0
    

    Then it creates rules in the new chain for each intercepted service. You can view the tproxy rules in play:

      $ sudo iptables -nt mangle -L NF-INTERCEPT
      Chain NF-INTERCEPT (1 references)
      target     prot opt source               destination         
      TPROXY     tcp  --  0.0.0.0/0            5.9.243.187          /* wttr.in */ tcp dpt:443 TPROXY redirect 127.0.0.1:33355 mark 0x1/0x1
      TPROXY     tcp  --  0.0.0.0/0            169.254.1.1          /* ssh-scarey */ tcp dpt:22 TPROXY redirect 127.0.0.1:33355 mark 0x1/0x1
      TPROXY     tcp  --  0.0.0.0/0            1.2.3.4              /* netcat */ tcp dpt:22169 TPROXY redirect 127.0.0.1:33355 mark 0x1/0x1
    

    Packets with a destination address that matches the intercept address of a Ziti service are directed to ziti-tunnel's network listener (127.0.0.1:33355 in the examples above). This effectively enables ziti-tunnel to capture packets that are destined for any address using a single listener (and a single port).

    NOTE: netfilter rules were considered when implementing ziti-tunnel's tproxy intercept mode. netfilter is a slightly more modern than iptables and has a supported netlink API for manipulating rules without "shelling out" to the iptables command line utility. netfilter was ultimately abandoned because netfilter tproxy support requires kernel configuration options (CONFIG_NFT_TPROXY, CONFIG_NFT_SOCKET) that are not enabled in the default kernels of many common Linux distributions.

  • Local Routes

    The TPROXY target is only valid in the PREROUTING iptables chain, which is traversed by incoming packets that were routed to the host over the network. A local route is necessary in order to get locally generated packets to traverse the PREROUTING chain:

      $ ip route show table local
      local 1.2.3.4 dev lo proto kernel scope host src 1.2.3.4
      local 5.9.243.187 dev lo proto kernel scope host src 5.9.243.187
      local 169.254.1.1 dev lo proto kernel scope host src 169.254.1.1
    

iptables rules and local routes are removed by a signal handler when ziti-tunnel is terminated.

tun

The tun intercept mode creates an ephemeral tun interface and configures it with the IP addresses of the services that are being proxied.

ziti-tunnel manipulates network interfaces when using the tun intercept mode, which requires the NET_ADMIN Linux capability. The usage example here runs ziti-tunnel with sudo as a simple way to obtain NET_ADMIN:

$ sudo ziti-tunnel --identity ziti.json tun
[   0.010]    INFO ziti/tunnel/dns.NewDnsServer: starting dns server...
[   2.012]    INFO ziti/tunnel/dns.NewDnsServer: dns server running at 127.0.0.1:53
[   2.012]    INFO ziti/tunnel/dns.(*resolver).AddHostname: adding ziti-tunnel.resolver.test = 19.65.28.94 to resolver
[   2.031]    INFO ziti/tunnel/dns.(*resolver).RemoveHostname: removing ziti-tunnel.resolver.test from resolver
[   2.089]    INFO ziti/tunnel/cmd/ziti-tunnel/subcmd.updateServices: starting tunnel for newly available service wttr.in
[   2.280]    INFO ziti/tunnel/dns.(*resolver).AddHostname: adding wttr.in = 5.9.243.187 to resolver
[   2.282]    INFO ziti/tunnel/cmd/ziti-tunnel/subcmd.updateServices: service wttr.in not hostable
[   2.282]    INFO ziti/tunnel/cmd/ziti-tunnel/subcmd.updateServices: starting tunnel for newly available service ssh-scarey
[   2.502]    INFO ziti/tunnel/dns.(*resolver).AddHostname: adding scarey.io = 169.254.1.2 to resolver
[   2.505]    INFO ziti/tunnel/cmd/ziti-tunnel/subcmd.updateServices: service ssh-scarey not hostable
[   2.505]    INFO ziti/tunnel/cmd/ziti-tunnel/subcmd.updateServices: starting tunnel for newly available service netcat
[   2.506]    INFO ziti/tunnel/cmd/ziti-tunnel/subcmd.updateServices: service netcat not hostable
...

The addresses that ziti-tunnel adds to the tun interface are point-to-point addresses:

$ ip addr show dev tun0
10: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 65535 qdisc fq_codel state UNKNOWN group default qlen 500
    link/none 
    inet 169.254.1.1/32 scope host tun0
       valid_lft forever preferred_lft forever
    inet 169.254.1.1 peer 5.9.243.187/32 scope host tun0
       valid_lft forever preferred_lft forever
    inet 169.254.1.1 peer 169.254.1.2/32 scope host tun0
       valid_lft forever preferred_lft forever
    inet 169.254.1.1 peer 1.2.3.4/32 scope host tun0
       valid_lft forever preferred_lft forever

The tun interface itself is assigned a link-local address, 169.254.1.1 in this case, and each intercepted service is represented by a point-to-point address with the remote address matching the intercept IP of the Ziti service. The tun intercept mode uses point-to-point addresses instead of local routes because local routes would result in the Linux networking stack receiving the packets that are routed to the tun interface. The point-to-point addresses ensure that the packets are delivered "to the wire", which mean that, for a tun interface, the packets will be picked up by ziti-tunnel when it reads data from the tun interface.

The tun interface and all addresses assigned to it are removed when ziti-tunnel terminates. The Linux kernel automatically removes the interface when the process that created it terminates.

proxy

The proxy intercept mode creates a network listener for each Ziti service that is intercepted. The services to intercept, and the ports that they are intercepted on, are specified on the command line (as opposed to using the service definitions that are retrieved from the edge controller):

$ ziti-tunnel --identity ziti.json proxy wttr.in:8443 ssh-scarey:2222 netcat:22169
[   0.004]    INFO ziti/tunnel/intercept/proxy.(*proxyInterceptor).Start: starting proxy interceptor
[   0.120]    INFO ziti/tunnel/intercept.updateServices: starting tunnel for newly available service ssh-scarey
[   0.183]    INFO ziti/tunnel/intercept.updateServices: service ssh-scarey not hostable
[   0.183]    INFO ziti/tunnel/intercept.updateServices: starting tunnel for newly available service netcat
[   0.183]    INFO ziti/tunnel/intercept/proxy.proxyInterceptor.runServiceListener: {addr=[0.0.0.0:2222] service=[ssh-scarey]} service is listening
[   0.203]    INFO ziti/tunnel/intercept.updateServices: service netcat not hostable
[   0.203]    INFO ziti/tunnel/intercept/proxy.proxyInterceptor.runServiceListener: {addr=[0.0.0.0:22169] service=[netcat]} service is listening
[   0.203]    INFO ziti/tunnel/intercept.updateServices: starting tunnel for newly available service wttr.in
[   0.226]    INFO ziti/tunnel/intercept.updateServices: service wttr.in not hostable
[   0.226]    INFO ziti/tunnel/intercept/proxy.proxyInterceptor.runServiceListener: {addr=[0.0.0.0:8443] service=[wttr.in]} service is listening

All network listeners bind to local network interfaces (0.0.0.0):

$ netstat -tnl | fgrep 0.0.0.0
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 0.0.0.0:2222            0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:22169           0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:8443            0.0.0.0:*               LISTEN     

Proxy mode is intended to be a developer tool, most useful in situations where root privileges are not available.

DNS Server

ziti-tunnel runs an internal DNS server by default. The DNS server must be first in the host's resolver configuration (e.g. resolve.conf). A self-test is performed when ziti-tunnel starts to ensure that its internal DNS server is configured in the system resolver:

INFO[0002] dns server started on 127.0.0.1:53           
INFO[0002] adding ziti-tunnel.resolver.test -> 19.65.28.94 to resolver 
INFO[0002] removing ziti-tunnel.resolver.test from resolver 

The test involves inserting a known hostname/IP address into the internal DNS server, and using the system resolver to retrieve the address of the hostname. ziti-tunnel will exit if the DNS self-test fails.

Linux distributions typically manage the contents of /etc/resolv.conf, so simply editing the file will only work for a short time until /etc/resolv.conf is overwritten by the managing process.

Resolver configuration changes must survive restarts of the Linux name resolution manager. Linux distrubutions use one of several name resolution managers. The simplest way to determine which name resolution manager is being used by your Linux distrubtion is to look at /etc/resolv.conf:

$ ls -l /etc/resolv.conf
  • If /etc/resolv.conf is a regular file, then it is most likely being managed by dhclient.
  • If /etc/resolv.conf is a symlink to a file in /run/systemd/resolve, then it is being managed by systemd-resolved
dhclient

If your Linux distribution uses dhclient, you can configure the system resolver to use ziti-tunnel's internal DNS server first by adding the following to /etc/dhcp/dhclient.conf:

prepend domain-name-servers 127.0.0.1;

Then restart network manager. Unless you know the name of the NetworkManager systemd service on your Linux distrubtion, it's probably easiest to reboot the host.

systemd-resolved
$ sudo ln -sf /run/systemd/resolve/resolv.conf /etc
$ echo -e "[Resolve]\nDNS=127.0.0.1" | sudo tee /etc/systemd/resolved.conf.d/ziti-tunnel.conf
$ sudo systemctl restart systemd-resolved

If you are unable to control the resolver on your operating system, ziti-tunnel can use/update a hosts file for any hostnames that it tunnels:

ziti-tunnel run --resolver file:///etc/hosts "${HOME}/ziti.json"
IP Address Assignment

If the service specifies a hostname for its address, ziti-tunnel resolves the hostname and adds the result to its internal DNS server:

[0127]  INFO adding myservice.mydomain.com -> 45.60.32.165 to resolver

If the service hostname does not resolve, ziti-tunnel will find an unused link-local address and assign it to the route for the service:

[0012]  INFO adding bogushost.net -> 169.254.1.4 to resolver
[0012]  INFO ziti/tunnel/protocols/tcp.Listen: Accepting on 169.254.1.4:25 service=telnet

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL