Skip to content
This repository has been archived by the owner on Dec 25, 2020. It is now read-only.

Feature Request: Specify routing internal / external #10

Open
andyjhall opened this issue Sep 29, 2019 · 7 comments
Open

Feature Request: Specify routing internal / external #10

andyjhall opened this issue Sep 29, 2019 · 7 comments
Assignees
Labels
enhancement New feature or request in development I am building this in right now question Further information is requested

Comments

@andyjhall
Copy link

Is it possible to specify whether a request should be fulfilled based on the requester being internal / external to the network?

@morph1904
Copy link
Owner

Hi Andy,

I am not sure about this because that is really a DNS thing.

For example, I run an internal DNS server that has my subdomains listed, and they all point to the IP of my Tyger2 instance, that way internal or not, my requests are proxied internally. If they are not listed in my local DNS then by default the local DNS will pass the request to an Internet DNS server and return my static IP creating a loop which my router set up blocks but others may not. Many router setups include a local DNS server you can configure,

There would probably be a way of implementing this but I am not sure of its use case.

Could you explain a little more as to why this feature is needed?

Morph.

@morph1904 morph1904 self-assigned this Sep 30, 2019
@morph1904 morph1904 added enhancement New feature or request question Further information is requested labels Sep 30, 2019
@morph1904
Copy link
Owner

Hi @andyjhall have you any response to the above? I am mapping out the next phase and if i need to factor this in.

Thanks
Morph

@andyjhall
Copy link
Author

andyjhall commented Oct 7, 2019 via email

@morph1904 morph1904 removed the question Further information is requested label Oct 7, 2019
@morph1904
Copy link
Owner

OK I see what you mean now. Leave it with me and I will look into it.

Morph

@morph1904
Copy link
Owner

Hi @andyjhall,

I have some good news for you. I have tested some raw functionality and I can make it so that it allows or blocks individual IP's or IP' ranges (if it is set to allow, it blocks all others). This is working in Caddy only at the moment and I need to add all of the frontend and backend functions to allow this to be set on a per address/application basis.

I think we need some discussion on how best to implement this now.

The rule needs an end point eg. / or /login, the allow or block keyword and at least an ip or ip range (192.168.1.1 or 192.168.1.0/24). I have not yet tested if I can place this rule inside the proxy definition or at the address level or if it will let me do both.

For you would this be better applied to the application or the address?

Regards
Lee

@morph1904 morph1904 added in development I am building this in right now question Further information is requested labels Oct 7, 2019
@andyjhall
Copy link
Author

andyjhall commented Oct 7, 2019 via email

@bugs181
Copy link

bugs181 commented Jun 13, 2020

Adding to the discussion, I tried to do it the way @morph1904 did it above but it would not play nice with my setup due to SSL validation. It kept thinking it was a DNS-MITM attack and would fail.

How I went about it was this:

  • Switched DNS over to Cloudflare
  • Wrote up a script that updates public subdomains with the WAN IP and private subdomains with LAN IP.
  • Block any ports that I don't want to be accessible via Public WAN

The script I wrote up is very specific to my requirements (Specific modem from ISP) but fortunately I made the script smart enough that you could type in any IP address you'd like to update for specific subdomains. It also uses the official Cloudflare API so it should work great for many years to come (barring any feature nerfs or premium costs if they come).

cloudflare-updater.js
'use strict'

const cloudflare = require('cloudflare')
// https://www.npmjs.com/package/cloudflare
// https://github.com/cloudflare/node-cloudflare

const cf = cloudflare({
  email: '...',
  key: '...',
})

const zones = [
  'domain.com'
]

const records = [
  'vpn.domain.com',
]

async function updateCloudflareIP(IP) {
  console.log('Updating cloudflare IP:', IP)

  try {
    const zones = await loadZones()
    for await (let zone of zones) {
      const zoneInfo = {
        name: zone.name,
        id: zone.id
      }

      console.log('Reading zone records:', zoneInfo.name)
      const dnsRecords = await cf.dnsRecords.browse(zoneInfo.id)

      if (!dnsRecords.success || typeof dnsRecords.result !== 'object' || !Array.isArray(dnsRecords.result))
        return console.log('Failed to read DNS configuration')

      let hasError = false
      for await (let nsRecord of dnsRecords.result) {
        if (!records.includes(nsRecord.name))
          continue

        // If Cloudflare IP is already this IP, then don't update it.
        if (nsRecord.content === IP)
          continue

        try {
          console.log('Updating record:', nsRecord.name)
          await cf.dnsRecords.edit(zoneInfo.id, nsRecord.id, {
            type: 'A',
            name: nsRecord.name,
            content: IP,
            ttl: 1,
            proxied: false,
          })

        } catch (err) {
          console.error('Failed to update DNS:', nsRecord.name)
          console.error(err)
          hasError = true
        }
      }

      if (!hasError)
        return IP
      else
        return null
    }

  } catch (err) {
    console.error(err)
    return null
  }
}

const loadedZones = []
async function loadZones() {
  if (loadedZones.length > 0)
    return loadedZones

  try {
    console.log('Finding zones')
    const browseResp = await cf.zones.browse()

    if (!browseResp.success || typeof browseResp.result !== 'object' || !Array.isArray(browseResp.result))
      return console.log('Failed to read zones configuration')

    for (let zone of browseResp.result) {
      if (!zones.includes(zone.name))
        continue

      const zoneInfo = {
        name: zone.name,
        id: zone.id
      }

      loadedZones.push(zoneInfo)
    }

    return loadedZones
  } catch (err) {
    console.error(err)
    return []
  }
}

And I use it like so:

async function checkAndUpdateIP() {
  if (!loginToken)
    loginToken = await login()

  const IP = await loadConnectionSummary()

  if (IP === changedIP) {
    //console.log('Nothing to do, IP has not changed')
  } else {
    changedIP = await updateCloudflareIP(IP)
  }
}

// Check IP change every 5 minutes
setInterval(async() => {
  await checkAndUpdateIP()
}, 5 * (60 * 1000))

NOTE: Written with minimal bandwidth limits in mind. It caches results and re-uses them where appropriate.


How this applies to your situation, is that I would use the above script and forward any internal routes to go to the docker containers, apps, etc. Then any public routes you want available but locked, you could forward to a login page of some kind. Either hosted on Caddy, or your own app. Anything you don't want publicly accessible, you just update the IP directly into the app (private LAN or container IP) bypassing any checks.

Hope this helps!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request in development I am building this in right now question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants