Zum Inhalt

Neuer Hotspot - pfSense

Im Folgenden wird beschrieben, wie Sie eine pfSense Firewall (Version 2.7.2, Community Version) konfigurieren und wie man diese mit unserer Hotspot-Lösung SyCes verbindet.

Für die Installation der pfSense Community Edition (pfSense CE) oder der pfSense Plus Software wird die offizielle Dokumentation Netgate Docs empfohlen.

Voraussetzungen

Um eine pfSense zu konfigurieren, benötigen Sie darauf Administratorzugriff. Dieses Tutorial zeigt die Konfiguration über die GUI-Weboberfläche der pfSense.

Für die Konfiguration benötigen Sie außerdem Informationen über die RADIUS-IP und das Secret des neuen Systems. Diese Informationen können aus SyCes🡕 ausgelesen werden.

Um den Router für SyCes einzurichten, muss der Standort, der diesen Router verwaltet, in der neuen Datenbank vorhanden sein. Für die Konfiguration werden die Mandant-ID und der für den Standort erstellte Token benötigt. Wenn Sie diese Werte auslesen möchten, benötigen Sie gültige Zugangsdaten für SyCes.

Navigieren Sie zur Standortseite des entsprechenden Mandanten und klicken Sie auf der Karte Angaben zum Standort auf den Kopieren Button neben dem Token im Feld Token. Der Link im URL-Feld sollte wie folgt aussehen: /login/<tenant_id>/<location_id>/.

Schritt 1: Einrichten der Netzwerkschnittstelle

Im Reiter Interfaces > Assignment ist zunächst ein Interface zu konfigurieren. Die dazugehörige Schnittstelle des Geräts sollte mit einem Wireless Access Point (WAP) ausgestattet sein, sodass sich nach der Konfiguration die Kundengeräte über die Schnittstelle verbinden können.

Image - Netzwerk Interface 1

Nach dem Klick auf Add wird ein neues Interface hinzugefügt. Danach müssen die Interface-Einstellungen mit Save gespeichert werden. Anschließend kann das Interface durch einen Klick auf dessen Namen weiter konfiguriert werden.

Image - Netzwerk Interface 1

Aktivieren Sie die Schnittstelle und geben Sie ihr optional einen beschreibenden Namen, z. B. GUESTNET. Setzen Sie die IPv4-Konfiguration auf Static IPv4 und geben Sie eine IP-Adresse (z. B. 192.168.5.1) mit einer passenden Netzwerkmaske ein (z. B. /24).
Beispielhafte Interface-Einstellungen können dem folgenden Bild entnommen werden:

Image - Interface Einstellungen

Speichern Sie die Einstellungen mit Save und wenden Sie diese mit Apply Changes an.

Schritt 2: DHCP-Server Konfiguration

Für das neu erstellte Interface muss nun ein DHCP-Server eingerichtet werden. Dies kann in Services > DHCP Server erfolgen. Falls als DHCP Backend noch ISC DHCP eingestellt ist (Werkseinstellungen), empfiehlt es sich, das DHCP Backend unter System > Advanced > Networking auf DHCP umzustellen.

Wählen Sie im Tab Services > DHCP Server das erstellte Interface aus.

Image - Services > DHCP Server

Hier muss der DHCP-Server durch einen Klick auf das Kontrollkästchen bei Enable aktiviert werden. Setzen Sie anschließend den DHCP-Bereich für die von Ihnen gewünschten IP-Adressen. Optional kann der DNS Server auf 1.1.1.1 (Cloudflare) und/oder 8.8.8.8 (Google) gesetzt werden.

Image - DHCP Einstellungen

Speichern Sie die Einstellungen mit Save und wenden Sie diese mit Apply Changes an.

Schritt 3: Erstellen der Firewall-Regeln

Im nächsten Schritt müssen die Firewall-Regeln erstellt werden, um den Zugriff von Endgeräten aus dem Hotspot-Netzwerk zu ermöglichen.

Navigieren Sie dafür zu Firewall > Rules.

Image - Firewall > Rules

Wählen Sie hier das im Schritt 2 erstellte Interface aus. Fügen Sie mit dem Klick auf Add eine neue Firewall-Regel hinzu. Diese Regel dient dazu, den Zugriff aud das Captive Portal zu ermöglichen.

Image - Firewall Regel hinzufügen

Setzen Sie die Regel auf Pass, das Protokoll auf TCP, die Quelle auf Interface Subnetz, das Ziel auf Interface Adresse und geben Sie im Feld Destination port range den Bereich 8000 to 10000 ein. Dies bedeutet, dass der Datenverkehr auf allen Ports zwischen 8000 und 10000 erlaubt wird. In der Praxis wird für das Captive Portal jedoch meist nur Port 8000 benötigt.

