dante   Frontpage - Dante - Download - Status - Support - Modules - Docs - Links - Survey
 

HOSTID operations

This page describes how to perform access control based on out-of-band hostid client information.

NOTE: This functionality currently depends on proprietary extensions similar to those described in draft-abdo-hostid-tcpopt-implementation-02.

A hostid as used here is a client identifier. When a client connects directly to a Dante server, the IP-address of the client can be obtained via the getpeername() system call, but with the standard TCP/IP and SOCKS protocols the identity of the client will not be available to the next hop (the address the Dante server connects to on behalf of the client).

Whether the proxy server forwards connections directly to a target destination or via server chaining to another proxy server, the first proxy will appear as the client for the next server if getpeername() is used. This makes it difficult to perform access control, logging or similar operations based on the original client IP-address in other locations than in the proxy server that communicates directly with the client. An approach, such as those described in the above hostid-tcpopt draft makes use of a TCP option to transmit the identity of the original client in forwarded connections. Any IP-address transmitted in this way is here referred to as a hostid.

The Dante hostid support is designed so that multiple hostids (or IP-addresses) can be transmitted, allowing the identity of multiple layers of caches to be encoded.

Dante hostid configuration

A possible hostid usage scenario would include a client, a Dante proxy and a target server. The client connects to the Dante proxy which then initiates a connection to the target server. The connection to the target server would be done by the Dante server, but the identity of the client would be available to the target server via the hostid information provided via a TCP socket option.

A second scenario would have a private company network with two layers of proxies. The first layer at the border of the private and public network, and the second layer inside the private network. The border proxy would receive connections directly from external clients and set the IP-address of these clients in the hostid field on connections forwarded to the internal proxy. The internal proxy would retrieve the hostid value and add the IP-address of the border proxy on connections to the target server. The target server would then have available the IP-address of the original client (as HOSTID), the border proxy (as HOSTID2), and the external address of the internal proxy (via a direct TCP connection).

Logging of hostid values

On platforms supporting the hostid socket option, any available hostid information on a TCP connection Dante receives will be automatically logged by Dante. The hostid information is merged with the information normally logged by Dante in the format shown below.

HOSTID corresponds to the first hostid TCP option value. The logged HOSTID values are enclosed in a set of [] characters to emphasize that this information is obtained out-of-band and cannot be verified by Dante.

info: pass(1): tcp/accept [: [HOSTID] CLIENT PROXY-INT
info: pass(1): tcp/hostid [: [HOSTID] CLIENT PROXY-INT
info: pass(1): tcp/connect [: [HOSTID] CLIENT PROXY-INT -> PROXY-EXT TARGET
info: pass(1): tcp/connect -: TARGET PROXY-EXT -> PROXY-INT CLIENT [HOSTID] (RLEN)
info: pass(1): tcp/connect -: [HOSTID] CLIENT PROXY-INT -> PROXY-EXT TARGET (WLEN)
info: pass(1): tcp/connect ]: RLEN -> [HOSTID] CLIENT PROXY-INT -> WLEN, WLEN -> PROXY-EXT TARGET -> RLEN: DESC. Session duration: SESSTIMEs
info: pass(1): tcp/hostid ]: RLEN -> [HOSTID] CLIENT PROXY-INT -> WLEN: DESC. Session duration: SESSTIMEs
info: pass(1): tcp/accept ]: RLEN -> [HOSTID] CLIENT PROXY-INT -> WLEN: DESC. Session duration: SESSTIMEs

The following information is reported on the connection when SIGINFO/SIGUSR1 is received:

debug: tcp: [HOSTID] CLIENT PROXY-INT <-> PROXY-EXT TARGET: age: SESSTIMEs, idle: IDLETIMEs, bytes transferred: WLEN (+ 0 buffered (0 + 0 + 0)) <-> RLEN (+ 0 buffered (0 + 0 + 0))

When two hostid addresses are set on a connection, the output will appear as follows. The second hostid address will be shown in the HOSTID2 field:

info: pass(1): tcp/accept [: [HOSTID] [HOSTID2] CLIENT PROXY-INT
info: pass(1): tcp/hostid [: [HOSTID] [HOSTID2] CLIENT PROXY-INT
info: pass(1): tcp/connect [: [HOSTID] [HOSTID2] CLIENT PROXY-INT -> PROXY-EXT TARGET
info: pass(1): tcp/connect -: TARGET PROXY-EXT -> PROXY-INT CLIENT [HOSTID2] [HOSTID] (RLEN)
info: pass(1): tcp/connect -: [HOSTID] [HOSTID2] CLIENT PROXY-INT -> PROXY-EXT TARGET (WLEN)
info: pass(1): tcp/connect ]: RLEN -> [HOSTID] [HOSTID2] CLIENT PROXY-INT -> WLEN, WLEN -> PROXY-EXT TARGET -> RLEN: DESC. Session duration: SESSTIMEs
info: pass(1): tcp/hostid ]: RLEN -> [HOSTID] [HOSTID2] CLIENT PROXY-INT -> WLEN: DESC. Session duration: SESSTIMEs
info: pass(1): tcp/accept ]: RLEN -> [HOSTID] [HOSTID2] CLIENT PROXY-INT -> WLEN: DESC. Session duration: SESSTIMEs

The SIGINFO/SIGUSR1 format for two hostid values is as follows:

debug: tcp: [HOSTID] [HOSTID2] CLIENT PROXY-INT <-> PROXY-EXT TARGET: age: SESSTIMEs, idle: IDLETIMEs, bytes transferred: WLEN (+ 0 buffered (0 + 0 + 0)) <-> RLEN (+ 0 buffered (0 + 0 + 0))

When server chaining is used, the proxy that forwards the request to a SOCKS server and uses the following extended log format, which also includes information about the next and last SOCKS proxies (as the contacted SOCKS server might forward the request to another SOCKS server, and so on). Note that when server chaining is used, the LASTPROXY-EXT value is also put inside a set of [] characters to indicate it cannot be directly verified.

info: pass(1): tcp/accept [: [HOSTID] CLIENT PROXY-INT
info: pass(1): tcp/hostid [: [HOSTID] CLIENT PROXY-INT
info: pass(1): tcp/connect [: [HOSTID] CLIENT PROXY-INT -> PROXY-EXT NEXTPROXY-INT [LASTPROXY-EXT] TARGET
info: pass(1): tcp/connect -: TARGET [LASTPROXY-EXT] NEXTPROXY-INT PROXY-EXT -> PROXY-INT CLIENT [HOSTID] (RLEN)
info: pass(1): tcp/connect -: [HOSTID] CLIENT PROXY-INT -> PROXY-EXT NEXTPROXY-INT [LASTPROXY-EXT] TARGET (WLEN)
info: pass(1): tcp/connect ]: RLEN -> [HOSTID] CLIENT PROXY-INT -> WLEN, WLEN -> PROXY-EXT NEXTPROXY-INT [LASTPROXY-EXT] TARGET -> RLEN: DESC. Session duration: SESSTIMEs
info: pass(1): tcp/hostid ]: RLEN -> [HOSTID] CLIENT PROXY-INT -> WLEN: DESC. Session duration: SESSTIMEs
info: pass(1): tcp/accept ]: RLEN -> [HOSTID] CLIENT PROXY-INT -> WLEN: DESC. Session duration: SESSTIMEs

SIGINFO/SIGUSR1 format:

debug: tcp: [HOSTID] CLIENT PROXY-INT <-> PROXY-EXT NEXTPROXY-INT [LASTPROXY-EXT] TARGET: age: SESSTIMEs, idle: IDLETIMEs, bytes transferred: WLEN (+ 0 buffered (0 + 0 + 0)) <-> RLEN (+ 0 buffered (0 + 0 + 0))

Manipulation of hostid values

TCP connections received by a Dante server can have zero or more hostid values set. By default the server will log these values but not forward them onto the outgoing connection. To explicitly specify how the hostid values should be handled, the following alternatives are available:

  • none - No hostid values will be set on the outgoing connection. This is the default behavior.
  • pass - This causes Dante to simply forward/duplicate any existing hostid values from the client connection, without adding or removing anything.
  • set-client - Any existing values are discarded and only the source address of the client connecting to Dante will be set in the hostid field on the outgoing connection.
  • add-client - This causes any existing values to be duplicated (unless all entries are used) and the client source address to be added.

    It should be noted that the hostid TCP option field is not of infinite size, and might be limited by both a given kernel implementation and the TCP header size. Care should be be taken to ensure that a chain of proxies will not attempt to set a larger number of hostid entries than there is room for as this will result in loss of information.

    Assuming a maximum of two hostid values are supported, the following behavior is implemented by Dante when adding addresses to the hostid fields on an outgoing connection:

    • If no hostid values are set on the incoming connection, the source address of the client connecting to Dante will be set in the first hostid position on the outgoing connection. The second position will remain empty.
    • If a single hostid value is set on the incoming connection, it will be copied to the first hostid position on the outgoing connection. The source address of the client connecting to Dante will be set in the second hostid position.
    • If two hostid values are set on the incoming connection, the first hostid value from the incoming connection will be copied to the first hostid position on the outgoing connection. The second hostid value from the incoming connection will be discarded, and instead the source address of the client connecting to Dante will be set in the second hostid position on the outgoing connection.

      This means that with this approach any address lost will be those off intermediate proxies or routers, rather than the first value added, which is expected to contain the address of the initial client.

      A warning will be logged by Dante when this occurs:

      warning: setconfsockoption(): connection from 127.0.0.1.57839 has already reached the maximum number of hostids (2), so can not add one more. Removing the last hostid (172.16.100.2) before adding the new one

