How to capture and forward incoming IP address in Node-RED?

I have been using Node-RED for 5 years now and have decent exposure to it. We use it for various HTTP calls and use it as a relay from internet to out internal API servers.

We would like to capture the IP address of incoming requests, but once the calls hits Node-RED application, we only see Node-RED server's IP address in our internal API request headers.

I have tried to capture the msg.req.ip and msg.req.connection.remoteAddress as shown in this topic Http in node, caller IP address - #8 by Colin but that always returns 127.0.01 which I think is the local IP address of the Node-RED server as seen by the UI builder.

Can someone help me in capturing and may be forwarding the incoming IP address. Thanks.

Hi Pawanpillai,

is your router/firewall set up to proxy the requests or just forward them?

If its proxying them you probably wont see the original IP

Try msg.req.headers['x-forwarded-for'] or it could be msg.req.headers['x-forwarded-For'] see what you get.

1 Like

Thanks, I tried both and both returned undefined.

msg.xff = msg.req.headers['x-forwarded-for'];
msg.xfF = msg.req.headers['x-forwarded-For'];

prints:
msg.xff: undefined
msg.xfF: undefined

1 Like

It may not being forwarded.

Can you confirm its forwarded or proxied from your firewall?

1 Like

My company's IT team manages the Node-RED server backend, I will check with them on Monday but as they use ngnix, its most probably setup as a proxy. I will keep you posted. Thank you for all the help so far.

1 Like

You may need to set an appropriate value for the Express setting "trust proxy" e.g...

In Node-REDs settings.js file:

    httpServerOptions: {
        "trust proxy": true // take left-most entry in the X-Forwarded-* header.
    },

REF: Express 5.x - API Reference

Be sure to read up on the implications this means for your env. e.g from the docs:

Indicates the app is behind a front-facing proxy, and to use the X-Forwarded-* headers to determine the connection and the IP address of the client. NOTE: X-Forwarded-* headers are easily spoofed and the detected IP addresses are unreliable.

2 Likes

As others have said, you should tell Node-RED to trust the proxy, it should be setting the upstream address. If not, you will need to ask your IT team to update the headers for the proxied addresses. Here are the NGINX proxy settings I use:

# Common reverse proxy settings
# Don't forget to also add proxy_pass url;

  proxy_set_header Forwarded "by=$host;for=$proxy_add_x_forwarded_for;host=$host;proto=$scheme";
  proxy_set_header Via       "$scheme/1.1 $host:$server_port";

  proxy_set_header Host              $host;
  proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Host  $host;
  proxy_set_header X-Forwarded-Port  $server_port;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_set_header X-Original-URI    $request_uri;
  proxy_set_header X-Real-IP         $remote_addr;

  # Proxy timeouts
  proxy_connect_timeout       60s;
  proxy_send_timeout          60s;
  proxy_read_timeout          60s;

  # If proxied responses happening too slowly, try turning off the buffering
  #proxy_buffering off;

And, for websockets:

# Common headers for proxy of websockets

proxy_http_version          1.1;
proxy_set_header Upgrade    $http_upgrade;
# proxy_set_header Connection "Upgrade";
proxy_set_header Connection $connection_upgrade_keepalive;

For that last setting, the NGINX main config should contain this:

# Upgrade WebSocket if requested, otherwise use keepalive
map $http_upgrade $connection_upgrade_keepalive {
    default Upgrade;
    ''      '';
}

When using UIBUILDER, any reported client IP will already be using the best available remote address including from proxy headings. Control messages such as the client connect message also list all the headers.

In addition, if you choose to use a custom ExpressJS web server rather than the default Node-RED one, you can set the proxy trust in the uibuilder section of settings.js.

    /** Custom settings for all uibuilder node instances */
    uibuilder: {
        /** Optional HTTP PORT.
         * If set and different to Node-RED's uiPort, uibuilder will create
         * a separate webserver for its own use.
         */
        port: process.env.UIBPORT || 3001,

        /** Optional: Change location of uibRoot
         * If set, instead of something like `~/.node-red/uibuilder`, the uibRoot folder can be anywhere you like.
         */
        uibRoot: process.env.UIBROOT || '/src/uibRoot', // path.join(os.homedir(), 'myuibroot'),
        // For project-specific uibuilder folders:
        // uibRoot: path.join(os.homedir(), '.node-red', 'projects', 'uibuilder')

        /** Only used if a custom ExpressJS server in use (see port above)
         * Optional: Default will be the same as Node-RED. @type {('http'|'https')}
         */
        customType: 'http',

        /** Only required if type is https, http2. Defines the cert & key. See Node-RED https settings for more details.
         * If not defined, will use Node-RED's https properties.
         * @type {Object<Buffer,Buffer>}
         */
        // https: {
        //     key: 'keyname.key',
        //     cert: 'fullchain.cer'
        // },

        /** Optional: Custom ExpressJS server options
         *  Only required if using a custom webserver (see port setting above).
         * For a full list of available options, refer to http://expressjs.com/en/api.html#app.settings.table
         */
        serverOptions: {
            // http://expressjs.com/en/api.html#trust.proxy.options.table
            'trust proxy': true,  // true/false; or subnet(s) to trust; or custom function returning true/false. default=false
            /** Optional view engine - the engine must be installed into your userDir (e.g. where this file lives)
             * If set as shown, ExpressJS will translate source files ending in .ejs to HTML.
             * See https://expressjs.com/en/guide/using-template-engines.html for details.
             */
            'view engine': 'ejs',
            // Optional global settings for view engine
            'view options': {},

            // Custom properties: can be used as vars in view templates
            'footon': 'bar stool',
        },

        /** Optional: Socket.IO Server options
         * See https://socket.io/docs/v4/server-options/
         * Note that the `path` property will be ignored, it is set by uibuilder itself.
         * You can set anything else though you might break uibuilder unless you know what you are doing.
         * @type {object}
         */
        // socketOptions: {
        //     // Make the default buffer larger (default=1MB)
        //     maxHttpBufferSize: 1e8 // 100 MB
        // },

        /** Controls whether the uibuilder instance API feature is enabled
         *  Off by default since uncontrolled instance api's are a security and
         *  operational risk. Use with caution. See Docs for details.
         */
        instanceApiAllowed: true,

        hooks: {
            /** Hook fn run every time any uibuilder node receives a message from the front-end client.
             * You CAN:
             * - Stop the inbound msg by returning false.
             * - Change the msg that is received before it is output.
             * You _could_ change the node's values BUT DO NOT! Bad things likely to happen if you do.
             * @param {object} data
             * @param {object} data.msg READ/WRITE. The msg that is being sent
             * @param {object} data.node READ-ONLY! The settings of the node sending the message
             * @returns {boolean} true = receive the msg. false = block the msg.
             */
            msgReceived: (data) => {
                const {msg, node} = data

                // console.log('hooks:msgReceived - msg: ', msg)
                // console.log('hooks:msgReceived - uibuilder url: ', node.url)

                if (msg.blockme) return false // simplistic example
                                
                // Example of altering the msg
                // msg._hook = 'I WAS HOOKED!'

                // Block inputs except from logged in users
                // if (!msg._client) return false

                // Default, allows msgs to flow
                return true
            },
            /** Hook fn run every time any uibuilder node is sending a msg to a front-end client
             * This could therefore get run thousands of times! So best to filter up front as shown.
             * You CAN:
             * - stop the outbound msg by returning false.
             * - Change the msg that is sent
             * You _could_ change the node's values BUT DO NOT! Bad things likely to happen if you do.
             * @param {object} data
             * @param {object} data.msg READ/WRITE. The msg that is being sent
             * @param {uibNode} data.node READ-ONLY! The settings of the node sending the message
             * @returns {boolean} true = receive the msg. false = block the msg.
             */
            msgSending: (data) => {
                const {msg, node} = data

                // Filter to restrict by topic
                // if (msg.topic !== 'test') return false

                // console.log('hooks:msgReceived - msg: ', msg)
                // console.log('hooks:msgReceived - uibuilder url: ', node.url)

                if (msg.blockme) return false // simplistic example

                // Block outputs except to logged in users
                // if (!msg._client) return false

                // Default, allows msgs to flow
                return true
            },
            /** Hook fn run whenever uibuilder looks up a connected client's details. NOTE:
             * This will be run every time a client connects and every time it sends a msg to Node-RED.
             * This lets you amend the details including out._client which can contain authenticated user details.
             * @param {object} data
             *   @param {object} data.client Key client information - this can be amended in this fn
             *     @param {object} data.client._uib Standard uibuilder client meta info
             *     @param {object=} data.client._client Only if a recognised authenticated client
             *   @param {socketio.Socket} data.socket Reference to client socket connection
             * @param {object} data.node READ-ONLY! The settings of the node sending the message
             */
            clientDetails: (data) => {
                const {out: client, socket, node} = data

                // node.log(`[uibuilder:hooks:clientDetails] for ${node.url}`)
                // console.log(socket.request.headers, socket.handshake.auth)

                // Simplistic e.g. to block a specific user from being logged in
                if (client?._client?.userId === 'horrible.user') delete client._client
            },
            /** Hook fn that allows overrides of Socket.IO connection headers. NOTE:
             * Connection headers will only ever update when a client (re)connects or
             * possibly also for long-polling requests.
             * @see https://socket.io/docs/v4/server-api/#event-headers for details
             * @param {object} data 
             * @param {object<string:string>} data.headers Response Headers
             * @param {Express.Request} data.req ExpressJS Request object
             */
            socketIoHeaders: (data) => {
                const { headers, req } = data

                // headers contains the response headers that go back to the client.
                headers['x-wowser'] = 'The Client gets this'

                // Simulate an authenticated user
                // If these are set, a msg._client object is added to uibuilder output messages
                // req.headers['remote-user'] = 'test-user'
                // req.headers['remote-name'] = 'Test User'
                // req.headers['remote-email'] = 'test.user@example.com'
                // req.headers['x-user-role'] = 'test-role'
                // req.headers['x-forwarded-groups'] = 'group1,group2'

                // NB: This is NOT suitable for session controls since it will only update
                //     when a client (re)connects, not when otherwise sending/receiving msgs.
                //     Use the msgReceived amd msgSending hooks for session management.
            },
        },
    },
2 Likes

Thanks everyone for the suggestions. I will reach out to my IT team to see what can be done. Appreciate everyone’s responses.

1 Like