Optional kann die Protokollverfolgung aktiviert werden, um den Datenverkehr zu überwachen und potenzielle Probleme zu diagnostizieren.

Image - Firewall Regel für das Captive Portal

Speichern Sie die Einstellungen mit Save und wenden Sie diese mit Apply Changes an.

Fügen sie nun eine weitere Regel hinzu, um den Internetzugang für Hotspot-Nutzer zu ermöglichen. Setzen Sie diese Regel auf Pass, das Interface auf das neue Interface (z. B. GUESTNET), das Protocol auf Any, die Quelle auf Interface subnets und das Ziel auf Any. Optional können sie dieser Regel eine Beschreibung hinzufügen.

Image - Firewall Regel für Gastzugang

Speichern Sie die Einstellungen mit Save und wenden Sie diese mit Apply Changes an.

Schritt 4: RADIUS-Server-Konfiguration

In diesem Abschnitt wird der RADIUS Server auf den SyCes2-Webserver eingestellt. Dafür muss dieser zunächst im User Manager unter System > User Manager im Reiter Authentication Servers angelegt werden.

Image - System > User Manager ...

Image - Authentication Servers im User Manager auswählen

Klicken Sie auf Add, um einen Authentifizierungs-Server hinzuzufügen.

Image - Authentifizierungs-Server hinzufügen

Geben Sie dem Authentifizierungs-Server einen Namen und wählen Sie bei Type den Typ RADIUS aus. Zusätzlich müssen die RADIUS IP und das RADIUS Secret angegeben werden. Diese Informationen können auf SyCes gefunden werden. Speichern Sie die Eingaben anschließend mit Save.

Image - RADIUS-Server Konfiguration

Schritt 5: Einrichten des Captive Portals

Navigieren Sie zu Services > Captive Portal, um das Captive Portal einzurichten.

Image - Services > Captive Portal

Fügen Sie mit Add eine neue Zone für das Captive Portal hinzu.

Image - Captive Portal hinzufügen

Für die Zone kann optional ein Name und eine Beschreibung eingegeben werden. Klicken Sie anschließend auf Save / Continue.

Image - Name für das Captive Portal eingeben, dann Speichern und weiter

Aktivieren Sie nun das Captive Portal, wählen Sie das GUESTNET-Interface aus und legen Sie einen Idle-Timeout fest (z.B 5 Minuten).

Image - Captive Portal aktivieren ...

Image - Idle timeout setzen

Scrollen Sie zum Abschnitt Authentication, wählen Sie Use an Authentication backend als Authentifizierungsmethode aus und wählen Sie bei Authentication Server den im vorherigen Schritt hinzugefügten Server aus.

Image - RADIUS Server als Authentifizierungsmethode auswählen

Scrollen Sie nun zum Abschnitt Accounting, aktivieren Sie das Senden von RADIUS-Accounting-Paketen und wählen Sie den konfigurierten RADIUS-Server als Accounting-Server aus. Wählen Sie Keine Updates oder Interim-Updates, aktivieren Sie den Idle-Timeout und speichern Sie die Einstellungen mit Save.

Image - RADIUS Accounting Server setzen

Schritt 6: Testen des Captive Portals

Verbinden Sie ein Gerät mit dem in den vorherigen Schritten eingerichteten Netz und rufen Sie mit einem Browser eine Webseite auf, um das Captive Portal zu testen. Nun sollte das Captive Portal erscheinen, in dem Sie sich mit Ihren Anmeldedaten anmelden können.

Das Standard-pfSense Captive Portal sollte wie folgt aussehen:

Image - pfSense Captive Portal

Sollten bei der Konfiguration Ihres Routers Probleme oder unerwartetes Verhalten auftreten, kontaktieren Sie uns bitte über tsp-ms-dev@concat.de.

Schritt 7: Anpassung des lokalen Captive Portals (optional)

PfSense bietet eine Möglichkeit, anstatt des vordefinierten Captive Portals, eine benutzerfreundliche Portalseite hochzuladen. Dafür muss man im Reiter Services > Captive Portal die in Schritt 5 konfigurierte Zone bearbeiten.

Image - Services > Captive Portal ...

Image - Captive Zone bearbeiten