Because any IP-address can be set in the hostid TCP option, values set by untrusted clients should be treated with care. A Dante proxy that communicates directly with an untrusted client should use either the none or the set-client keywords to discard any hostid TCP option values set by the untrusted client. The pass or add-client values should only be used for connections from trusted hosts (such as another proxy).

The hostid keywords can be set both globally, as defaults, and in socks rules, meaning that different approaches can be used for different clients and SOCKS requests. The syntax is as follows:

 external.tcp.hostid: pass|add-client|set-client|none

The example below shows a configuration that specifies that by default, hostid values should not be set for outgoing connections (keyword none), with two exceptions:

  • For any other connections from a machine with the IP-address 10.0.0.1, any hostid values should be passed on unchanged (keyword pass).
  • For any connections to the target server www.example.org, the address of the client should be added to any existing hostid values (keyword add-client).
#default, discard any hostid
external.tcp.hostid: none

#duplicate any hostid values from the host 10.0.0.1
socks pass {
     from: 10.0.0.1/32 to: 0.0.0.0/0

     external.tcp.hostid: pass
}

#add client source address to hostid values.
socks pass {
     from: 0.0.0.0/0 to: www.example.org

     external.tcp.hostid: add-client
}

#generic pass rule.  Use the default; not setting any hostids.
socks pass {
     from: 0.0.0.0/0 to: 0.0.0.0/0

     #external.tcp.hostid: none
}

Access control based on hostid values

A new access control layer has been added in this release that can be used to either grant or deny access to the proxy based on hostid information.

Access control is now done in these steps:

  1. First the client pass/block rules are applied.
  2. Then the new hostid pass/block rules are applied for any TCP connections that have hostid values set. These rules are ignored for TCP connections without hostid values.
  3. Finally, after SOCKS request processing, the socks pass/socks block socks rules are applied.

The first step is basically unchanged, but a new keyword (hostid)) has been added that can be used to ensure that connections that should have hostid values set are blocked if no hostid values have been set. The following example shows how this can be done:

#matches connections from 10.0.0.1 *only* if they have hostid values set,
#regardless of what the hostid value is.
#if there are no other client pass rules matching connections from 10.0.0.1,
#connections from 10.0.0.1 will always be blocked unless they have at
#least one hostid value set.
client pass {
        from: 10.0.0.1/32 to: 0.0.0.0/0
        hostid: 0.0.0.0/0
}

#require hostid values to be set on connections from 10.0.0.2,
#but also require the hostid values to be in the 172.16 network.
client pass {
        from: 10.0.0.2/32 to: 0.0.0.0/0
        hostid: 172.16.0.0/12
}

#allow connections from 10.0.0.3 regardless of whether hostid values
#are set or not.
client pass {
        from: 10.0.0.3/32 to: 0.0.0.0/0
}

