Tip of the day: Check out Special users on how to give trusted users/bots more rights without making them IRCOp.

Proxy block

From UnrealIRCd documentation wiki
Jump to navigation Jump to search

The proxy block allows you to configure host spoofing via web reverse proxies or WEBIRC.

NOTE: If you are running UnrealIRCd 6.1.0 or older, then instead see the WebIRC block for webirc support.

Is a reverse proxy a good idea?

Note that while using WEBIRC is reasonably common, using other proxy types like using a reverse proxy for IRC is rare. Using a reverse proxy could be useful in the specific case where you don't expose the IRC server directly to the internet and only allow it to be accessed through websocket connections through a reverse proxy (like, having an IRC server for webchats only). Using a reverse proxy makes considerable less sense when your IRC server is still directly reachable from the internet (direct IRC without websockets, without the reverse proxy).

Also, using a reverse proxy adds another Point of Failure (another host in-between that may be down, have glitches, network resets, etc.). For example, Cloudflare resets idle websocket connections after 2 minutes and may reset websocket connections during day to day upgrades of their servers. Make sure you understand the effects before going this route.

If you were considering reverse proxies to distribute user load, then better read Distributing user load and doing server maintenance instead, which uses DNS RR and takes away the need for the reverse proxy inbetween.

Syntax

proxy name-of-proxy {
        type [webirc|forwarded|x-forwarded|cloudflare];
        match { ip 1.2.3.4; }
        password "somepass";
};

The name-of-proxy is a short name for the proxy. It is not used yet as of UnrealIRCd 6.1.1, but may be used or displayed somewhere at a later point.

Within the block:

  • type is the type of proxy:
    • webirc: a WEBIRC / CGI:IRC gateway, see also WebIRC Support.
    • forwarded: a web proxy sending Forwarded headers (modern standard, but less common)
    • x-forwarded: a web proxy sending X-Forwarded-For and X-Forwarded-Proto headers (not standardized but more common)
    • cloudflare: if you are using cloudflare as a reverse proxy, this uses a combination of CF-Connecting-IP and X-Forwarded-Proto.
      IMPORTANT: Cloudflare terminates idle websocket connections after 2 minutes and has a policy of only allowing a certain number of concurrent websocket connections. See the Cloudflare documentation. As you can see, the maximum concurrent connections vary by plan and the exact numbers are unspecified. Using Cloudflare for IRC websockets is likely not an ideal choice.
  • match is checked against the proxy that is trying to connect. It is a Mask item. We recommend strict IP address restrictions here. For proxy type forwarded/x-forwarded/cloudflare, which don't use a password, we automatically exempt hosts matching this from connect floods and from blacklist checks (in UnrealIRCd 6.1.8 and later).
  • password is only used for type webirc

Examples

We have several examples:

WEBIRC example with KiwiIRC

The example below configures KiwiIRC and UnrealIRCd. Note that this is just an example, if you use something other than KiwiIRC you edit that instead of the KiwiIRC-side.

The most important thing is that you set the same password on both the Kiwi side and on the UnrealIRCd side, we use ThisIsMySecretWebIRCPassword as an example here.

KiwiIRC-side (old and outdated)

Edit kiwiirc's config.js as follows (note: this is NOT a complete configuration file, it just shows 3 important sections):

// WebIRC passwords enabled for this server
conf.webirc_pass = {
    "irc1.yournetwork.org":        "ThisIsMySecretWebIRCPassword"
};

// Default settings for the client. These may be changed in the browser
conf.client = {
    server: 'irc1.yournetwork.org',
    port:    6697,
    ssl:     true,
    channel: '#test',
    nick:    'kiwi_?'
};
// or use conf.restrict_server etc... to achieve the same effect (see example config.js file).

// What matters is that the server name in conf.client or conf.restrict_server
// match the server name in conf.webirc_pass (and use the right password).
// So be sure to replace BOTH the irc1.yournetwork.org instances with the same server name.