Stellen Sie sicher, dass das Kontrollkästchen bei Enable Captive Portal gesetzt ist. Scrollen sie danach zur Schaltfläche Use custom captive portal page und aktivieren Sie auch hier das Kontrollkästchen. Anschließend können Sie über Browse eine HTML-Datei hochladen. Alternativ können Sie den unten bereitgestellten Webseiten-Code verwenden, um eine von uns vorgeschlagene Seite einzustellen. Mit View Page Contents können Sie die gerenderte Seite anzeigen lassen.

Image - Custom Captive Portal Upload

Auch andere Seiten des Captive-Portals und die Logos können in diesem Tab angepasst werden. Im folgenden Abschnitt finden Sie ein Beispiel für eine benutzerdefinierte Anmeldeseite.

Um von einem Hotspot-Endgerät auf SyCes ein Benutzeraccount erstellen zu können, muss die URL für das SyCes-Backend vor der Authentifizierung verfügbar gemacht werden. Klicken Sie dafür in Services > Captive Portal auf den Reiter Allowed Hostnames und dann auf Add, um einen neuen Eintrag hinzuzufügen. Geben Sie unter Hostname backend.syces.de ein. Sie können optional eine Beschreibung für den Eintrag hinzufügen. Der Rest der Einstellungen kann auf default bleiben. Speichern Sie die Einstellungen anschließend mit Save.

Image - Allowed Hostname hinzufügen

Anmeldeseite

Nachfolgend finden Sie den Website-Code für die oben dargestellte angepasste Anmeldeseite. Dort finden Sie auch den Code für Seiten, die nur die Funktion Anonym oder Login nutzen. Anmeldeseiten auf Englisch finden Sie unter New Hotspot - pfSense

Dieser Code kann für die Funktionalität kopiert werden, folgende Felder müssen jedoch noch ersetzt werden:

  • <tenant_id>: Ersetzen Sie dies durch Ihre Mandanten-ID in SyCes
  • <location_identification_token>: Ersetzen Sie dies durch den Token des konfigurierten Standorts in SyCes mit Routertyp pfSense. Dies ist für die Anonym Funktion erforderlich
  • const tenant_domain = "";: Variable setzen, um das automatische Anhängen der Mandanten-Domäne an den Benutzernamen vor der Anmeldung zu ermöglichen. Dadurch wird die Anmeldung mit "Accountname" anstelle von "Accountname@Mandantendomäne" ermöglicht. Die Domäne kann von der Mandantenseite in SyCes kopiert werden.
<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>SyCes® - Hotspot</title>
    <link rel="stylesheet" href="https://backend.syces.de/static/login/css/styles.css">
  </head>
  <body>
    <div class="message-container">
      <div>
        <img
          src="https://backend.syces.de/static/login/img/logo2.png"
          alt="SyCes-Logo"
          width="500"
          height="139"
          class="img"
        />
      </div>
      <div class="center window">
        <h1>Benutzer-Login</h1>
        <form id="default_login" action="$PORTAL_ACTION$" method="post">
          <input name="redirurl" type="hidden" value="$PORTAL_REDIRURL$" />
          <input name="zone" type="hidden" value="$PORTAL_ZONE$" />
          <input name="accept" type="hidden" value="Continue" />
          <table>
            <tr>
              <td><label for="pf_un">Benutzername:</label></td>
              <td><input name="auth_user" type="text" id="pf_un" /></td>
            </tr>
            <tr>
              <td><label for="pf_pd">Passwort:</label></td>
              <td><input name="auth_pass" type="password" id="pf_pd" /></td>
            </tr>
            <tr>
              <td>
                <label for="tos_check_login"
                >Ich akzeptiere die
                <a href="https://backend.syces.de/login/<tenant_id>/tos/"
                  >Geschäftsbedingungen</a
                >:</label
                >
              </td>
              <td>
                <label class="checkbox">
                  <input
                    type="checkbox"
                    name="tos"
                    id="tos_check_login"
                    onclick="handleTOSClick(this)"
                  />
                  <span class="checkmark"></span>
                </label>
              </td>
            </tr>
            <tr>
              <td></td>
              <td>
                <button
                  class="primary"
                  type="submit"
                  id="submit_btn_login"
                  disabled
                >
                Absenden
                </button>
              </td>
            </tr>
          </table>
        </form>
      </div>
      <div class="center window">
        <h1>Anonym registrieren</h1>
        <form id="anon_login">
          <table>
            <tr>
              <td>
                <label for="tos_check_reg">
                Ich akzeptiere die
                <a href="https://backend.syces.de/login/<tenant_id>/tos/"
                  >Geschäftsbedingungen</a
                >:
                </label>
              </td>
              <td>
                <label class="checkbox">
                <input
                  type="checkbox"
                  name="tos"
                  id="tos_check_reg"
                  onclick="handleTOSClick(this)"
                />
                <span class="checkmark"></span>
                </label>
              </td>
            </tr>
            <tr>
              <td></td>
              <td>
                <button
                  class="primary"
                  type="submit"
                  id="submit_btn_reg"
                  disabled
                >
                Absenden
                </button>
              </td>
            </tr>
          </table>
        </form>
      </div>
    </div>
  </body>
  <script>
    const url =
    "https://backend.syces.de/login/token_login/<location_identification_token>/";

    const anon_form = document.getElementById("anon_login");
    const default_form = document.getElementById("default_login");

    const tos_checkbox_reg = document.getElementById("tos_check_reg");
    const tos_checkbox_login = document.getElementById("tos_check_login");
    const submit_button_reg = document.getElementById("submit_btn_reg");
    const submit_button_login = document.getElementById("submit_btn_login");

    function handleTOSClick(cb) {
      if (cb.checked === true) {
        tos_checkbox_reg.checked = true;
        tos_checkbox_login.checked = true;
        submit_button_reg.disabled = false;
        submit_button_login.disabled = false;
      } else {
        tos_checkbox_reg.checked = false;
        tos_checkbox_login.checked = false;
        submit_button_reg.disabled = true;
        submit_button_login.disabled = true;
      }
    }

    function sendRequest() {
      fetch(url)
      .then((response) => response.json())
      .then((data) => {
        let field_username = document.getElementById("pf_un");
        let field_password = document.getElementById("pf_pd");
        field_username.value = data.login_name;
        field_password.value = data.password;
        default_form.submit();
      })
      .catch((error) => console.error(error));
    }

    function appendDomainIfMissing() {
      const tenant_domain = "";
      let form_input_user = document.getElementById("ft_un");
      let username = form_input_user.value;

      if (
        tenant_domain !== "" &&
        !username.includes("@") &&
        !username.endsWith("@" + tenant_domain)
      ) {
        form_input_user.value = username + "@" + tenant_domain;
      }
    }

    anon_form.addEventListener("submit", function (e) {
      e.preventDefault();
      sendRequest();
    });

    default_form.addEventListener("submit", function (e) {
      appendDomainIfMissing();
    });
  </script>