As with the from and to keywords, the hostid keyword can contain an address or hostname to match against. All the specified parameters (from, to, hostid, etc. must match for a rule to pass.

The hostid rules are similar to the client pass/block rules in that they are applied before SOCKS processing. The contents of the from keyword is matched against the hostid values and the to keyword against the internal address of the Dante server.

Since the hostid field can contain more than one address, it is necessary to be able to control which hostid address the from address should be matched against.

This is controlled with the hostindex keyword, which can have the values any, 1, or 2 (it is not zero-indexed). The any keyword causes a rule to match if any of the hostid values matches. A value of 1 will only match if it matches the first hostid value (the initial client), and 2 will only match if it matched the second value. By default, the value of this hostindex parameter is 1.

If there is at least one hostid rule configured in the Dante configuration file, the default behavior is to block connections with hostid information set unless there is a matching hostid pass rule, while if no hostid rules exists, or no hostid value is set on the incoming connection, the default behaviour is to ignore hostid values as far as access control is concerned. Note that this is different from the other ACL-levels, which block by default and always require a matching pass rule.

The following example shows a simple example with a single hostid rule:

#pass connections where the first hostid value is 10.0.0.1
hostid pass {
       from: 10.0.0.1/32 to: 0.0.0.0/0
       hostindex: 1
}

This example has two rules to allow different matching for each of the two hostid values:

#pass connections where the first hostid value is from the 10.0.0.0 network
hostid pass {
       from: 10.0.0.0/8 to: 0.0.0.0/0
       hostindex: 1
}

#also pass connections where the second hostid value is 172.16.0.20
hostid  {
       from: 172.16.0.20/32 to: 0.0.0.0/0
       hostindex: 2
}

Usage example A - Network border forwarder

This section shows how the different keywords can be combined in a typical usage scenario.

This example is based on a usage scenario where there is a Dante server running on the border between a trusted internal network and an untrusted external network. The Dante server receives SOCKS requests from untrusted clients and forwards connections to internal target servers.

The Dante server ignores any hostid values set on connections from the clients connecting to it and sets the source address of the clients in the first hostid field of the connections going to the internal servers by using set-client. This ensures that malicious or untrusted clients will be unable to supply incorrect hostid data on the connection to the Dante server.

The following Dante configuration is used to achieve this behavior:

#always set client IP on outgoing connections, ignoring any existing values
external.tcp.hostid: set-client

#pass all client connections from the 192.168-net.
client pass {
        from: 192.168.0.0/16 to: 0.0.0.0/0
        log: connect disconnect error
}

#allow all accepted clients to connect to the internal 10-net.
socks pass {
        from: 0.0.0.0/0 to: 10.0.0.0/8
        log: connect disconnect error
}

The configuration above would give the following behavior for a client with the address 192.168.1.10 connecting to the Dante proxy and requesting a connection to a target server at 10.1.1.1.

  1. The client pass and socks pass rules would match, causing the Dante server to establish a connection to 10.1.1.1 on behalf of the client from 192.168.1.10.
  2. The connection received by the target server would see the external IP-address of the Dante proxy as the source IP-address, and have the IP-address of the client (192.168.1.10) set as a hostid value in the TCP connection from Dante. No other hostid values would be set.

Usage example B - Internal network forwarder

This example is based on a usage scenario where there is a Dante server running somewhere inside an internal network, for example receiving traffic forwarded by a server like the one in the previous example, which has added hostid information to the connections it forwards.

For the internal server, the Dante configuration is more restrictive and only accepts connections from, a presumably trusted, server with the address 192.168.1.1. All connections need to have at least one hostid value set. Since it is assumed that the machine at 192.168.1.1 can be relied upon to provide correct and valid hostid values, add-client is used to add the client address to the set of hostid values forwarded on outgoing connections.

This example also uses the hostid addresses for access control, allowing only specific address ranges to connect through Dante.

The following Dante configuration is used to achieve this behavior:

#always add client IP on outgoing connections, keeping the existing value.
external.tcp.hostid: add-client

#pass client connections from the trusted server 192.168.1.1, but require
#them to have a hostid value set. 
client pass {
        from: 192.168.1.1/32 to: 0.0.0.0/0
        hostid: 0.0.0.0/0
        log: connect disconnect error
}

#only clients with a hostid from the 172.16-net are accepted.
hostid pass {
        from: 172.16.0.0/12 to: 0.0.0.0/0
        log: connect disconnect error
}

#allow accepted clients to connect to the internal 10-net.
socks pass {
        from: 0.0.0.0/0 to: 10.0.0.0/8
        log: connect disconnect error
}

The configuration above would give the following behavior for a client with the address 172.16.0.10 connecting via the trusted server at 192.168.1.1 to the Dante proxy, and requesting a connection to a target server at 10.1.1.1.

  1. The client pass, hostid pass and socks pass rules would all match, causing the Dante server to establish a connection to 10.1.1.1 on behalf of the proxy from 192.168.1.1. The connection to the target server 10.1.1.1 would have both the client address 172.16.0.1 and the proxy address 192.168.1.1 set in the hostid header.
  2. A TCP connection would be received by the 10.1.1.1 server. The hostid values on this connection would contain the addresses 172.16.0.1 and 192.168.1.1. The source IP-address of the connection would be that of the external Dante interface.

Copyright © 1998-2016 Inferno Nettverk A/S