// Now something else... "send ip as username"....
// Be sure *NOT* to list your server here !!!!
// If you add it here, then the IP will show up in the ident. This is NOT what you want
// as the IP is already sent via WEBIRC. Doing so would reveal the users' IP to everyone which is bad.
conf.ip_as_username = [
    "do_not_set_your_server_here"
];

KiwiIRC-side (new aka nextclient)

Edit kiwiirc's webircgateway config.conf as follows (note: this is NOT a complete configuration file):

// WebIRC passwords enabled for this server
[upstream.1]
webirc = "ThisIsMySecretWebIRCPassword"

UnrealIRCd-side

Then, in your unrealircd.conf you add a webirc block:

proxy {
        type webirc;
	mask 127.0.0.1;
	password "ThisIsMySecretWebIRCPassword";
};

except ban {
        mask 127.0.0.1;
        type { connect-flood; handshake-data-flood; blacklist; };
};

The Except ban block block is highly recommended. This will ensure your webirc gateway is not seen as connection flooding and the blacklist exception will make it connect faster. If you don't have an except ban block for your webirc gateway then users will not be able to connect during peak hours or after a server restart.

NGINX reverse proxy for websockets

In this example we use a reverse proxy in NGINX to proxy websocket connections via NGINX to your UnrealIRCd. We choose to use the "forwarded" type which is less common but standardized.

Note that UnrealIRCd also supports X-Forwarded-For header, but this is NOT used in the example below (we set the Forwarded header).

UnrealIRCd configuration

First, set up websockets on port 8000 with SSL/TLS:

loadmodule "websocket";
loadmodule "webserver";

listen {
    ip *;
    port 8000;
    options { 
       websocket { type text; }
       tls;
    }
}

Then add the proxy block:

proxy {
    type forwarded;
    match { ip 6.6.6.6; } // IP of your reverse proxy
}

NGINX configuration

This is based on NGINX docs plus the addition of $proxy_protocol from ourselves

Add this to your config so the $remote_addr variable becomes available:

map $https $proxy_protocol {
    "^on$" "proto=https";
    default "proto=http";
}

map $remote_addr $proxy_forwarded_elem {
    # IPv4 addresses can be sent as-is
    ~^[0-9.]+$          "for=$remote_addr;$proxy_protocol";

    # IPv6 addresses need to be bracketed and quoted
    ~^[0-9A-Fa-f:.]+$   "for=\"[$remote_addr]\";$proxy_protocol";

    # Unix domain socket names cannot be represented in RFC 7239 syntax
    default             "for=unknown;$proxy_protocol";
}

map $http_forwarded $proxy_add_forwarded {
    # If the incoming Forwarded header is syntactically valid, append to it
    "~^(,[ \\t]*)*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*([ \\t]*,([ \\t]*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*)?)*$" "$http_forwarded, $proxy_forwarded_elem";

    # Otherwise, replace it
    default "$proxy_forwarded_elem";
}

And then add something like this in your site block:

location /websocket {
        proxy_set_header Forwarded $proxy_add_forwarded;
        proxy_pass https://5.5.5.5:8000/;
}

This assumes your UnrealIRCd is running at 5.5.5.5 with websockets enabled at port 8000 and it is a SSL/TLS port, as we configured earlier above.

Test

Be sure to REHASH the IRCd and reload your NGINX (systemctl restart nginx.service). Then connect with a websocket to https://yourproxy/websocket

Your websocket client should pop up on IRC with the end-user IP address (and not the IP address of the proxy). If it doesn't work, then also connect as IRCOp via normal (non-websocket) IRC and see if there is anything logged by the IRCd, UnrealIRCd log messages such as "proxy did not send a valid forwarded header".

  • If you see the "proxy did not send a valid forwarded header" warning then it means your proxy is not sending the Forwarded header. For example it could be using the wrong headers such as Via or X-Forwarded-For:
    • Either reconfigure the proxy to send the modern Forwarded header instead. This is all explained under #NGINX configuration above.
    • Or, if you want to use X-Forwarded-For instead of Forwarded, then in UnrealIRCd use proxy { type x-forwarded; [..] }