</html>
<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>SyCes® - Hotspot</title>
    <link rel="stylesheet" href="https://backend.syces.de/static/login/css/styles.css">
  </head>
  <body>
    <div class="message-container">
      <div>
        <img
          src="https://backend.syces.de/static/login/img/logo2.png"
          alt="SyCes-Logo"
          width="500"
          height="139"
          class="img"
        />
      </div>
      <div class="center window">
        <h1>Benutzer-Login</h1>
        <form id="default_login" action="$PORTAL_ACTION$" method="post">
          <input name="redirurl" type="hidden" value="$PORTAL_REDIRURL$" />
          <input name="zone" type="hidden" value="$PORTAL_ZONE$" />
          <input name="accept" type="hidden" value="Continue" />
          <table>
            <tr>
              <td><label for="pf_un">Benutzername:</label></td>
              <td><input name="auth_user" type="text" id="pf_un" /></td>
            </tr>
            <tr>
              <td><label for="pf_pd">Passwort:</label></td>
              <td><input name="auth_pass" type="password" id="pf_pd" /></td>
            </tr>
            <tr>
              <td>
                <label for="tos_check_login">
                  Ich akzeptiere die
                  <a href="https://backend.syces.de/login/<tenant_id>/tos/">
                    Geschäftsbedingungen
                  </a>:
                </label>
              </td>
              <td>
                <label class="checkbox">
                  <input
                    type="checkbox"
                    name="tos"
                    id="tos_check_login"
                    onclick="handleTOSClick(this)"
                  />
                  <span class="checkmark"></span>
                </label>
              </td>
            </tr>
            <tr>
              <td></td>
              <td>
                <button
                  class="primary"
                  type="submit"
                  id="submit_btn_login"
                  disabled
                >
                  Absenden
                </button>
              </td>
            </tr>
          </table>
        </form>
      </div>
    </div>
  </body>
  <script>
    const default_form = document.getElementById("default_login");

    const tos_checkbox_login = document.getElementById("tos_check_login");
    const submit_button_login = document.getElementById("submit_btn_login");

    function handleTOSClick(cb) {
      if (cb.checked === true) {
        tos_checkbox_login.checked = true;
        submit_button_login.disabled = false;
      } else {
        tos_checkbox_login.checked = false;
        submit_button_login.disabled = true;
      }
    }

    function appendDomainIfMissing() {
      const tenant_domain = "";
      let form_input_user = document.getElementById("ft_un");
      let username = form_input_user.value;

      if (
        tenant_domain !== "" &&
        !username.includes("@") &&
        !username.endsWith("@" + tenant_domain)
      ) {
        form_input_user.value = username + "@" + tenant_domain;
      }
    }

    default_form.addEventListener("submit", function (e) {
    appendDomainIfMissing();
    });
  </script>
</html>
<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>SyCes® - Hotspot</title>
    <link rel="stylesheet" href="https://backend.syces.de/static/login/css/styles.css">
  </head>
  <body>
    <div class="message-container">
      <div>
        <img
          src="https://backend.syces.de/static/login/img/logo2.png"
          alt="SyCes-Logo"
          width="500"
          height="139"
          class="img"
        />
      </div>
      <div class="center window">
        <h1>Anonym registrieren</h1>
        <form id="anon_login">
          <table>
            <tr>
              <td>
                <label for="tos_check_reg">
                  Ich akzeptiere die
                  <a href="https://backend.syces.de/login/<tenant_id>/tos/">
                    Geschäftsbedingungen
                  </a>:
                </label>
              </td>
              <td>
                <label class="checkbox">
                  <input
                    type="checkbox"
                    name="tos"
                    id="tos_check_reg"
                    onclick="handleTOSClick(this)"
                  />
                  <span class="checkmark"></span>
                </label>
              </td>
            </tr>
            <tr>
              <td></td>
              <td>
                <button
                  class="primary"
                  type="submit"
                  id="submit_btn_reg"
                  disabled
                >
                  Absenden
                </button>
              </td>
            </tr>
          </table>
        </form>
        <form
          id="default_login"
          action="$PORTAL_ACTION$"
          method="post"
          style="display:none"
        >
          <input name="redirurl" type="hidden" value="$PORTAL_REDIRURL$" />
          <input name="zone" type="hidden" value="$PORTAL_ZONE$" />
          <input name="accept" type="hidden" value="Continue" />
          <h1>
            Login
          </h1>
          <table>
            <tr>
              <td><label for="pf_un">Username:</label></td>
              <td><input name="auth_user" type="text" id="pf_un" /></td>
            </tr>
            <tr>
              <td><label for="pf_pd">Password:</label></td>
              <td><input name="auth_pass" type="password" id="pf_pd" /></td>
            </tr>
            <tr>
              <td>
                <label for="tos_check_login"
                >I agree to the
                <a href="https://backend.syces.de/login/<tenant_id>/tos/"
                  >Terms and conditions</a
                >:</label
                >
              </td>
              <td>
                <label class="checkbox">
                  <input
                    type="checkbox"
                    name="tos"
                    id="tos_check_login"
                    onclick="handleTOSClick(this)"
                  />
                  <span class="checkmark"></span>
                </label>
              </td>
            </tr>
            <tr>
              <td></td>
              <td>
                <button
                  class="primary"
                  type="submit"
                  id="submit_btn_login"
                  disabled
                >
                Submit
                </button>
              </td>
            </tr>
          </table>
        </form>
      </div>
    </div>
  </body>
  <script>
    const url =
    "https://backend.syces.de/login/token_login/<location_identification_token>/";

    const anon_form = document.getElementById("anon_login");
    const default_form = document.getElementById("default_login");

    const tos_checkbox_reg = document.getElementById("tos_check_reg");
    const tos_checkbox_login = document.getElementById("tos_check_login");
    const submit_button_reg = document.getElementById("submit_btn_reg");
    const submit_button_login = document.getElementById("submit_btn_login");

    function handleTOSClick(cb) {
      if (cb.checked === true) {
        tos_checkbox_reg.checked = true;
        tos_checkbox_login.checked = true;
        submit_button_reg.disabled = false;
        submit_button_login.disabled = false;
      } else {
        tos_checkbox_reg.checked = false;
        tos_checkbox_login.checked = false;
        submit_button_reg.disabled = true;
        submit_button_login.disabled = true;
      }
    }

    function sendRequest() {
      fetch(url)
      .then(response => response.json())
      .then(data => {
        let field_username = document.getElementById("pf_un");
        let field_password = document.getElementById("pf_pd");
        field_username.value = data.login_name;
        field_password.value = data.password;
        default_form.submit();
        }
      )
      .catch(error => console.error(error));
    }

    anon_form.addEventListener("submit", function(e) {
      e.preventDefault();
      sendRequest();
    });
  </script>
</html>

Mit Restore Default Page können Sie die Einstellungen wieder auf das voreingestellte Captive Portal zurücksetzen.