diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 14:11:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 14:11:00 +0000 |
commit | af754e596a8dbb05ed8580c342e7fe02e08b28e0 (patch) | |
tree | b2f334c2b55ede42081aa6710a72da784547d8ea /doc/antora | |
parent | Initial commit. (diff) | |
download | freeradius-af754e596a8dbb05ed8580c342e7fe02e08b28e0.tar.xz freeradius-af754e596a8dbb05ed8580c342e7fe02e08b28e0.zip |
Adding upstream version 3.2.3+dfsg.upstream/3.2.3+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'doc/antora')
83 files changed, 7153 insertions, 0 deletions
diff --git a/doc/antora/antora.yml b/doc/antora/antora.yml new file mode 100644 index 0000000..e345e9c --- /dev/null +++ b/doc/antora/antora.yml @@ -0,0 +1,18 @@ +# +# Metadata for the freeradius-server component +# Examples of other components are the PAM module, +# apache module, and the client library. +# +name: freeradius-server +title: The FreeRADIUS Server +version: '3.2.3' +start_page: ROOT:index.adoc +nav: +- modules/ROOT/nav.adoc +- modules/installation/nav.adoc +- modules/concepts/nav.adoc +- modules/howto/nav.adoc +- modules/tutorials/nav.adoc +- modules/unlang/nav.adoc +- modules/developers/nav.adoc +- modules/raddb/nav.adoc diff --git a/doc/antora/modules/ROOT/assets/images/favicon.png b/doc/antora/modules/ROOT/assets/images/favicon.png Binary files differnew file mode 100644 index 0000000..8c71104 --- /dev/null +++ b/doc/antora/modules/ROOT/assets/images/favicon.png diff --git a/doc/antora/modules/ROOT/assets/images/favicon.svg b/doc/antora/modules/ROOT/assets/images/favicon.svg new file mode 100644 index 0000000..7476355 --- /dev/null +++ b/doc/antora/modules/ROOT/assets/images/favicon.svg @@ -0,0 +1,104 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 160 160" style="enable-background:new 0 0 160 160;" xml:space="preserve"> +<style type="text/css"> + .st0{fill:none;stroke:#FFD62B;stroke-width:3;stroke-miterlimit:10;} + .st1{fill:#B0C3C6;} + .st2{fill:none;stroke:#DEE7E8;stroke-miterlimit:10;} + .st3{fill:#00A6E2;} + .st4{fill:none;stroke:#B0C3C6;stroke-width:2;stroke-miterlimit:10;} + .st5{fill:#666666;} + .st6{fill:none;stroke:#B0C3C6;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:10;} + .st7{fill:#00C9ED;} + .st8{fill:#FFFFFF;stroke:#B0C3C6;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:10;} + .st9{fill:#303030;} + .st10{opacity:0.4;fill:#F6F6F6;} + .st11{fill:none;stroke:#999999;stroke-width:2;stroke-miterlimit:10;} + .st12{fill:#999999;} + .st13{fill:#FFFFFF;} + .st14{clip-path:url(#SVGID_2_);fill:#2F3537;} + .st15{opacity:0.3;fill:none;stroke:#9FB1B3;stroke-width:2;stroke-miterlimit:10;} + .st16{fill:none;stroke:#FFFFFF;stroke-miterlimit:10;} + .st17{fill:#FFFFFF;stroke:#FFFFFF;stroke-miterlimit:10;} + .st18{fill:none;stroke:#303030;stroke-miterlimit:10;} + .st19{opacity:0.8;fill:#B0C3C6;} + .st20{opacity:0.7;} + .st21{opacity:0.8;clip-path:url(#SVGID_4_);fill:#00A6E2;} + .st22{opacity:0.8;fill:#00A6E2;} + .st23{opacity:0.8;clip-path:url(#SVGID_6_);fill:#00A6E2;} + .st24{clip-path:url(#SVGID_8_);} + .st25{clip-path:url(#SVGID_10_);} + .st26{fill:none;stroke:#B0C3C6;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:10;} + .st27{opacity:0.3;fill:none;stroke:#9FB1B3;stroke-width:3;stroke-miterlimit:10;} + .st28{fill:#FFFFFF;stroke:#B0C3C6;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:10;} + .st29{clip-path:url(#SVGID_12_);fill:#2F3537;} + .st30{clip-path:url(#SVGID_14_);fill:#B0C3C6;} + .st31{fill:#33B8E8;} + .st32{fill:#238DB4;} + .st33{fill:#E2E7E8;} + .st34{clip-path:url(#SVGID_16_);} + .st35{fill:#FFFFFF;stroke:#B0C3C6;stroke-width:2;stroke-miterlimit:10;} + .st36{fill:#B4CBCE;} + .st37{fill:#003147;} + .st38{fill:#FFD62B;} + .st39{fill:#00B78E;} + .st40{fill:#FF7824;} + .st41{fill:#FF3223;} + .st42{fill:#7955DF;} + .st43{fill:none;stroke:#FF3223;stroke-width:2;stroke-miterlimit:10;} + .st44{fill:none;stroke:#00A6E2;stroke-width:2;stroke-miterlimit:10;} + .st45{clip-path:url(#SVGID_18_);fill:#303030;} + .st46{fill:#F5C81F;} + .st47{fill:#F49F90;} + .st48{fill:#F3EEDE;} + .st49{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;} + .st50{fill:#00131F;} + .st51{clip-path:url(#SVGID_20_);fill:#303030;} + .st52{fill:none;stroke:#FFFFFF;stroke-width:2;stroke-miterlimit:10;} + .st53{clip-path:url(#SVGID_22_);fill:#303030;} + .st54{clip-path:url(#SVGID_24_);fill:#303030;} +</style> +<path class="st0" d="M72.3,25.6c0,0-2.3-3.2-6-2.3"/> +<g> + <polygon class="st50" points="79.2,73 20.3,37.9 86.2,19.3 145.1,54.4 "/> + <g> + <g> + <g> + <g> + <path class="st3" d="M126.2,113.6c-4.4,0-8.2-3-9.3-7.1c2.8,1,5.7,1.6,8.8,1.6c4.4,0,7.9-3.5,7.9-7.9c0-4.4-3.5-7.9-7.9-7.9 + c-5.3,0-9.9-4.3-9.9-9.6c0-0.7,0-2.1,0-2.1v-6.1c0-19.4-15.7-35.2-35.2-35.2c-19.4,0-35.2,15.7-35.2,35.2v6.1c0,0,0,1.3,0,2.1 + c0,5.3-4.6,9.6-9.9,9.6c-4.4,0-7.9,3.5-7.9,7.9c0,4.4,3.5,7.9,7.9,7.9c3.5,0,6.8-0.7,9.8-2c-1,4.3-4.8,7.5-9.4,7.5 + c-4.4,0-7.9,3.5-7.9,7.9c0,4.4,3.5,7.9,7.9,7.9c6.9,0,13.2-2.8,17.8-7.3v10.4c0,4.4,3.5,7.9,7.9,7.9c4.4,0,7.9-3.5,7.9-7.9 + v-14.8h3.1v18.7c0,4.4,3.5,7.9,7.9,7.9c4.4,0,7.9-3.5,7.9-7.9v-18.7h3.1v14.8c0,4.4,3.5,7.9,7.9,7.9c4.4,0,7.9-3.5,7.9-7.9 + v-11.4c4.7,5.1,11.3,8.2,18.7,8.2c4.4,0,7.9-3.5,7.9-7.9C134.1,117.1,130.5,113.6,126.2,113.6z"/> + <g> + <circle class="st13" cx="80" cy="76.9" r="17.5"/> + <g> + <defs> + <circle id="SVGID_19_" cx="80" cy="76.9" r="17.5"/> + </defs> + <use xlink:href="#SVGID_19_" style="overflow:visible;fill:#FFFFFF;"/> + <clipPath id="SVGID_2_"> + <use xlink:href="#SVGID_19_" style="overflow:visible;"/> + </clipPath> + <path style="clip-path:url(#SVGID_2_);fill:#303030;" d="M73.3,85.5c-6.7,0-12.2-5.5-12.2-12.2c0-6.7,5.5-12.2,12.2-12.2 + s12.2,5.5,12.2,12.2C85.5,80,80.1,85.5,73.3,85.5z"/> + </g> + </g> + </g> + </g> + </g> + </g> + <g> + <path class="st50" d="M46,65.8c0-19.1,15.5-29.7,34.6-29.7s34.6,10.6,34.6,29.7c0,0-9.6-9.8-34.2-9.8S46,65.8,46,65.8z"/> + </g> + <g> + <path class="st0" d="M66.3,23.3c0,0-10.7,2.8-10.2,9.7v20.8"/> + <line class="st0" x1="56" y1="53.7" x2="59.3" y2="66"/> + <line class="st0" x1="56" y1="53.7" x2="54" y2="64.7"/> + <line class="st0" x1="56" y1="53.7" x2="62" y2="59.2"/> + <line class="st0" x1="56" y1="53.7" x2="50.8" y2="60.4"/> + </g> +</g> +</svg> diff --git a/doc/antora/modules/ROOT/assets/images/networkradius.png b/doc/antora/modules/ROOT/assets/images/networkradius.png Binary files differnew file mode 100644 index 0000000..bf1ea80 --- /dev/null +++ b/doc/antora/modules/ROOT/assets/images/networkradius.png diff --git a/doc/antora/modules/ROOT/nav.adoc b/doc/antora/modules/ROOT/nav.adoc new file mode 100644 index 0000000..3d92412 --- /dev/null +++ b/doc/antora/modules/ROOT/nav.adoc @@ -0,0 +1 @@ +* xref:index.adoc[Introduction] diff --git a/doc/antora/modules/ROOT/pages/directories.adoc b/doc/antora/modules/ROOT/pages/directories.adoc new file mode 100644 index 0000000..9b16249 --- /dev/null +++ b/doc/antora/modules/ROOT/pages/directories.adoc @@ -0,0 +1,69 @@ += Directories + +The directories in the server source are laid out ad follows: + +== Documentation + +[width="100%",cols="50%,50%",options="header",] +|=== +| Directory | Description +| `doc/` | Various snippets of documentation. +| `doc/introduction/` | Concepts and introduction to FreeRADIUS. +| `doc/raddb/` | HTML versions of the configuration files. +| `doc/developers/` | Developer documentation for internal APIs +| `doc/unlang/` | The unlang processing language. +| `doc/upgrade/` | How to upgrade from version 3 to version 4. +| `doc/rfc/` | Copies of the RFC’s. If you have Perl, do a `make` in + that directory, and look at the HTML output. +| `doc/antora/` | Metadata and documentation source files to build + an Antora based documentation site. +| `doc/doxygen/` | Files to build a Doxygen site from the source code. +| `man/` | Unix Manual pages for the server, configuration files, + and associated utilities. +|=== + +== Utility + +[cols=",",options="header",] +|=== +|Directory | Description +| `mibs/` | SNMP Mibs for the server. +| `scripts/` | Sample scripts for startup and maintenance. +|=== + +== Configuration + +[width="100%",cols="50%,50%",options="header",] +|=== +| Directory | Description +| `raddb/` | Sample configuration files for the server. +| `raddb/mods-available` | Module configuration files. +| `raddb/mods-enabled` | Directory containing symlinks to `raddb/mods-available`. + Controls which modules are enabled. +| `raddb/sites-available` | Virtual servers. +| `raddb/sites-enabled` | Directory containing symlinks to `raddb/sites-available`. + Control which virtual servers are enabled. +|=== + +== Packaging + +[cols=",",options="header",] +|=== +|Directory | Description +| `debian/` | Files to build a `freeradius` Debian Linux package. +| `redhat/` | Additional files for a RedHat Linux system. +| `suse/` | Additional files for a SuSE (UnitedLinux) system. +|=== + +== Source + +[cols=",",options="header",] +|=== +|Directory | Description +| `src/` | Source code. +| `src/bin/` | Source code for the daemon and associated utilities. +| `src/lib/` | Source code for various utility libraries. +| `src/include/` | Header files. +| `src/protocols/` | Dynamic frontend plug-in modules. +| `src/modules/` | Dynamic backend plug-in modules. +|=== diff --git a/doc/antora/modules/ROOT/pages/index.adoc b/doc/antora/modules/ROOT/pages/index.adoc new file mode 100644 index 0000000..e9bc7a0 --- /dev/null +++ b/doc/antora/modules/ROOT/pages/index.adoc @@ -0,0 +1,137 @@ += Introduction + +This is the documentation for FreeRADIUS, version 3. The documentation +is available under the Creative Commons Non-Commercial license, as given +in the `LICENSE` file in this directory. + +FreeRADIUS is a complex piece of software with many configuration +options. However, we have taken great care to make the default +configuration work in most circumstances. The result is that for most +simple systems, it is trivial to install and configure the server. For +those situations, this documentation will serve to answer basic +questions about functionality, configuration, etc. + +For more complex requirements, FreeRADIUS can be difficult to +configure. The reason for this difficulty is that the server can do +almost anything, which means that there are a near-infinite number of +ways to configure it. The question for an administrator, then, is what +piece of the configuration to change, and how to change it. + +This documentation will answer those questions. The FreeRADIUS team has +put substantial effort into writing the documentation for this release. +Everything in the server is fully documented, and there are many +`how-to` guides available. + +The documentation is split into sections by subject area, oganized by +desired outcome. At a high level, the subject areas describe: + +* xref:concepts:index.adoc[Concepts] and introduction for newcomers. +* xref:installation:index.adoc[Installing] and xref:installation:upgrade.adoc[upgrading] FreeRADIUS. +* The syntax of the xref:unlang:index.adoc[unlang] processing language. +* The xref:raddb:index.adoc[configuration files] located in `/etc/raddb/`, or `/etc/freeradius/` +* Various xref:howto:index.adoc[how-to] guides. +* xref:developers:index.adoc[Developer documentation]. + +This organization means that for example, the `ldap` module will have +documention located in multiple places. We feel that organizing the +documentation by desired _goal_ is better than the alternatives. + +Within each section, the documentation is split into small pages, which +are generally no more than a few screens worth of information. We feel +that having multiple small pages with cross-links is more helpful than +having a smaller number of enormous pages. This division ensures that +(for example) the `how-to` guides are split into a series of small +steps, each of which can be performed quickly. + +We hope that this extended documentation will address any lingering +concerns about the quality of the FreeRADIUS documentation. + +== Changes From Earlier Versions + +Administrators who have version 2 and wish to upgrade to version 3 +should read the xref:installation:upgrade.adoc[upgrading] documentation. +That documentation explains the differences between the two versions, and +how an existing configuration can be reproduced in the latest +release. We do _not_ recommend using version 2 configuration files +with version 3. The configuration files are _not_ compatible across a +major version upgrade. + +== Getting Started with FreeRADIUS + +FreeRADIUS can be installed using the pre-built packages available +from http://packages.networkradius.com[Network RADIUS, +window="_blank"]. That page contains packages for all common OS +distributions. New packages are available as soon as a new version +has been released. Packages for older releases are also available for +historical purposes. + +FreeRADIUS can also be installed from the source code. Please see the +xref:installation:index.adoc[installation guide] for instructions. + +WARNING: Many Operating System distributions ship versions of FreeRADIUS +which are years out of date. Those versions may contain bugs which have +been fixed in newer releases. We recommend using the +http://packages.networkradius.com[Network RADIUS, window="_blank"] packages where +possible. + +Administrators who are new to FreeRADIUS should read the +xref:concepts:index.adoc[concepts section] as it describes the concepts behind +FreeRADIUS. It is vital for newcomers to understand these concepts, as the rest +of the documentation assumes familiarity with them. + +A detailed xref:unlang:index.adoc[unlang] reference guide is also available. +This section describes the syntax and functionality of the keywords, +data types, etc. used in the `unlang` processing language. + +All of the xref:raddb:index.adoc[configuration files] are available in +hypertext format. In can often be easier to read the configuration files +in a nicely formatted version, instead of as a fixed-width font in a +text editor. + +For specific problem solving, we recommend the xref:howto:index.adoc[how-to] +guides. These guides give instructions for reaching high-level goals, or +for configuring and testing individual xref:howto:modules/index.adoc[modules]. + +There is also xref:developers:index.adoc[developer documentation]. This section +documents the APIs for developers. Most people can ignore it. + +== Debugging + +If you have ANY problems, concerns, or surprises when running the +server, the the server should be run in debugging mode as root, from the +command line: + +``` +# radiusd -X +``` + +It will produce a large number of messages. The answers to many +questions, and the solution to many problems, can usually be found in +these messages. When run in a terminal window, error messages will be +shown in red text, and warning messages will be shown in yellow text. + +For other use-cases, please look for `ERROR` or `WARNING` in the +debug output. In many cases, those messages describe exactly what is +going wrong, and how to fix it. + +For further details, about the debug output see the +http://wiki.freeradius.org/radiusd-X[radiusd-X, window="_blank"] page on the +http://wiki.freeradius.org[wiki, window="_blank"]. + +== Getting Help + +We also recommend joining the +http://lists.freeradius.org/mailman/listinfo/freeradius-users[mailing +list] in order to ask questions and receive answers. The developers are +not on Stack Overflow, IRC, or other web sites. While the FreeRADIUS +source is available on +https://github.com/FreeRADIUS/freeradius-server/[GitHub, window="_blank"], questions +posted there will not be answered. + +Before posting to the list, please read the +http://wiki.freeradius.org/list-help[list help, window="_blank"] page. That page explains +how to run the server in debugging mode; how to understand the debug +output; and what information to post to the list. + +Commercial support for FreeRADIUS is available from +https://networkradius.com/freeradius-support/[Network RADIUS, window="_blank"]. diff --git a/doc/antora/modules/howto/nav.adoc b/doc/antora/modules/howto/nav.adoc new file mode 100644 index 0000000..351200b --- /dev/null +++ b/doc/antora/modules/howto/nav.adoc @@ -0,0 +1,19 @@ +* xref:index.adoc[Howto Guides] +** Protocols +**** xref:protocols/dhcp/index.adoc[DHCP] +***** xref:protocols/dhcp/prepare.adoc[Preparation] +***** xref:protocols/dhcp/enable.adoc[Enabling the DHCP service] +***** xref:protocols/dhcp/test.adoc[Testing the DHCP service] +***** xref:protocols/dhcp/policy.adoc[Defining the DHCP policy] +****** xref:protocols/dhcp/policy_ippool_creation.adoc[IP pool creation] +****** xref:protocols/dhcp/policy_common_options.adoc[Common options] +****** xref:protocols/dhcp/policy_network_options.adoc[Network options and IP pool selection] +****** xref:protocols/dhcp/policy_subnet_options.adoc[Subnet options] +****** xref:protocols/dhcp/policy_device_options.adoc[Device, class and group options] +****** xref:protocols/dhcp/policy_ippool_access.adoc[IP pool access restriction] +**** xref:protocols/proxy/index.adoc[PROXY Protocol] +***** xref:protocols/proxy/enable_radsec.adoc[Enabling RadSec] +***** xref:protocols/proxy/radsec_client.adoc[Configuring a test RadSec client] +***** xref:protocols/proxy/radsec_with_haproxy.adoc[Proxying RadSec with HAproxy] +***** xref:protocols/proxy/radsec_with_traefik.adoc[Proxying RadSec with Traefik] +***** xref:protocols/proxy/enable_proxy_protocol.adoc[Enabling PROXY Protocol for RadSec] diff --git a/doc/antora/modules/howto/pages/index.adoc b/doc/antora/modules/howto/pages/index.adoc new file mode 100644 index 0000000..47a5146 --- /dev/null +++ b/doc/antora/modules/howto/pages/index.adoc @@ -0,0 +1,17 @@ += Howto Guides + +The documents in this section describe how to perform various common tasks with +FreeRADIUS. They also provide worked examples on using the various modules in +common deployment scenarions. + +If you have a topic you'd like to see included in the list of howtos, contact +the developers on the +link:http://lists.freeradius.org/mailman/listinfo/freeradius-users[User's +mailing list]. + +Some of the documents here started life as pages on +link:http://wiki.freeradius.org[wiki.freeradius.org]. If you've just been +through a particularly arduous service configuration and deployment, and would +like to help your fellow users, then please create a new how to on the wiki. +If it's popular enough, we'll include it in the official documentation for the +next release. diff --git a/doc/antora/modules/howto/pages/protocols/dhcp/enable.adoc b/doc/antora/modules/howto/pages/protocols/dhcp/enable.adoc new file mode 100644 index 0000000..2824bd0 --- /dev/null +++ b/doc/antora/modules/howto/pages/protocols/dhcp/enable.adoc @@ -0,0 +1,213 @@ +== Enabling the DHCP service + +A major difference between configuring FreeRADIUS as a DHCP server versus most +other DHCP software such as ISC DHCP is that other software typically uses a +single monolithic configuration file whereas FreeRADIUS has a collection of +configuration files. This reflects the modularity of FreeRADIUS; attempting to +put the entire configuration in a single file would result in a very difficult +to read configuration. + +The root of the FreeRADIUS configuration may be in a different location on the +filesystem depending on how FreeRADIUS has been installed. This directory will +be referred to as `<raddb>` below. The sample configuration files are well +commented describing what each configuration option does. + +FreeRADIUS compiled from source will default to `/usr/local/etc/raddb`. +Pre-built packages will default to either `/etc/raddb` or +`/etc/freeradius`. + + +=== Enable the DHCP virtual server + +The FreeRADIUS configuration separates each network service that it provides +into "virtual servers". A number of sample virtual server definitions are +provided in `<raddb>/sites-available`, one of which is the sample +configuration for a DHCP service. + +Sites may be added to the working configuration by either creating a symlink to +them or copying them to `<conf>/sites-enabled` depending on how you wish to +manage future upgrades. + +[TIP] +==== +As with other package-managed configuration files, package upgrades will not +automatically replace files that you have edited but you will need to resolve +any local differences. Creating copies avoids the need to resolve conflicts +during a package upgrade. +==== + +Add the DHCP virtual server to the active configuration: + +[source,shell] +---- +cd <raddb>/sites-enabled +ln -s ../sites-available/dhcp . +---- + +or: + +[source,shell] +---- +cd <raddb>/sites-enabled +cp ../sites-available/dhcp . +---- + +The sample configuration has been set up in such a way that it is initially +safe. It will not actually take over live DHCP serving on the network when it +is simply enabled until it is configured to do so. Rather is set up for testing +prior to going live. + +The virtual server begins with a `listen` section. In this section your need to +modify the following configuration items: + +`ipaddr`:: The IP address to listen on. +`src_ipaddr`:: The source IP for unicast packets. +`port`:: The port to listen on. Setting this to `67` will make the DHCP service live on the network. +`interface`:: The network interface to listen on. +`broadcast`:: Allow broadcast packets. For most live systems this will need to be set to `yes`. + +Below the `listen` section, there are sections that define how to respond to +each of the DHCP packet types. Most installations will require that you review +the settings for `DHCP-Discover` and `DHCP-Request`. + +Their contents contain directives in the FreeRADIUS policy language, "unlang". +Many examples are provided which have been carefully described. + + +=== Enable SQL and IP pool modules + +FreeRADIUS has many modules to support different aspects of the functionality +required for the network protocols it can process. The two of most significance +for DHCP are `dhcp_sql` and `dhcp_sqlippool`. As with virtual servers, a +number of example module configurations are available in +`<raddb>/mods-available`. +These should be symlinked or copied into `<raddb>/mods-enabled` in order to +enable them. + + +==== Configure the `dhcp_sql` module + +Add the `dhcp_sql` module to the active configuration: + +[source,shell] +---- +cd <raddb>/mods-enabled +ln -s ../mods-available/dhcp_sql . +---- + +or: + +[source,shell] +---- +cd <raddb>/mods-enabled +cp ../mods-available/dhcp_sql . +---- + +The `dhcp_sql` module should be configured with the connection parameters for +whichever database is to be used. The key configuration items are: + +`dialect`:: Which SQL dialect is in use. +`driver`:: Which driver to use to access the database. For most databases this + is `rlm_sql_<dialect>`, however Microsoft SQL Server has a choice of + drivers. + +Then, there are configuration options that are unique to each database, +including connection details. For most databases these are: + +`server`:: The host name or IP address of the database server. +`port`:: The port to connect to the database server on. +`login`:: The user name used to connect to the database. +`password`:: The password for authenticating to the database. +`radius_db`:: The name of the database. + +[NOTE] +==== +SQLite does not use these connection options, rather the `filename` +option within the `sqlite` section is used to determine where the database +will be stored. +==== + + +==== Configure the `dhcp_sqlippool` module + +Add the `dhcp_sqlippool` module to the active configuration: + +[source,shell] +---- +cd <raddb>/mods-enabled +ln -s ../mods-available/dhcp_sqlippool . +---- + +or + +[source,shell] +---- +cd <raddb>/mods-enabled +cp ../mods-available/dhcp_sqlippool . +---- + +The `dhcp_sqlippool` module must be configured. The key configuration +items are: + +`dialect`:: Set this to the same SQL dialect as in the `sql` module. +`offer_duration`:: How long an IP is offered to the client in a DHCP OFFER. +`lease_duration`:: How long an IP is leased to the client in a DHCP ACK. + + +=== Provision the database + +You should provision your database by creating a user for FreeRADIUS (matching +the configuration that you have previously provided) and then loading the +schema. The procedure for doing this will vary according to the database +server. + +The schema, stored procedure definition and any additional setup scripts for +your database are in `<raddb>/mods-config/sql/ippool-dhcp/{dialect}/`. + +=== Test FreeRADIUS startup + +Once you have provisioned your schema, created a user account and granted +access to the user, you should be able to start FreeRADIUS. + +If FreeRADIUS has been configured correctly then the output of `ss` will +contain a line showing that FreeRADIUS is listening for DHCP packets on the +designated interface on port 67: + +.Example of FreeRADIUS listening on `<interface>` for DHCP packets +================================================================== + # ss -lunp + Netid Recv-Q Send-Q Local Address:Port ... + udp 0 0 0.0.0.0%<interface>:67 ... users:(("radiusd",...)) +================================================================== + +Note that if the database is inaccessible then FreeRADIUS will normally refuse +to start. + +The FreeRADIUS wiki contains extensive information about debugging FreeRADIUS +startup issues that we do not repeat in any detail here. + +Essentially, stop your init system from repeatedly trying to launch FreeRADIUS: + +[source,shell] +---- +service radiusd stop +---- + +Then start FreeRADIUS manually in debug mode: + +[source,shell] +---- +radiusd -X +---- + +Carefully read the output since this will tell you why FreeRADIUS was unable to +start. + +Once you have fixed the issue start FreeRADIUS as normal: + +[source,shell] +---- +service radiusd start +---- + +Now xref:protocols/dhcp/test.adoc[test the DHCP service] to ensure that it is responding to requests. diff --git a/doc/antora/modules/howto/pages/protocols/dhcp/index.adoc b/doc/antora/modules/howto/pages/protocols/dhcp/index.adoc new file mode 100644 index 0000000..fde2202 --- /dev/null +++ b/doc/antora/modules/howto/pages/protocols/dhcp/index.adoc @@ -0,0 +1,35 @@ += FreeRADIUS DHCP server + +This guide describes how FreeRADIUS can be used in place of ISC DHCP or ISC Kea +to provide a significantly more performant and, above all, more flexible DHCP +server. + +This guide provides a suggested configuration that should be somewhat familiar +to anyone who has previously implemented DHCP using the most frequently used +features of other DHCP server software. + +The modular design of FreeRADIUS means that there is no one "right" way to +implement the DHCP service. FreeRADIUS allows you to put together a "mix and +match" approach. + +For example you can manage the leases in an SQL database. You might then hard +code certain DHCP reply parameters within configuration and then look up +additional parameters using a datastore such as: + + * a local file such as a structured text file or an SQLite database + * an organisational LDAP directory + * an SQL or "no SQL" database + * a remote endpoint such as a RESTful HTTP API + +The policy language and modular configuration of FreeRADIUS is sufficiently +powerful and that almost any aspect of the server's behaviour can be customised +to implement even the most sophisticated DHCP configurations. + +== Sections in this guide + +This guide is organised into four parts that should be read in order: + +1. xref:protocols/dhcp/prepare.adoc[Preparation] +2. xref:protocols/dhcp/enable.adoc[Enabling the DHCP service] +3. xref:protocols/dhcp/test.adoc[Testing the DHCP service] +4. xref:protocols/dhcp/policy.adoc[Defining the DHCP policy] diff --git a/doc/antora/modules/howto/pages/protocols/dhcp/policy.adoc b/doc/antora/modules/howto/pages/protocols/dhcp/policy.adoc new file mode 100644 index 0000000..d8f1bcb --- /dev/null +++ b/doc/antora/modules/howto/pages/protocols/dhcp/policy.adoc @@ -0,0 +1,14 @@ +== Defining the DHCP policy + +Now that FreeRADIUS is successfully running as a DHCP server it is necessary to +configure a DHCP policy so that it returns correctly formed responses to the DHCP +requests that it receives. + +This involves a number of steps: + + * xref:protocols/dhcp/policy_ippool_creation.adoc[Defining the IP address pools.] + * xref:protocols/dhcp/policy_common_options.adoc[Defining the options that are common to all replies.] + * xref:protocols/dhcp/policy_network_options.adoc[Defining the options for the network from which the request originates and ensuring that IP addresses are allocated from the correct pool.] + * xref:protocols/dhcp/policy_subnet_options.adoc[Defining the options for the subnet to which this issued IP address belongs.] + * xref:protocols/dhcp/policy_device_options.adoc[Defining the device, class and group based options specific to the device.] + * xref:protocols/dhcp/policy_ippool_access.adoc[Using device properties to restrict access to certain pools.] diff --git a/doc/antora/modules/howto/pages/protocols/dhcp/policy_common_options.adoc b/doc/antora/modules/howto/pages/protocols/dhcp/policy_common_options.adoc new file mode 100644 index 0000000..949868d --- /dev/null +++ b/doc/antora/modules/howto/pages/protocols/dhcp/policy_common_options.adoc @@ -0,0 +1,80 @@ +== Configure common reply options + +FreeRADIUS includes a powerful xref:unlang/index.adoc[policy language] called +"unlang". + +Statements in unlang may be used to call further policies, update attribute +lists and invoke modules. There are also control flow statements (if, +switch, etc.) typical of most imperative languages. + +FreeRADIUS has a number attribute lists that it maintains as it processes +packets within the virtual server sections. Most relevant to DHCP are +`request`, `control` and `reply`. + +The DHCP options from the current request packet are provided in the +`request` list. This includes fixed DHCP parameters such as +`DHCP-Client-Hardware-Address`, optional parameters such as +`DHCP-Requested-IP-Address`, and parameters synthesised by FreeRADIUS such as +`DHCP-Message-Type` and `DHCP-Network-Subnet`. + +DHCP options can be set by updating their value in the `reply` list. This +forms the basis of the packet returned to the client. + +In the default DHCP server configuration, a "policy" (akin to a subroutine) is +used to set common options for reply packets. The policy is found in +`<raddb>/policy.d/dhcp`. + +Look at the contents of the `dhcp_common` section and set any global options +applicable to all clients in this policy. + +[source,unlang] +---- +dhcp_common { + update reply { + &DHCP-Domain-Name-Server := 8.8.8.8 + &DHCP-Domain-Name-Server += 8.8.4.4 + &DHCP-Subnet-Mask := 255.255.255.0 + &DHCP-Router-Address := 192.0.2.1 + ... + } +} +---- + +Note, FreeRADIUS has four main operators for assigning values to attributes: + +`=`:: Add the attribute to the list, if and only if an attribute of the same + name is not already present in that list. +`:=`:: Add the attribute to the list. If any attribute of the same name is + already present in that list it is replaced with the new one. +`+=`:: Add the attribute to the tail of the list, even if attributes of the + same name are already present in the list. +`^=`:: Add the attribute to the head of the list, even if attributes of the + same name are already present in the list. + +These operators allow for attributes to be set to default values and then +overwritten, e.g. setting a default lease time, but then overwriting it for +a particular group of clients. + +Attributes in the `control` list are not returned in the DHCP reply packets +but instead govern aspects of server's behaviour. + +To use an SQL backend for either static or dynamic IP allocation, un-comment +the block: + +[source,unlang] +---- +update control { + &Pool-Name := "local" +} +dhcp_sqlippool +---- + +The `Pool-Name` control attribute is used in looking up addresses in the +database. The line containing `dhcp_sqlippool` is a call to invoke an +instance of a module with that name. This module is responsible for assigning a +free IP address into the `DHCP-Your-IP-Address` reply attribute from the pool +identified by `Pool-Name`. + +Here `Pool-Name` is being set to a constant value (`local`) indicating +that a single pool is to be used. If you have multiple pools, then replace this +`update` block with logic to map clients to the correct pool, as described below. diff --git a/doc/antora/modules/howto/pages/protocols/dhcp/policy_device_options.adoc b/doc/antora/modules/howto/pages/protocols/dhcp/policy_device_options.adoc new file mode 100644 index 0000000..05845ea --- /dev/null +++ b/doc/antora/modules/howto/pages/protocols/dhcp/policy_device_options.adoc @@ -0,0 +1,310 @@ +== Configure "device", "class" and "group" options + +Beyond the global, network and subnet options already described, most sites +will have a number of group or class based options, and have a requirement for +setting reply parameters against individual devices. + +In general, FreeRADIUS does not differentiate between "classes" (memberships +defined by some attribute of the DHCP request) and "groups" (memberships +defined by some manually aggregation related devices, typically based on lists +of MAC address). + +The sample DHCP configuration provided with FreeRADIUS makes use of an internal +attribute `DHCP-Group-Name` to support the setting of different options for +different groups of devices. + +In general the groups to which a device belongs is determined during the +processing of a request and these are added as instances of the +`DHCP-Group-Name` attribute. This may be by performing a test on one or more +request parameters (akin to a "class"), hash-based lookup of up all of part of +an attribute in a local list (akin to a "subclass"), or doing the same using a +remote datastore (SQL, LDAP, REST API, etc). + +FreeRADIUS can then iterate over `DHCP-Group-Name` to set group-specific +options. + +We describe some of these options in more detail. + +=== Directly in Policy + +Simple class options can be written directly into policy. This is most +suited to those options that rarely change and are based on attributes in the +request such as the `User-Class`. + +Consider the ISC DHCP configuration snippet: + +[source,iscdhcp] +---- +filename "undionly.kpxe"; +class "pxeclient" { + match option substring(user-class,0,4); +} +subclass "pxeclient" "iPXE" { + filename "http://my.web.server/boot_script.php"; +} +---- + +Or the equivalent Kea configuration: + +[source,isckea] +---- +"Dhcp4": { + "option-data": [ + { "name": "boot-file-name", "data": "undionly.kpxe" } + ], + "client-classes": [ + { + "name": "pxeclient", + "test": "substring(option[77],0,4) == 'iPXE'", + "option-data": [ + { + "name": "boot-file-name", + "data": "http://my.web.server/boot_script.php" + } + ] + } + ] + ... +} +---- + +These define the "filename" DHCP option differently based on whether or not the +supplied "user-class" option begins with "iPXE". + +FreeRADIUS provides multiple ways for this to be configured. + +For example, the following "unlang" policy implements the class options defined +above: + +[source,unlang] +---- +if (&DHCP-User-Class && "%{substring:&DHCP-User-Class 0 4}" == "iPXE") { + update reply { + &DHCP-Boot-Filename := "http://my.web.server/boot_script.php" + } +} else { + update reply { + &DHCP-Boot-Filename := "undionly.kpxe" + } +} +---- + +Policy-based configuration of DHCP options is also useful for complex matching. +For example, the following Unlang sets the DHCP-Boot-Filename parameter based +on the request's DHCP-Client-Identifier using regular expression captures, +provided that it matches the given format: + +[source,unlang] +---- +if (&DHCP-Client-Identifier && \ + "%{string:DHCP-Client-Identifier}" =~ /^RAS([0-9])-site([A-Z])$/) { + update reply { + &DHCP-Boot-Filename := "rasboot-%{1}-%{2}.kpxe" + } +} +---- + +=== In Text Files + +The `files` module that has already been described for global, network and +subnet options can also be used to apply options to groups of clients. + +Firstly we must defined a mapping from a set of clients clients to their +respective groups. One option for this is to use the `passwd` module, for +which a sample configuration is included. + +Firstly symlink or copy the module configuration +`<raddb>/mods-available/dhcp_passwd` into `<raddb>/mods-enabled/`. The +suggested configuration expects the group membership file to be in +`<raddb>/mods-config/files/dhcp_groups` and take the form of: + +[source,config] +---- +<group1 name>|<hardware address>,<hardware address>,<hardware address> +<group2 name>|<hardware address>,<hardware address> +---- + +i.e. one line for each group starting with the group name followed by a pipe +character and then a comma-separated list of hardware addresses. + +The `allow_multiple_keys` option allows for a host to be a member of +more than one group. + +Sample configuration for looking up group options is contained in +`<raddb>/policy.d/dhcp` in the `dhcp_group_options` policy and in +`<raddb>/mods-available/dhcp_files` as the `dhcp_set_group_options` instance. + +The same data file `<raddb>/mods-config/files/dhcp` is used to lookup +group options as was used for global and network options. In this instance, +add entries with the group name as the key such as: + +[source,config] +---- +group1 + DHCP-Log-Server := 10.10.0.100, + DHCP-LPR-Server := 10.10.0.200 + +group2 + DHCP-LPR-Server := 192.168.20.200 +---- + +=== In the SQL Database + +Policy and files are both read during startup and editing them while +FreeRADIUS is running will not result in any changes in behaviour. If +you require regular changes to DHCP options, then storing them in +an SQL database provides greater flexibility since the queries will be run in +response to each DHCP packet rather than requiring the server to be restarted. + +DHCP reply options for devices (including network-specific options) can be +fetched from SQL using an arbitrary lookup key. This can be performed multiple +times as necessary using different contexts, for example to first set +subnet-specific options and then to set group-specific options. + +The default schema contains three tables to support this: + +"dhcpreply" contains reply options for a given identifier (e.g. MAC Address): + +.dhcpreply table +|=== +|Identifier |Attribute |Op |Value |Context + +|`02:01:aa:bb:cc:dd` |`DHCP-Log-Server` |`:=` |`192.0.2.10` |`by-mac` +|`02:01:aa:bb:cc:dd` |`DHCP-LPR-Server` |`:=` |`192.0.2.11` |`by-mac` +|`02:01:aa:bb:cc:dd` |`Fall-Through` |`:=` |`Yes` |`by-mac` +|=== + +"dhcpgroup" maps identifiers to a group of options that can be shared: + +.dhcpgroup table +|=== +|Identifier |GroupName |Priority |Context + +|`02:01:aa:bb:cc:dd` |`salesdept` |`10` |`by-mac` +|=== + +"dhcpgroupreply" contains reply options for each group: + +.dhcpgroupreply table +|=== +|GroupName |Attribute |Op |Value |Context + +|`salesdept` |`DHCP-NTP-Servers` |`:=` |`192.0.2.20` |`by-mac` +|`salesdept` |`DHCP-Log-Server` |`+=` |`192.0.2.21` |`by-mac` +|`salesdept` |`DHCP-LPR-Server` |`^=` |`192.0.2.22` |`by-mac` +|=== + +Within the context of assigning options directly to devices, as well as to +manually-curated groups of devices keyed by their MAC address: + + - Place device-specific options in the "dhcpreply" table. + - Add `Fall-Through := Yes` to the options in the "dhcpreply" table in order + to trigger group lookups, which are disabled by default. + - Place entries in the "dhcpgroup" `identifier = <MAC-Address>, groupname = <group>, priority = + <priority>` in the "dhcpgroup" table to map a device to its groups by + priority. + - Place the grouped options in the "dhcpgroupreply" table. + - For each of the above, set `Context` to something by which the option + lookup is referred to in the policy, for example `Context = 'by-mac'`. + +For the above example you would add the following to the DHCP virtual server to +perform reply option lookup using the device's MAC address against the `by-mac` +context: + +[source,unlang] +---- +update control { + &DHCP-SQL-Option-Context := "by-mac" + &DHCP-SQL-Option-Identifier := &request:DHCP-Client-Hardware-Address +} +dhcp_sql.authorize +---- + +In the above, the DHCP reply options would be assigned to a device with MAC +address 02:01:aa:bb:cc:dd as follows: + + - Firstly, the `DHCP-Log-Server` option would be set to `192.0.2.10` and the + `DHCP-LPR-Server` option set to `192.0.2.11`. + - `Fall-Through` is set, so the group mapping is then queried which + determines that the device belongs to a single `salesdept` group. + - Finally, the options for the `salesdept` group are now merged, setting a + `DHCP-NTP-Servers` option to `192.0.2.20`, appending an additional + `DHCP-Log-Server` option set to `192.0.2.21`, and prepending an additional + `DHCP-LPR-Server` option set to `192.0.2.22`. + +If instead you wanted to perform a "subclass" lookup based on the first three +octets of the device's MAC address then with tables containing the following +sample data you could invoke an SQL lookup as shown: + +."dhcpreply" table: +|=== +|Identifier |Attribute |Op |Value |Context + +|`000393` |`Fall-Through` |`:=` |`Yes` |`class-vendor` +|`000a27` |`Fall-Through` |`:=` |`Yes` |`class-vendor` +|`f40304` |`Fall-Through` |`:=` |`Yes` |`class-vendor` +|=== + +."dhcpgroup" table: +|=== +|Identifier |GroupName |Priority |Context + +|`000393` |`apple` |`10` |`class-vendor` +|`000a27` |`apple` |`10` |`class-vendor` +|`f40304` |`google` |`10` |`class-vendor` +|=== + +."dhcpgroupreply" table: +|=== +|GroupName |Attribute |Op |Value |Context + +|`apple` |`DHCP-Boot-Filename` |`:=` |`apple.efi` |`class-vendor` +|`google` |`DHCP-Boot-Filename` |`:=` |`google.efi` |`class-vendor` +|=== + + +[source,unlang] +---- +update control { + &DHCP-SQL-Option-Context := "class-vendor" + &DHCP-SQL-Option-Identifier := \ + "%{substring:%{hex:&DHCP-Client-Hardware-Address} 0 6}" +} +dhcp_sql.authorize +---- + +The file `policy.d/dhcp` contains a policy named `dhcp_policy_sql` which +provides further worked examples for different types of option lookups. + +=== Testing "device", "class" and "group" options + +You should now test that any device-related options that you have configured +using the various methods available are applied successfully by generating +packets containing those parameters based upon which the reply options are set. + +For example, to test the iPXE user class example above you might want to +generate a request as follows: + +[source,shell] +---- +cat <<EOF > dhcp-packet-ipxe-boot.txt +DHCP-Message-Type := DHCP-Discover +DHCP-Client-Hardware-Address := 02:01:aa:bb:cc:dd +DHCP-User-Class := "iPXE-class-abc" +EOF +---- + +To which you would expect to see a response such as: + +.Example output from dhcpclient +=============================== + dhcpclient: ... + ---------------------------------------------------------------------- + Waiting for DHCP replies for: 5.000000 + ---------------------------------------------------------------------- + ... + DHCP-Message-Type = DHCP-Offer + DHCP-Your-IP-Address = 1.2.3.4 + DHCP-Boot-Filename := "http://my.web.server/boot_script.php" + ... +=============================== diff --git a/doc/antora/modules/howto/pages/protocols/dhcp/policy_ippool_access.adoc b/doc/antora/modules/howto/pages/protocols/dhcp/policy_ippool_access.adoc new file mode 100644 index 0000000..40b8e30 --- /dev/null +++ b/doc/antora/modules/howto/pages/protocols/dhcp/policy_ippool_access.adoc @@ -0,0 +1,54 @@ +== Configure access restrictions for pools + +We can combine what we have learned in the preceeding sections to provide pools +whose access is restricted in some way, for example to a particular class. + +Consider the ISC DHCP configuration snippet: + +[source,iscdhcp] +---- +subnet 10.99.99.0 netmask 255.255.255.0 { + pool { + range 10.99.99.200 10.99.99.250; + allow members of "printers"; + } + option routers 10.99.99.1; +} +---- + +Or the equivalent Kea configuration: + +[source,isckea] +---- +"Dhcp4": { + "subnet4": [{ + "subnet": "10.99.99.0/24", + "pools": [ + { + "pool": "10.99.99.200 - 10.99.99.250", + "client-class": "printers" + } + ], + "option-data": [ + { "name": "routers", "data": "10.10.0.1" } + ] + }], + ... +} +---- + +These define a subnet containing a single pool that is restricted to members of +the "printers" class. (The definition for this class is omitted.) + +In FreeRADIUS, to filter access to this pool entries such as the following +should included in the `<raddb>/mods-config/files/dhcp` configuration file: + +[source,config] +---- +network DHCP-Network-Subnet < 10.99.99.0/24, \ + DHCP-Group-Name == "printers", Pool-Name := "printers-pool" + DHCP-Router-Address := 10.99.99.1 +---- + +Note that any number of additional filters can be added to the initial "check" +line to restrict matches to the network block. diff --git a/doc/antora/modules/howto/pages/protocols/dhcp/policy_ippool_creation.adoc b/doc/antora/modules/howto/pages/protocols/dhcp/policy_ippool_creation.adoc new file mode 100644 index 0000000..e976873 --- /dev/null +++ b/doc/antora/modules/howto/pages/protocols/dhcp/policy_ippool_creation.adoc @@ -0,0 +1,112 @@ +=== Determine the IP pool plan + +Except for cases where all IP allocation is performed using a mapping from the +device MAC address to a fixed IP address, the DHCP configuration will involve +the use of one or more IP address pools. + +FreeRADIUS stores all the IP addresses in its pools in whichever database has +been chosen. An instance of the `sqlippools` module is used to manage all pools +within a single table (normally `dhcpippool`). Each row of this table +corresponds to an IP address that is a member of some pool. The pools are +distinguished by name, so the table has a column (`pool_name`) that denotes +this. + +Each pool in this table should be composed of a set of equally valid IP +addresses for the devices that are designated to be members of the pool. + +Firstly, consider the network locations to which distinct ranges of IP +addresses must be allocated and provisionally assign a pool to each. + +Next, consider that many networks support multiple co-existing subnets without +VLAN separation. We will call this a "shared-network" to use the original ISC +DHCP parlance. In Microsoft DHCP contexts this is often referred to as a +"multinet". + +Often in a shared-network the policy has no regard for which of the network's +devices is allocated to which subnet. In this case we must create a single, +combined pool containing all of the IP addresses from each subnet in that +network. Since all addresses in a pool are treated equally this will mean that +any IP address may be allocated to a device that is making a DHCP request from +that network. The appropriate DHCP parameters for the subnet to which the IP +address belongs is determined after allocation. + +There are sometimes shared-networks (or even single subnets) for which IP +addresses belonging to any subnet may be technically suitable for any device, +however some local policy wants to assigning them to a particular subnet, for +example to provide loose segregation between classes of device. In this case we +define multiple pools, one for each range of IP addresses whose devices needs to +be differentiated. + +The choice of pool is ordinarily determined based on the network from which the +request originates using a mapping from Layer 2 networks to the pool name +provided by the user. The indicator for the originating network can be +overridden when this alone is insufficient to implement the required pool +selection policy such as when you need to differentiate the pool's users with +more granularity that their Layer 2 network, such as by considering device +attributes ("class" membership in ISC parlance) or Option 82 circuit data. + + +=== Populate the IP Pools + +By this stage you should have derived a list of pools, the IP address ranges +contained therein, and the means of selecting the pool to use based on the +originating network and/or some additional criteria from the request. + +A helper Perl script is provided with FreeRADIUS that can be used to populate +the pools provide that you are using the default schema. + +[source,shell] +---- +rlm_sqlippool_tool -p <pool_name> -s <range_start> -e <range_end> \ + -t <table_name> (-d <sql_dialect> | -f <raddb_dir> [ -i <instance> ]) \ + [ -c <capacity> ] [ -x <existing_ips_file> ] +---- + +If, for example, you had a range configured in ISC DHCP as: + +[source,iscdhcp] +---- +range 10.0.0.5 10.0.0.199 +---- + +and you are using PostgreSQL as your database, and you wish to refer to this pool +using the name `local`, this could be prepared with: + +[source,shell] +---- +rlm_sqlippool_tool -p local -s 10.0.0.5 -e 10.0.0.199 -t dhcpippool -d postgresql +---- + +If the SQL module of FreeRADIUS is already configured then this can +be referenced so that the tool is able to use the configured connection +parameters to connect to the database and populate the pool: + +[source,shell] +---- +rlm_sqlippool_tool -p local -s 10.0.0.5 -e 10.0.0.199 -t dhcpippool -f /etc/raddb +---- + +For installations that require multiple pools, `rlm_sqlippool_tool` can +be called referencing a YAML file defining the pools. Comments at the +head of `rlm_sqlippool_tool` explain the options in more detail. + +If static leases are required then these should be set up in the database +such that the MAC address of the client should be set as the `pool_key` +against the corresponding address and the `status` column of the row +representing the address set to `static`. A helper perl script, +`rlm_iscfixed2ippool` can be used to read an ISC DHCP config file and produce +SQL to perform these changes or directly update the database: + +[source,shell] +---- +rlm_iscfixed2ippool -c <dhcpd.<raddb> -t <table_name> -k <mac|id> \ + (-d <sql_dialect> | -f <raddb_dir> [-i <instance>]) +---- + +For example, to read /etc/dhcp/dhcpd.conf and populate the configured +FreeRADIUS database, using the mac as the identifier: + +[source,shell] +---- +rlm_iscfixed2ippool -c /etc/dhcp/dhcpd.conf -t dhcpippool -k mac -f /usr/local/etc/raddb +---- diff --git a/doc/antora/modules/howto/pages/protocols/dhcp/policy_network_options.adoc b/doc/antora/modules/howto/pages/protocols/dhcp/policy_network_options.adoc new file mode 100644 index 0000000..e2657a8 --- /dev/null +++ b/doc/antora/modules/howto/pages/protocols/dhcp/policy_network_options.adoc @@ -0,0 +1,237 @@ +== Configure network-specific options and IP pool selection + +In an environment where multiple networks (often VLANs) are in use, it is +necessary to identify which network a client belongs to in order to assign an +address from the correct pool. + +Consider the ISC DHCP configuration snippet: + +[source,iscdhcp] +---- +option domain-name "example.org"; + +subnet 10.10.0.0 netmask 255.255.0.0 { + range 10.10.1.10 10.10.10.254; + range 10.10.100.10 10.10.110.254; + option routers 10.10.0.1; + option domain-name-servers 10.10.0.2, 10.10.0.3; + default-lease-time 7200; +} +---- + +Or the equivalent Kea configuration: + +[source,isckea] +---- +"Dhcp4": { + "option-data": [ + { "name": "domain-name", "data": "example.org" } + ], + "subnet4": [{ + "subnet": "10.10.0.0/16", + "pools": [ { "pool": "10.10.1.10 - 10.10.10.254" }, + { "pool": "10.10.100.10 - 10.10.110.254" } + ], + "option-data": [ + { "name": "routers", "data": "10.10.0.1" }, + { "name": "domain-name-servers", "data": "10.10.0.2, 10.10.0.3" } + ], + "valid-lifetime": 7200 + }], + ... +} +---- + +These define a network consisting of a single subnet 10.10.0.0/16 containing two +IP address pools 10.10.1.10 - 10.10.10.254 and 10.10.100.10 - 10.10.110.254. +Requests that are determined to have originated from this network (e.g. because +their `giaddr` belongs within the subnet) will be assigned the specified DHCP +parameters and allocated an address from one of its ranges. + +To provide equivalent functionality, FreeRADIUS must identify the correct DHCP +reply parameters as well as the name of the pool to be used for IP address +assignment, based on the originating network of the request. + +The definition for this pool (the addresses contained within it, corresponding +to the `range` statement in ISC DHCP and Kea) is specified entirely in the +database: It is precisely the rows in the `dhcpippool` table with a particular +`pool_name`. + +[TIP] +==== +As described previously, in FreeRADIUS a pool is a set of IP addresses that are +equally valid with respect to the network policy; therefore, unlike ISC DHCP +and ISC Kea, FreeRADIUS does not differentiate between the two `range`s. +Instead we should have previously populated a single pool containing all of the +IP addresses from both ranges. +==== + +FreeRADIUS derives a request attribute called `DHCP-Network-Subnet` which +honours the standard DHCP process for designating the choice of network, in +order of preference: + + 1. Link Selection Suboption of Option 82 + 2. IPv4 Subnet Selection Option + 3. Gateway IP Address ("giaddr") + 4. Client IP Address ("ciaddr", only set for unicast packets) + +If `DHCP-Network-Subnet` contains an IP address then this should be used as +the basis of choosing a network. When there is no address in this attribute it +can be assumed that the packet has been received from a client on the local +LAN. + +The `files` module in FreeRADIUS provides a simple method to map +`DHCP-Network-Subnet` to the corresponding pool based on its network +membership, setting the appropriate options to return to clients. It can also +set the global options. + +[TIP] +==== +In the case where an instance of the `files` module is used to get global +default parameters, the `dhcp_common` policy becomes redundant so the +statement calling the policy (by name) can be commented out in +`<raddb>/sites-enabled/dhcp`. +==== + +To use the provided example `files` module instance for DHCP, symlink or copy +`<raddb>/mods-available/dhcp_files` into `<raddb>/mods-enabled/` and then +uncomment the calls to `dhcp_network` in `<raddb>/sites-enabled/dhcp`. + +A template configuration file `<raddb>/mods-config/files/dhcp` is also +provided which should be adapted to suit your network topology. + +For the configuration above you may deduce the following configuration, which +has been extended to include an initial default section for requests originating +from directly-connected clients on the local LAN (192.168.20/24): + +[source,config] +---- +network Pool-Name := "local" + DHCP-Domain-Name := "example.org", + DHCP-Subnet-Mask := 255.255.255.0, + DHCP-Router-Address := 192.168.20.1, + DHCP-Domain-Name-Server := 192.168.20.2, + Fall-Through := yes + +network DHCP-Network-Subnet < 10.10.0.0/16, Pool-Name := "remote" + DHCP-Subnet-Mask := 255.0.0.0, + DHCP-Router-Address := 10.10.0.1, + DHCP-Domain-Name-Server := 10.10.0.2, + DHCP-Domain-Name-Server += 10.10.0.3, + DHCP-IP-Address-Lease-Time := 7200 +---- + +Each block in the file starts with a line beginning with the key to be matched. +In this case the keyword of `network` (defined earlier in `dhcp_networks` +configuration) is used for each block, so each of the above blocks is a +candidate during the search. + +There may be further filtering of the candidates in the form of `<Attribute> +<op> <Value>`. In the case of the second block we match the +`DHCP-Network-Subnet` to an enclosing subnet with +`DHCP-Network-Subnet < <subnet>`. Additional filters could be added as +required, comma separated. + +Following the filters on the first line, attributes in the `control` list can +be set using the syntax of `<Attribute> := <Value>`. In this example this is +used to specify the `Pool-Name` for choosing the appropriate IP pool to +allocate an address from. + +Subsequent indented lines are attribute assignments for values in the `reply` +list. Note that, apart from the last line, they are all terminated with a +comma. + +The special option `Fall-Through` determines whether, following a match, +other records are checked for a match. All lookups will match the entry +with a key of `network` and no further filtering, so `Fall-Through` +is set on that record in order that the other records will be tested +to find subnet matches. + +=== Example packet processing + +For our example, we consider a request arriving from a DHCP relay within +10.10.0.0/16. In the absence of any specific DHCP subnet selection options in +the request, the `DHCP-Network-Subnet` attribute is calculated to be the +relay's IP address, say 10.10.0.1. + +The request is matched against the first block, setting an initial pool name to +"local", domain name to "example.org" and setting some additional global +default parameters. By virtue of `Fall-Through` being set, the next block is +considered. + +Since the network identifier is within the specified subnet (i.e. `10.10.0.1 < +10.10.0.0/16`) this second block is matched. This block overrides the pool name +setting it to "remote", overrides some other global defaults and sets the lease +time to 7200 seconds. `Fall-Through` is not set, so we are now done with +deriving the pool name and network options. + +When the `dhcp_sqlippool` module is called during DHCP DISCOVER processing (in +`<raddb>/sites-enabled/dhcp`) the `remote` pool will be used for IP address +allocation. + +The assigned IP address and network parameters will subsequently be returned in +the DHCP reply. + +=== Testing the pool operation and network-specific options + +Before proceeding further, you should test the operation of the IP pools and +ensure that any network-specific reply attributes that you have configured are +correctly set in replies. + +For example, if you have a single, flat pool you should test using sample +packets for devices with different MAC addresses and/or Client Identifiers. + +[source,shell] +---- +cat <<EOF > dhcp-packet-1.txt +DHCP-Message-Type := DHCP-Discover +DHCP-Client-Hardware-Address := 02:01:11:11:11:11 +DHCP-Client-Identifier := device1 +EOF +---- + +[source,shell] +---- +cat <<EOF > dhcp-packet-2.txt +DHCP-Message-Type := DHCP-Discover +DHCP-Client-Hardware-Address := 02:01:22:22:22:22 +DHCP-Client-Identifier := device2 +EOF +---- + +Generate these packets as show previously using the dhcpclient tool and look +for `DHCP-Your-IP-Address` in the DHCP responses to determine the IP address +that has been offered. + +Ensure that the DHCP Offer responses contain unique IP addresses. Ensure that +when these requests are resent within the lifetime of the initial offer that +the reponses to the subsequent replies contain the original IP address that was +in the initial offer to the device. + +Additionally, ensure that the DHCP Offers contain any network-specific +parameters that you have specified. + +In the case that the policy contains multiple IP pools and network definitions +for clients belonging to different Layer 2 networks (or indeed belonging to the +same network but segregated according to some local policy) you should ensure +that the devices are being mapped to the correct definition. + +For a typical policy that selects the IP pool and network options based on the +originating network for the DHCP packet, explicitly specifying a network by +including a `DHCP-Subnet-Selection-Option` parameter may avoid the need to test +from a host within each individual network: + +[source,shell] +---- +cat <<EOF > dhcp-packet-network-10.10.10.0.txt +DHCP-Message-Type := DHCP-Discover +DHCP-Client-Hardware-Address := 02:01:aa:bb:cc:dd +DHCP-Client-Identifier := abc123 +DHCP-Subnet-Selection-Option := 10.10.10.0 +EOF +---- + +For policies where the IP pool and network option selection is based on some +custom criteria it is necessary to include different variations for the +parameters on which the policy makes the decision. The testing example for the +class-specific options later in this document provides such an example. diff --git a/doc/antora/modules/howto/pages/protocols/dhcp/policy_subnet_options.adoc b/doc/antora/modules/howto/pages/protocols/dhcp/policy_subnet_options.adoc new file mode 100644 index 0000000..1980e89 --- /dev/null +++ b/doc/antora/modules/howto/pages/protocols/dhcp/policy_subnet_options.adoc @@ -0,0 +1,184 @@ +== Configure subnet-specific options for shared networks + +In the case that shared-networks are in use, with the pool containing +equally-valid IP addresses from multiple subnets, it is necessary to set the +subnet-specific parameters such as `DHCP-Router-Address`, `DHCP-Subnet-Mask` +and `DHCP-Broadcast-Address` based on the IP address that has been allocated. + +Consider the ISC DHCP configuration snippet: + +[source,iscdhcp] +---- +option domain-name "example.org"; + +shared-network bigdept { + + option domain-name-servers 10.10.0.2, 10.10.0.3; + default-lease-time 7200; + + subnet 10.30.10.0 netmask 255.255.255.0 { + option routers 10.30.10.1; + } + subnet 10.30.20.0 netmask 255.255.255.0 { + option routers 10.30.20.1; + } + range 10.30.10.10 10.30.10.254; + range 10.30.20.10 10.30.20.254; + +} +---- + +Or the equivalent Kea configuration: + +[source,kea] +---- +"Dhcp4": { + "option-data": [ + { "name": "domain-name", "data": "example.org" } + ], + "shared-networks": [{ + "name": "bigdept", + "option-data": [ + { "name": "domain-name-servers", "data": "10.10.0.2, 10.10.0.3" } + ], + "valid-lifetime": 7200, + "subnet4": [{ + "subnet": "10.30.10.0/24", + "pools": [ { "pool": "10.30.10.10 - 10.30.10.254" } ], + "option-data": [ + { "name": "routers", "data": "10.30.10.1" } + ] + }], + "subnet4": [{ + "subnet": "10.30.20.0/24", + "pools": [ { "pool": "10.30.20.10 - 10.30.20.254" } ], + "option-data": [ + { "name": "routers", "data": "10.30.20.1" } + ] + }] + }], + ... +} +---- + +As with the network to pool lookup, an instance of the `files` modules can be +employed (this time after the allocation of an IP address) to set the correct +reply parameters based on the subnet membership of the assigned address. + +To do this, we can use this section of `<raddb>/mods-available/dhcp_files`: + +[source,config] +---- +files dhcp_subnets { + filename = ${modconfdir}/files/dhcp + key = "subnet" +} +---- + +Additionally, uncomment the `dhcp_subnets` policy in `<raddb>/policy.d/dhcp`. +This policy wraps the call to the `dhcp_subnets` files module with code that +"tightens" the `DHCP-Network-Subnet` attribute by setting it to the +just-allocated IP address. + +The relevant entries in the `<raddb>/mods-config/files/dhcp` configuration +file might then look something like this: + +[source,config] +---- +network + DHCP-Domain-Name := "example.org", + Fall-Through := yes + +network DHCP-Network-Subnet < 10.30.0.0/16, Pool-Name := "bigdept" + DHCP-Domain-Name-Server := 10.10.0.2, + DHCP-Domain-Name-Server += 10.10.0.3, + DHCP-IP-Address-Lease-Time := 7200 + +subnet DHCP-Network-Subnet < 10.30.10.0/24 + DHCP-Router-Address := 10.30.10.1 + +subnet DHCP-Network-Subnet < 10.30.20.0/24 + DHCP-Router-Address := 10.30.20.1 +---- + +=== Example packet processing + +For our example, we consider a request arriving from a DHCP relay within +10.30.10.0/24. In the absence of any specific DHCP subnet selection options in +the request, the `DHCP-Network-Subnet` attribute is calculated to be the +relay's IP address, say 10.30.10.1. + +The request is matched against the first "network" block, setting the domain +name to "example.org". By virtue of `Fall-Through` being set, the next "network" +block is considered. + +Since the network identifier is within the specified subnet (i.e. `10.30.10.1 < +10.30.0.0/16`) this second "network" block is matched. This block sets the pool +name to "bigdept", sets some network-specific DNS resolvers and sets the lease +time to 7200 seconds. `Fall-Through` is not set, so we are now done with +deriving the pool name and network options. + +When the `dhcp_sqlippool` module is called during DHCP DISCOVER processing (in +`<raddb>/sites-enabled/dhcp`) the `bigdept` pool will be used for IP address +allocation. + +After IP allocation the `dhcp_subnet` policy and files instance are called. +Before the subnet options are looked up the `DHCP-Network-Subnet` +attribute is tightened to match the assigned IP address, say 10.30.20.123. + +The request does not match the first subnet block since 10.30.20.123 is not +within 10.30.10.0/24. However, the request does match the second subnet block +since `10.30.20.123 < 10.30.20.0/24`. This block sets the default gateway +reply parameter. `Fall-Through` is not set, so we are now done with deriving +the pool name and network options. + +The assigned IP address, network and subnet parameters will subsequently be +returned in the DHCP reply. + +=== Testing the subnet-specific options + +If you have set any subnet-specific reply parameters then you should test these +before proceeding further. + +For example, in the case that you have a single, large pool spanning two IP +subnets you might want to test by repeatedly allocating addresses using sample +packets with different MAC addresses, each time checking to ensure that the +DHCP parameters correspond to the IP address that has been offered. + +.Example output from dhcpclient showing a response +================================================== + dhcpclient: ... + ... + ---------------------------------------------------------------------- + Waiting for DHCP replies for: 5.000000 + ---------------------------------------------------------------------- + ... + DHCP-Your-IP-Address = 10.0.10.50 + DHCP-Router-Address = 10.0.10.1 + DHCP-Broadcast-Address = 10.0.10.255 + DHCP-Subnet-Mask = 255.255.255.255 +================================================== + + +.Example output from dhcpclient showing a response +================================================== + dhcpclient: ... + ... + ---------------------------------------------------------------------- + Waiting for DHCP replies for: 5.000000 + ---------------------------------------------------------------------- + ... + DHCP-Your-IP-Address = 10.99.99.50 + DHCP-Router-Address = 10.99.99.1 + DHCP-Broadcast-Address = 10.99.99.255 + DHCP-Subnet-Mask = 255.255.255.255 +================================================== + + +[TIP] +==== +If the subnets are large then you might want to temporarily reduce their +size by setting the `status` field of the majority of the rows for each subnet +to "`disabled`" to cause offers to be made more readily with IP addresses in +different subnets. +==== diff --git a/doc/antora/modules/howto/pages/protocols/dhcp/prepare.adoc b/doc/antora/modules/howto/pages/protocols/dhcp/prepare.adoc new file mode 100644 index 0000000..aa43530 --- /dev/null +++ b/doc/antora/modules/howto/pages/protocols/dhcp/prepare.adoc @@ -0,0 +1,59 @@ +== Preparation + +It is necessary to consider the requirements for the installation in order to +devise an efficient and manageable set up. + +=== Understand the network topology + +When multiple networks (VLANs) are in use consideration must be given to how +the correct "pool" (IP address ranges) from which to allocate addresses is +identified. + +The policy for setting specific DHCP options (e.g. lease time, default gateway, +time server and vendor-specific parameters) for different groups of hosts, +based on their network or some device attributes either supplied in the DHCP +requests or determined by dynamic lookup, should be well defined and +understood. + +Other DHCP servers may implement implicit assumptions about the requirement of +your network topology and silently define particular behaviours, such as the +selection of IP address pool for a request based on a relay address. Some of +these behaviours must be specifed explicitly when using FreeRADIUS. + +=== Choose a database backend + +FreeRADIUS stores its leases in an SQL database, so one of the key decisions to +make is which database to use. + +FreeRADIUS supports: + + * SQLite + * PostgreSQL + * MySQL / MariaDB + * Microsoft SQL Server + * Oracle + +In most configurations the SQL database is likely to be the limiting component +that restricts the IP allocation throughput of the overall system. Each +database server has its own performance characteristics and unique approach to +features such as high-availability. + +The choice of database should be made carefully based on the performance and +high-availability requirements of the system, as well as any prior experience. + +[TIP] +==== +SQLite is an in-process database that uses the local file system, is simple to +configure and is suitable for smaller installations. However, users with larger +address pools or high availability requirements should choose one of the other +standalone databases based on criteria such as performance, features, +familiarity and your need for commercial support. +==== + +FreeRADIUS ships with a default database schema and set of queries for each +supported database. These are sufficient for most DHCP deployments but can be +reviewed and modified as required to suit a particular situation, for example +to customise the IP allocation policy such as by disabling address +"stickiness". + +Now xref:protocols/dhcp/enable.adoc[enable the DHCP service]. diff --git a/doc/antora/modules/howto/pages/protocols/dhcp/test.adoc b/doc/antora/modules/howto/pages/protocols/dhcp/test.adoc new file mode 100644 index 0000000..322de08 --- /dev/null +++ b/doc/antora/modules/howto/pages/protocols/dhcp/test.adoc @@ -0,0 +1,143 @@ +== Testing the DHCP service + +We can verify that FreeRADIUS is providing a DHCP service using the +`dhcpclient` tool that is included with the FreeRADIUS distribution. + +Temporarily configure FreeRADIUS to issue a single static IP address to all +clients by updating the `dhcp DHCP-Discover` section in the `dhcp` virtual +server to include the following: + +[source,unlang] +---- +update reply { + &DHCP-Your-IP-Address := 1.2.3.4 +} +---- + +Define a sample DHCP packet as follows: + +[source,shell] +---- +cat <<EOF > dhcp-packet.txt +DHCP-Message-Type := DHCP-Discover +DHCP-Client-Hardware-Address := 02:01:aa:bb:cc:dd +DHCP-Client-Identifier := abc123 +EOF +---- + +We can now generate this packet by invoking one of the following commands based +on the current circumstances... + +From the host that is running the FreeRADIUS DHCP server: + +[source,shell] +---- +dhcpclient -i lo 255.255.255.255 -f dhcp-packet.txt -x auto +---- + +From a different host with an interface (eth0) in the same broadcast domain +as the FreeRADIUS DHCP server: + +[source,shell] +---- +dhcpclient -i eth0 255.255.255.255 -f dhcp-packet.txt -x auto +---- + +If all of the DHCP broadcast traffic in other Layer 2 networks is converted to +unicast by DHCP relay agents then it is not necessary for FreeRADIUS to listen +on a broadcast address. In this case you can test DHCP using a unicast request: + +[source,shell] +---- +dhcpclient 192.0.2.10 -f dhcp-packet.txt -x auto +---- + +[NOTE] +==== +In order for the returned, unicast DHCP OFFER to be received it is necessary to +ensure that the `DHCP-Your-IP-Address` parameter set by FreeRADIUS matches an +address on the interface used by the dhcpclient tool to send the Discover +packet. +==== + +When one of the above commands is run, the tool with generate output such as +the following which shows that the packet was sent and that it is now waiting +for replies: + +.Example output from dhcpclient showing the request +=================================================== + dhcpclient: ... + ---------------------------------------------------------------------- + DHCP-Opcode = 0x01 + DHCP-Hardware-Type = 0x01 + DHCP-Hardware-Address-Length = 0x06 + DHCP-Hop-Count = 0x00 + DHCP-Transaction-Id = 0x5e0bbfab + DHCP-Number-of-Seconds = 0x0000 + DHCP-Flags = 0x0000 + DHCP-Client-IP-Address = 0x00000000 + DHCP-Your-IP-Address = 0x00000000 + DHCP-Server-IP-Address = 0x00000000 + DHCP-Gateway-IP-Address = 0x00000000 + ... + ---------------------------------------------------------------------- + Waiting for DHCP replies for: 5.000000 + ---------------------------------------------------------------------- +=================================================== + + +Each received DHCP response will generate output such as the following: + +.Example output from dhcpclient showing a response +================================================== + ... + ---------------------------------------------------------------------- + DHCP-Opcode = Server-Message + DHCP-Hardware-Type = Ethernet + DHCP-Hardware-Address-Length = 6 + DHCP-Hop-Count = 0 + DHCP-Transaction-Id = 1577828267 + DHCP-Number-of-Seconds = 0 + DHCP-Flags = 0 + DHCP-Client-IP-Address = 0.0.0.0 + DHCP-Your-IP-Address = 1.2.3.4 + DHCP-Server-IP-Address = 192.0.2.10 + DHCP-Gateway-IP-Address = 0.0.0.0 + DHCP-Client-Hardware-Address = 02:42:0a:00:00:0b + DHCP-Message-Type = DHCP-Offer + DHCP-Client-Identifier = 0x616263313233 + Waiting for additional DHCP replies for: 4.999429 + ... +================================================== + +Examine the DHCP response to ensure that it has the correct message type +(`DHCP-Offer`, in this case), contains the temporary IP address that you +configured earlier, i.e. `DHCP-Your-IP-Address = 1.2.3.4`, and any other +expected reply parameters (which we configure later). You should also carefully +examine the output of a FreeRADIUS debug session (`radius -X`) to ensure that +the policy is being executed in the way that you expect and that no warnings +are being generated. + +You can now change the content of the sample DHCP request by editing the +`dhcp-packet.txt` file and re-run the above command to see the server's reply. +You should examine the DHCP dictionary distrubuted with FreeRADIUS (usually +`/usr/share/freeradius/dictionary.dhcp`) which provides the list of all of the +DHCP parameters ("attributes") understood by FreeRADIUS. + +[WARNING] +==== +When you are done **remember** to remove the temporary edit that was made to +the `dhcp` virtual server that provides the static IP assignment. +==== + +=== Testing the DHCP policy + +The remainder of this guide describes how to configure the IP address plan, +setup the IP pools and define a DHCP policy. You should develop your policy by +making small, incremental changes to the provided configuration and then test +those changes with the approach described above, using `dhcpclient` and `radius -X`, +modifying the sample DHCP packet as required. If you break the policy then +revert the last change, attempt to understand what went wrong, and try +something else. + +Now xref:protocols/dhcp/policy.adoc[define the DHCP policy]. diff --git a/doc/antora/modules/howto/pages/protocols/proxy/enable_proxy_protocol.adoc b/doc/antora/modules/howto/pages/protocols/proxy/enable_proxy_protocol.adoc new file mode 100644 index 0000000..b689824 --- /dev/null +++ b/doc/antora/modules/howto/pages/protocols/proxy/enable_proxy_protocol.adoc @@ -0,0 +1,114 @@ +== Enabling PROXY Protocol + +Now that we have a working configuration which used RadSec and HAproxy +or Traefik, we are finally ready to enable PROXY Protocol. + +Configure FreeRADIUS on the `radsecsvr` host to expect the PROXY +Protocol for RadSec connections. This is done by editing the `listen +{}` section of the `tls` virtual server to include a reference to the +proxy protocol: + +.Enabling PROXY Protocol in a FreeRADIUS virtual server +======================================================= + + listen { + ... + proxy_protocol = true + ... + } + +======================================================= + +Now restart the debugging session: +[source,shell] +---- +radiusd -fxxl /dev/stdout +---- + + +For HAproxy, you should enable the PROXY Protocol on connections to +the RadSec backend, by editing the `backend` definition to add a +`send-proxy` argument: + +.Example HAproxy backend configuration with PROXY Protocol +========================================================== + + backend radsec_be + mode tcp + balance roundrobin + server radsecsvr 172.23.0.3:2083 send-proxy + +========================================================== + +Note the `send-proxy` argument in the `server` definition. + +Now reload the HAproxy service: + +[source,shell] +---- +service haproxy reload +--- + + +For Traefik, enable the PROXY Protocol on connections to the RadSec +backend by editing the `radsec-service` definition to add a reference +to the proxy protocol" + +.Example Traefik service configuration with PROXY Protocol +========================================================== + + radsec-service: + loadBalancer: + servers: + - address: "172.23.0.3:2083" + proxyProtocol: + version: 1 + +========================================================== + +Note the `proxyProtocol` and `version: 1` directives. + +Traefik should automatically detect the updates and reconfigure the +service. + + +=== Testing RadSec connectivity via a proxy using PROXY Protocol + +Finally, with your test client configured to use the proxy, perform a +test authentication: + +[source,shell] +---- + echo "User-Name = bob" | radclient 127.0.0.1 auth testing123 +---- + +You should expect to see the familiar output: + +.Example output from radclient +============================== + + Sent Access-Request Id 252 from 0.0.0.0:50118 to 127.0.0.1:1812 length 27 + Received Access-Accept Id 252 from 127.0.0.1:1812 to 127.0.0.1:50118 length 39 + +============================== + +Now examine the FreeRADIUS debug output on the RadSec server: + +.Expected output from `radiusd -X` with PROXY Protocol +====================================================== + + ... + (0) (TLS) Received PROXY protocol connection from client \ + 172.23.0.2:55343 -> 172.23.0.4:2083, via proxy 172.23.0.4:40268 -> 0.0.0.0:2083 + ... + (0) Received Access-Request Id 227 from 172.23.0.2:55343 to 172.23.0.4:2083 length 49 + (0) Sent Access-Accept Id 227 from 172.23.0.4:2083 to 172.23.0.2:55343 length 0 + ... + +====================================================== + +The output indicates that FreeRADIUS is receiving the originating +connection information from the PROXY Protocol. FreeRADIUS then +handles the RadSec requests as though they have been received directly +from the originating client. + diff --git a/doc/antora/modules/howto/pages/protocols/proxy/enable_radsec.adoc b/doc/antora/modules/howto/pages/protocols/proxy/enable_radsec.adoc new file mode 100644 index 0000000..f5e7603 --- /dev/null +++ b/doc/antora/modules/howto/pages/protocols/proxy/enable_radsec.adoc @@ -0,0 +1,188 @@ +== Enabling RadSec with FreeRADIUS + +Our first task is to set up a RadSec server by configuring an instance of +FreeRADIUS to accept RADIUS over TLS requests. + +The following steps should be performed on the host which will be the +RadSec server, we will call it `radsecsvr`. + +You can install FreeRADIUS using the NetworkRADIUS packages by +following the instructions provided here: + +<https://networkradius.com/packages/> + +Before making any configuration changes, you should stop the radiusd +service: + +[source,shell] +---- + service radiusd stop +---- + +Then, enable the `tls` virtual server: + +[source,shell] +---- +cd /etc/raddb/sites-enabled +ln -s ../sites-available/tls +---- + +The FreeRADIUS distribution contains an example Certificate Authority +that will have generated the necessary CA, server and client +certificates and keys during package installation. You can use this +CA, or you can use your own CA and certificates. + +[TIP] +==== +If the example certificates are not present (for example if FreeRADIUS was +installed from source) then FreeRADIUS will fail to start. The files can be +regenerated by running `make` in the `/etc/raddb/certs` directory. +==== + +Edit the `tls` virtual server configuration, in order to add +definitions for the clients by extending the `clients radsec {}` section: + +.Example radsec client definitions in `/etc/raddb/sites-available/tls` +==== + + clients radsec { + ... + # Direct connections from the test client + client radseccli { + ipaddr = 172.23.0.2 + proto = tls + virtual_server = default + } + # Connections via HAproxy + client haproxy { + ipaddr = 172.23.0.4 + proto = tls + virtual_server = default + } + # Connections via Traefik + client traefik { + ipaddr = 172.23.0.5 + proto = tls + virtual_server = default + } + } + +==== + +The client `ipaddr` configuration item is used to match the source IP +address of incoming connections. You must add client definitions for +each of the clients which will connect. + +For RadSec, you can just list the IP address of the RadSec client. +This client definition is used for processing RADIUS packets from the +RadSec client. + +[NOTE] +==== +A `secret` does not have to be specified for RadSec clients, as the +default is `radsec`. If you specify a secret, then that will be used +instead of `radsec`. +==== + +When the PROXY protocol is used, you must _also_ define a client which +matches the IP address of the proxy (haproxy, etc). This client is +only used to check that the source IP is permitted to connect to the +server. Fields other than `ipaddr` can be specified (and in some +cases may be required). However, all other fields will be ignored. + +For testing purposes, we want to amend the `default` virtual server so +that it accepts all authentication reqeusts and immediately responds +to accounting requests. + +Edit the `/etc/raddb/sites-enabled/default` file so that the beginning of +the `authorize` and `preacct` sections looks as follows: + +.Example default virtual server modification to unconditionally accept Access-Requests +==== + + authorize { + accept + ... + } + ... + preacct { + handled + ... + } + +==== + +This change makes the `authorize` section always "accept" the user, +and makes the `preacct` section always say "we handled the accounting +request". These changes are only for testing, and should never be +used in production. + +Start the FreeRADIUS service in the foreground with debugging enabled: + +[source,shell] +---- +radiusd -fxxl /dev/stdout +---- + +Examine the output from FreeRADIUS to ensure that it is now listening for +RadSec connection on TCP/2083: + +.Example output from running `radiusd -fxxl /dev/stdout` +==== + + FreeRADIUS Version 3.0.24 + Copyright (C) 1999-2021 The FreeRADIUS server project and contributors + ... + ... : Debug: Listening on auth+acct proto tcp address * port 2083 (TLS) bound to server default + ... : Debug: Listening on auth address * port 1812 bound to server default + ... : Debug: Listening on acct address * port 1813 bound to server default + ... : Debug: Listening on auth address :: port 1812 bound to server default + ... : Debug: Listening on acct address :: port 1813 bound to server default + ... + ... : Info: Ready to process requests + +==== + +FreeRADIUS is now ready to process RadSec traffic. + +For testing, we first test normal RADIUS over UDP functionality, then +the RadSec connection using a test client, then introduce a proxy +server, and finally we enable PROXY Protocol. Doing the tests in this +way ensures that we know that all previous steps work before trying +the next step. This process allows us to quickly narrow down +problems, and gets us to the final goal _faster_ than just "doing +everything all at once". + +=== Testing the RADIUS policy + +Before moving on, verify that the FreeRADIUS policy is able to +authenticate a local test RADIUS Access-Request over UDP: + +[source,shell] +---- +echo "User-Name = terry" | radclient 127.0.0.1 auth testing123 +---- + +Due to the `accept` we added in the `authorize` section, the expected +output should be an Access-Accept: + +.Expected output from radclient +=============================== + + Sent Access-Request Id 157 from 0.0.0.0:36850 to 127.0.0.1:1812 length 27 + Received Access-Accept Id 157 from 127.0.0.1:1812 to 127.0.0.1:36850 length 20 + +=============================== + +Any other output indicates that there is a problem with the FreeRADIUS +configuration which *must* be solved before testing RadSec. Carefully verify that +you have carried out each of the above steps correctly and examine the debug +output from FreeRADIUS, which will usually tell you what is wrong. + +See [how to read the debug +output](http://wiki.freeradius.org/radiusd-X) for instructions on +reading amd understanding the debug output. + +The next step is to xref:protocols/proxy/radsec_client.adoc[configure +FreeRADIUS as a RadSec test client] so that we can verify that our +RadSec server is working. diff --git a/doc/antora/modules/howto/pages/protocols/proxy/index.adoc b/doc/antora/modules/howto/pages/protocols/proxy/index.adoc new file mode 100644 index 0000000..5100635 --- /dev/null +++ b/doc/antora/modules/howto/pages/protocols/proxy/index.adoc @@ -0,0 +1,126 @@ += Proxying RadSec and enabling PROXY Protocol + +This guide shows how to set up FreeRADIUS to serve RadSec connections, fronted +by either HAproxy or Traefik as Layer 4 proxies that pass on the original +client connection information using PROXY Protocol. + +It is not a comprehensive guide to using RadSec with FreeRADIUS. It presents a +basic configuration that uses an example CA and does not validate certificate +attributes or perform revokation status. + + +== Introduction + +FreeRADIUS supports receiving RADIUS requests over TLS-enabled TCP connections +and supports proxying of requests over TCP connections to another TLS-enabled +homeserver. The protocol for RADIUS over TLS is called "RadSec" and is defined +in RFC 6614. + +FreeRADIUS is a capable and performant application-aware ("Layer 7") proxy / +load-balancer for RadSec and other forms of RADIUS traffic. + + +=== Layer 4 proxying + +Rather than use an application-aware proxy it is sometimes better to reduce the +performance impact incurred by re-encoding an application protocol by using a +"Layer 4" proxy that operates at the level of individual connections without +regard for the application protocol. Such a proxy is more of a "bump in the +wire" than a request buffer and minimises the latency incurred due to proxying. + +It is common to see software such as HAproxy and Traefik used in Layer 4 mode +in place of FreeRADIUS for purposes such as connection load balancing. In +addition to improved performance, these tools have the benefit that they +typically support dynamic service discovery and "hitless" reloads to +automatically adapt their connection routing based on changes to backend +services such as the introduction of new nodes with even a momentary loss of +service. + + +=== Loss of connection information + +When TCP connections are relayed through Layer 4 proxies the information +about the originating source of the connection is no longer known to the +backend service, unless it is otherwise made available. Identifying the +originator of connections is often necessary for security purposes and for +request processing. + +Whilst many application protcols support headers that allow proxies to preserve +connection information these are not helpful in the context of Layer 4 +proxying: The process of populating headers requires knowledge of the +application protocol to re-encode requests as they are transmitted between the +frontend and backend connections. + + +=== PROXY Protocol + +PROXY Protocol overcomes this limitation by allowing the original connection +information to be provided to the backend at the start of the TCP connection. +After this initial data is encoded the remainder of the conversation then +proceeds as normal. However now that the connection information is known to the +backend server it is able to process requests made on the connection as though +the connection were being made directly by the client and not via the proxy. + +PROXY Protocol is specified in this document: +http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt + + +== Requirements + +PROXY Protocol Version 1 is supported by FreeRADIUS v3.0.24 and later versions. + +You will require the following set of VMs or containers, each with their own +IP address: + +[cols="1,1,1"] +|=== +|Hostname|IP address|Purpose + +|radseccli +|172.23.0.2 +|FreeRADIUS configured to provide a RadSec test client + +|radsecsvr +|172.23.0.3 +|FreeRADIUS configured as a RadSec server + +|haproxy +|172.23.0.4 +|HAproxy in Layer 4 mode to the FreeRADIUS RadSec backend +|=== + +Optionally you may want to configure a host to run Traefik within a Docker +container using host mode networking, perhaps configured by Docker Compose, +however the installation is beyond the scope of this guide: + +[cols="1,1,1"] +|=== +|traefik +|172.23.0.5 +|Traefik configured as a TCP router with TLS passthrough to the FreeRADIUS RadSec backend +|=== + +The hostnames and IP addresses provided above are for examples purposes and are +used throughout the remainder of this guide. This guide provides commands and +output for CentOS. Other distributions will have minor differences, including +the location of the FreeRADIUS configuration (the "raddb"). + +[NOTE] +==== +You can choose to use your own hostname, IP addresses and OS distribution. You +could also use official Docker images provided by the respecitive projects, +however these prescribe methods for configuring and managing the services +that are not typical for a normal package installation which would provide a +distraction if used for by guide. +==== + + +== Sections in this guide + +This guide is organised into four parts that should be read in order: + +1. xref:protocols/proxy/enable_radsec.adoc[Enabling RadSec] +2. xref:protocols/proxy/radsec_client.adoc[Configuring a test RadSec client] +3. xref:protocols/proxy/radsec_with_haproxy.adoc[Proxying RadSec with HAproxy] +4. xref:protocols/proxy/radsec_with_traefik.adoc[Proxying RadSec with Traefik] +5. xref:protocols/proxy/enable_proxy_protocol.adoc[Enabling PROXY Protocol for RadSec] diff --git a/doc/antora/modules/howto/pages/protocols/proxy/radsec_client.adoc b/doc/antora/modules/howto/pages/protocols/proxy/radsec_client.adoc new file mode 100644 index 0000000..d92345e --- /dev/null +++ b/doc/antora/modules/howto/pages/protocols/proxy/radsec_client.adoc @@ -0,0 +1,181 @@ +== Configuring FreeRADIUS as a RadSec test client + +Unfortunately, the `radclient` program does not support RadSec. We +must therefore configure an instance of FreeRADIUS as a "transport +converter" which proxies UDP-based RADIUS requests to a RadSec +destination of our choice. + +The following steps should be performed on a client system, which we +will call `radseccli`. This system should be a new system, with a +different IP address. That is, you shoudl not edit the configuration +on the `radsecsvr` host. Doing so will break the RadSec configuration. + +Install FreeRADIUS using the NetworkRADIUS packages by following the +instructions provided here: + +<https://networkradius.com/packages/> + +Before making any configuration changes, you should stop the radiusd +service: + +[source,shell] +---- + service radiusd stop +---- + +Add a new `tls` home server definition, which will point to the RadSec +server. We do this by creating a file +`/etc/raddb/sites-enabled/radsec-homeserver` with the following +contents: + +.Example homeserver, pool and realm definitions for the RadSec service +==== + + home_server tls { + ipaddr = 172.23.0.3 # IP address of our RadSec server + port = 2083 + type = auth+acct + proto = tcp + tls { + private_key_password = whatever + private_key_file = ${certdir}/client.pem + certificate_file = ${certdir}/client.pem + ca_file = ${cadir}/ca.pem + } + } + home_server_pool tls { + type = fail-over + home_server = tls + } + realm tls { + auth_pool = tls + acct_pool = tls + } + +==== + +[TIP] +==== +Complete descriptions of each of the above configuration items can be found in the +`[raddb]/sites-available/tls` example configuration file. For simple tests, however, +we can omit all of the comments from the file. +==== + +To use this `tls` home server, we change the `default` virtual server to proxy +all authentication and accounting requests to it. + +Edit the `/etc/raddb/sites-enabled/default` file so that the beginning of +the `authorize` and `preacct` sections looks as follows: + +.Example default virtual server modification to proxy requests to a RadSec proxy server +==== + + authorize { + update control { + &Proxy-To-Realm := tls + } + handled + ... + } + ... + preacct { + update control { + &Proxy-To-Realm := tls + } + handled + ... + } + +==== + +These changes make the `tls` virtual server always proxy packets. +These changes are only for testing, and should never be used in +production. + +We must now copy the example CA certificate as well as the client +certificate and key files which are on the `radsecsrv` host to this +test client. + +Replace the following files on `radseccli` with the equivalent files from +`radsecsrv`: + +[cols="1,1,1"] +|=== +|File|Corresponding configuration item|Purpose + +|/etc/raddb/certs/ca.pem +|`ca_file` +|CA certificate which is used to authenticate the server certificate presented by the RadSec server to the client. + +|/etc/raddb/certs/client.pem +|`certificate_file` +|Client certificate (signed by the CA certificate) that is presented by the test client to the RadSec server. + +|/etc/raddb/certs/client.pem +|`private_key_file` and `private_key_password` +|Private key corresponding to the client certificate +|=== + +Note that the client certificate and key are typically bundled into a single file. + +[CAUTION] +==== +If you do not correctly replace the CA, client certificate, and key +material on the test client then the RadSec client and RadSec server +will fail to mutually authenticate each other as they do not share a +trusted CA. If you see messages like `unknown CA`, then you know that +the certificates have not been set up correctly. +==== + +Start the FreeRADIUS service in debug mode: + +[source,shell] +---- +radiusd -X +---- + + +=== Testing RadSec connectivity + +At this stage you should be able to cause the test client to send RadSec +requests directly to the RadSec server. + +Run the following to send a RADUS (UDP) Access-Request to the local FreeRADIUS +instance. It should then proxy the request over RadSec connection to +the remote RadSec server: + +[source,shell] +---- + echo "User-Name = bob" | radclient 127.0.0.1 auth testing123 +---- + +If the test client is able to successfully establish the RadSec +connection, and the RadSec server replies with an Access-Accept +response, then the output will be as follows: + +.Expected output from radclient +=============================== + + Sent Access-Request Id 252 from 0.0.0.0:50118 to 127.0.0.1:1812 length 27 + Received Access-Accept Id 252 from 127.0.0.1:1812 to 127.0.0.1:50118 length 39 + +=============================== + +Lack of response or an Access-Reject response indicates that the RadSec +connection is not being established successfully. + +There may be serveral reasons for broken connectivity including: + + * The client not accepting the certificate presented by the server. + * The server not accepting the certificate presented by the client. + +Look at the debug output generated by both the test client and the RadSec +server. In many cases it will tell you exactly what the problem is. + +Do not proceed with any further steps until direct connections between the +RadSec client and Radsec Server are working properly. + +Once things are working we are ready to +xref:protocols/proxy/radsec_with_haproxy.adoc[configure HAproxy to proxy RadSec +connections] or to xref:protocols/proxy/radsec_with_traefik.adoc[configure +Traefik to proxy RadSec connections]. diff --git a/doc/antora/modules/howto/pages/protocols/proxy/radsec_with_haproxy.adoc b/doc/antora/modules/howto/pages/protocols/proxy/radsec_with_haproxy.adoc new file mode 100644 index 0000000..e58abfe --- /dev/null +++ b/doc/antora/modules/howto/pages/protocols/proxy/radsec_with_haproxy.adoc @@ -0,0 +1,134 @@ +== Proxying RadSec with HAproxy + +This section shows how to configure HAproxy to proxy RadSec connections. + +The following steps should be performed on the `haproxy` host, unless otherwise +stated. + +Install the HAproxy package supplied with the OS distribution: + +[source,shell] +---- + yum install haproxy +---- + +Stop the haproxy service: + +[source,shell] +---- + service haproxy stop +---- + +Modify the haproxy configuration (typically `/etc/haproxy/haproxy.conf`) so +that it includes new frontend and backend configuration for the radsec service: + +.Example minimal HAproxy configuration +====================================== + + global + maxconn 100 + defaults + mode tcp + timeout connect 10s + timeout client 30s + timeout server 30s + frontend radsec_fe + bind *:2083 + default_backend radsec_be + backend radsec_be + balance roundrobin + server radsecsvr 172.23.0.3:2083 + +====================================== + +Note the `mode tcp` directive which tells HAproxy to act as a Layer 4 +proxy, so that it doesn't attempt to perform SSL termination or +decode the RADIUS protocol. + +[NOTE] +==== +The above example is a minimal configuration. In practise you will want to +retain many of the HAproxy configuration items already present in the +configuration (e.g. `log`, `chroot`, `user`, `group`), but these vary across +distributions. Other HTTP-related options that may already exist in the +configuration will conflict with `mode tcp` (Layer 4 proxying) and should be +removed if HAproxy complains about them. + +However, you should first get things working with the minimal +configuration which is known to work, and then make customisations. +If you start off with a complex configuration, then there may be a +large number of things which are broken, and debugging them all will +be difficult. Start simple, and then add complexity! +==== + +Restart the haproxy service in foreground mode for debugging purposes: + +[source,shell] +---- +haproxy -f /etc/haproxy/haproxy.cfg -db +---- + + +=== Testing RadSec connectivity via HAproxy + +Now edit the test RadSec client, so that instead of making connections directly +to the RadSec server it makes connections to the HAproxy server. + +On `radseccli` edit the `/etc/raddb/sites-enabled/tls` file, and set +the IP address to the address of the `haproxy` host. + +.Example updated test client homeserver configuration +===================================================== + + home_server tls { + ipaddr = 172.23.0.4 # Updated from radsecsvr to haproxy + ... + } + +===================================================== + +Restart the debug mode session: + +[source,shell] +---- +radiusd -X +---- + +Perform a test authentication: + +[source,shell] +---- + echo "User-Name = bob" | radclient 127.0.0.1 auth testing123 +---- + +If the test client is able to successfully establish the RadSec +connection via HAproxy, and the RadSec server replies with an +Access-Accept response, then the output will be as follows: + +.Expected output from radclient +=============================== + + Sent Access-Request Id 252 from 0.0.0.0:50118 to 127.0.0.1:1812 length 27 + Received Access-Accept Id 252 from 127.0.0.1:1812 to 127.0.0.1:50118 length 39 + +=============================== + +HAproxy should also log a message that indicates that the connection was +proxied, such as the following: + +.Expected output from HAproxy +============================= + + <150>...: Connect from 172.23.0.2:50087 to 172.23.0.4:2083 (radius_fr/TCP) + +============================= + +Any other output from radclient or HAproxy indicates that there is a +problem with the HAproxy configuration, or that FreeRADIUS is not +accepting connection from the `haproxy` host, which must be solved +before continuing. + +Once proxied connections are working we are ready to +xref:protocols/proxy/enable_proxy_protocol.adoc[enable the PROXY +Protocol] on both HAproxy and the RadSec server. + diff --git a/doc/antora/modules/howto/pages/protocols/proxy/radsec_with_traefik.adoc b/doc/antora/modules/howto/pages/protocols/proxy/radsec_with_traefik.adoc new file mode 100644 index 0000000..11030e9 --- /dev/null +++ b/doc/antora/modules/howto/pages/protocols/proxy/radsec_with_traefik.adoc @@ -0,0 +1,128 @@ +== Proxying RadSec with Traefik + +This section shows how to configure Traefik to proxy RadSec connections. You +should skip this section if you are not using Traefik as your proxy. + +Installing Traefik is beyond the scope of this guide. It is typically installed +as a service mesh router within a Docker or Kubernetes environment using +offical Docker images. + +Traefik configuration has two components of interest: + + * Static configuration: Defines "entrypoints" on which Traefik listens for connections. + * Dynamic configuration: Defines backend service components and the routing policy. + +Traefik supports a number of providers of dynamic configuration data for the +router and service definitions. For demonstration purposes the files provider +is used here, however you can switch to another provide once you have things +working using this method. + +The static configuration can be provided by starting Traefik with the following +arguments: + +.Example Traefik static configuration +===================================== + + traefik \ + --log.level=DEBUG \ + --providers.file.filename=/etc/traefik/dynamic_config.yml + --providers.file.watch=true + --entryPoints.radsec.address=:2083 + +===================================== + +Note that a `radsec` entrypoint is defined to listen on port 2083 and that a +static `file` provider is used to defined the dynamic services. + +The backend for RadSec should be defined in this file as follows: + +.Example Traefik dynamic configuration +====================================== + + tcp: + routers: + radsec-router: + entryPoints: + - radsec + rule: "HostSNI(`*`)" + service: "radsec-service" + tls: + passthrough: true + services: + radsec-service: + loadBalancer: + servers: + - address: "172.23.0.3:2083" + +====================================== + +Note the `passthrough: true` directive under `tls:` which tells Treafik not to +attempt TLS termination which it would otherwise perform for all incoming TLS +connections. We require that the connection is passed through from the RadSec +client to the RadSec server without being reterminated since the end client's +certificate is authenticated by the RadSec server and many be used for +policy decisions. + + +=== Testing RadSec connectivity via Traefik + +Now amend the test RadSec client so that instead of making connections directly +to the RadSec server it makes them via Traefik. + +On `radseccli` amend `/etc/raddb/sites-enabled/tls` and set the IP address to +that of the `traefik` host. + +.Example updated test client homeserver configuration +===================================================== + + home_server tls { + ipaddr = 172.23.0.5 # Updated from radsecsvr to traefik + ... + } + +===================================================== + +Restart the debug mode session: + +[source,shell] +---- +radiusd -X +---- + +Perform a test authentication: + +[source,shell] +---- + echo "User-Name = bob" | radclient 127.0.0.1 auth testing123 +---- + +If the test client is able to successfully establish the RadSec connection via +Traefik and the RadSec server replies with an Access-Accept response then the +output will be as follows: + +.Example output from radclient +============================== + + Sent Access-Request Id 252 from 0.0.0.0:50118 to 127.0.0.1:1812 length 27 + Received Access-Accept Id 252 from 127.0.0.1:1812 to 127.0.0.1:50118 length 39 + +============================== + +Traefik should also log a message that indicates that the connection was +proxied, such as the following: + +.Example output from Traefik +============================ + + time="..." level=debug msg="Handling connection from 172.23.0.2:57367" + +============================ + +Any other output from radclient or Traefik indicates that there is a problem +with the Traefik configuration or that FreeRADIUS is not accepting connection +from the `traefik` host, which must be solved before continuing. + +Once proxied connections are working we are ready to +xref:protocols/proxy/enable_proxy_protocol.adoc[enable the PROXY Protocol] on +both Traefik and the RadSec server. + diff --git a/doc/antora/modules/installation/nav.adoc b/doc/antora/modules/installation/nav.adoc new file mode 100644 index 0000000..26ce32e --- /dev/null +++ b/doc/antora/modules/installation/nav.adoc @@ -0,0 +1,5 @@ +* xref:index.adoc[Installing and upgrading] +** xref:packages.adoc[Install from packages] +** xref:dependencies.adoc[Dependencies] +** xref:source.adoc[Build from source] +** xref:upgrade.adoc[Upgrading to v3] diff --git a/doc/antora/modules/installation/pages/dependencies.adoc b/doc/antora/modules/installation/pages/dependencies.adoc new file mode 100644 index 0000000..e910e76 --- /dev/null +++ b/doc/antora/modules/installation/pages/dependencies.adoc @@ -0,0 +1,58 @@ += FreeRADIUS Dependencies + +Some external dependencies must be installed before building or +running FreeRADIUS. The core depends on two mandatory libraries: +`libtalloc` for memory management and `libkqueue` for event +handling. + +Many of the modules also have optional dependencies. For example, +the LDAP module requires LDAP client libraries to be installed +and database modules need their respective database client +libraries. + +If building from source code, the configure stage will check for +the optional dependencies. Any missing libraries will cause that +particular module to be skipped. + +== Libraries + +=== libtalloc + +Talloc is a memory allocation library available at +https://talloc.samba.org/talloc/doc/html/index.html + +*OSX* + +`# brew install talloc` + +*Debian, Ubuntu and `dpkg`-based systems* + +`# apt-get install libtalloc-dev` + +*RedHat or CentOS* + +``` +# subscription-manager repos --enable rhel-7-server-optional-rpms +# yum install libtalloc-dev +``` + +=== kqueue + +Kqueue is an event / timer API originally written for BSD systems. +It is _much_ simpler to use than third-party event libraries. A +library, `libkqueue`, is available for Linux systems. + +*OSX* + +_kqueue is already available, there is nothing to install._ + +*Debian, Ubuntu and `dpkg`-based systems* + +`# apt-get install libkqueue-dev` + +*RedHat or CentOS* + +``` +# subscription-manager repos --enable rhel-7-server-optional-rpms +# yum install libkqueue-dev +``` diff --git a/doc/antora/modules/installation/pages/index.adoc b/doc/antora/modules/installation/pages/index.adoc new file mode 100644 index 0000000..b810078 --- /dev/null +++ b/doc/antora/modules/installation/pages/index.adoc @@ -0,0 +1,15 @@ +== Installation + +FreeRADIUS is available from multiple sources: + +* Official xref:packages.adoc[Network RADIUS packages] +* xref:source.adoc[Source code] +* Many Operating System distributions + +We highly recommend using the official packages from Network +RADIUS, where available. + +The documents in this section cover details of the above +installation methods, as well as instructions on building +packages locally. + diff --git a/doc/antora/modules/installation/pages/packages.adoc b/doc/antora/modules/installation/pages/packages.adoc new file mode 100644 index 0000000..ffc52cd --- /dev/null +++ b/doc/antora/modules/installation/pages/packages.adoc @@ -0,0 +1,22 @@ +== Install from packages + +Network RADIUS provide pre-built binary packages of FreeRADIUS for +common Linux distributions. This is the recommended installation +method when packages are available for your system. + +The official http://packages.networkradius.com[Network RADIUS +packages] page contains recent FreeRADIUS packages and +installation instructions. + +=== Distribution-supplied packages + +While many Operating System distributions ship FreeRADIUS +packages, the versions they include are often years out of date. +As well as missing out on the latest bug fixes and features, this +also means that it is very hard to know if an issue encountered is +still a problem or if it is fixed in the latest release. + +Therefore, whilst the distribution-supplied packages can often be +the most convenient to install, we do not usually recommend using +them. + diff --git a/doc/antora/modules/installation/pages/source.adoc b/doc/antora/modules/installation/pages/source.adoc new file mode 100644 index 0000000..cf40a79 --- /dev/null +++ b/doc/antora/modules/installation/pages/source.adoc @@ -0,0 +1,199 @@ +== Building from Source + +We recommend xref:packages.adoc[installing from packages] if +possible. Full instructions on building and installing from source +code follow. + +The mandatory xref:installation:dependencies.adoc[dependencies] +must be installed before FreeRADIUS can be built. These dependencies +are `libtalloc` and `libkqueue`, which FreeRADIUS uses for memory +management, and platform-independent event handling. + +Per-module dependencies that enable support for external services +such as LDAP, SQL, etc, are optional. They must be installed for +any modules that are to be used. The FreeRADIUS `./configure` step +will automatically detect if each module has its dependencies met +and automatically enable support for them. If the features you +require are not enabled you should inspect the configure script +output to figure out which additional development libraries need +to be installed. + +The FreeRADIUS source may be obtained from a number of locations: + +* Download the latest version of the FreeRADIUS source from + https://www.freeradius.org/releases/[the FreeRADIUS web site]; or +* download directly from the + ftp://ftp.freeradius.org/pub/freeradius/[FreeRADIUS FTP site]; or +* download from + https://github.com/FreeRADIUS/freeradius-server/[GitHub]. + +The file wil be name something like: `freeradius-server-3.0.22.tar.gz`. +Later versions will be `3.0.23`, or `4.0.0`, etc. PGP signatures are +also provided for official releases from the FTP site; these are +named e.g. `freeradius-server-3.0.22.tar.gz.sig`. + +Un-tar the file, and change to the FreeRADIUS directory (where +`VERSION` below is the version of the server that you have +downloaded). + +[source,shell] +---- +tar -zxf freeradius-server-VERSION.tar.gz +cd freeradius-server-VERSION +---- + +Take the following steps to build and install the server from source: + +[source,shell] +---- +./configure +make +sudo make install +---- + +=== Custom build + +FreeRADIUS has GNU autoconf support. This means you have to run +`./configure`, and then run `make`. To see which configuration +options are supported, run `./configure --help`, and read its output. + +The `make install` stage will install the binaries, the "man" pages, +and _may_ install the configuration files. If you have not installed a +RADIUS server before, then the configuration files for FreeRADIUS will +be installed. + +If you already have a RADIUS server installed, then *FreeRADIUS +WILL NOT over-write your current configuration.* + +The `make install` process will warn you about the files it could not +install. + +If you see a warning message about files that could not be +installed, then you *must* ensure that the new server is using the +new configuration files and not the old configuration files, as +this may cause undesired behavior and failure to operate correctly. + +The initial output from running in debugging mode (`radiusd -X`) +will tell you which configuration files are being used. See +xref:installation:upgrade.adoc[Upgrading] for information about +upgrading from older versions. There _may_ be changes in the +dictionary files which are required for a new version of the +software. These files will not be installed over your current +configuration, so you *must* verify and install any problem files by +hand, for example using `diff(1)` to check for changes. + +When installing from source, it is _extremely_ helpful to read the +output of `./configure`, `make`, and `make install`. If a +particular module you expected to be installed was not installed, +then the output will tell you why that module was not installed. +The most likely reason is that required libraries (including their +development header files) are not available. + +Please do _not_ post questions to the FreeRADIUS users list +without first carefully reading the output of this process as it +often contains the information needed to resolve a problem. + +== Upgrading To A New Minor Release + +The installation process will not over-write your existing configuration +files. It will, however, warn you about the files it did not install. +These will require manual integration with the existing files. + +It is not possible to re-use configurations between different major +versions of the server. + +For details on what has changed between the version, see the +xref:installation:upgrade.adoc[upgrade] guide. + +We _strongly_ recommend that new major versions be installed in a +different location than any existing installations. Any local policies +can then be migrated gradually to the configuration format of the new +major version. The number of differences in the new configuration mean +that is is both simpler and safer to migrate your configurations rather +than to try and just get the old configuration to work. + +== Running the server + +If the server builds and installs, but doesn’t run correctly, then +you should first use debugging mode (`radiusd -X`) to figure out +the problem. + +This is your best hope for understanding the problem. Read _all_ +of the messages which are printed to the screen, the answer to +your problem will often be in a warning or error message. + +We really cannot emphasize that last sentence enough. Configuring +a RADIUS server for complex local authentication isn’t a trivial +task. Your _best_ and _only_ method for debugging it is to read +the debug messages, where the server will tell you exactly what +it’s doing, and why. You should then compare its behaviour to what +you intended, and edit the configuration files as appropriate. + +If you don’t use debugging mode, and ask questions on the mailing +list, then the responses will all tell you to use debugging mode. +The server prints out a lot of information in this mode, including +suggestions for fixes to common problems. Look especially for +`WARNING` and `ERROR` messages in the output, and read the related +messages. + +Since the main developers of FreeRADIUS use debugging mode to +track down their configuration problems with the server, it’s a +good idea for you to use it, too. If you don’t, there is little +hope for you to solve any configuration problem related to the +server. + +To start the server in debugging mode, do: + +[source,shell] +---- +radiusd -X +---- + +You should see a lot of text printed on the screen as it starts up. If +you don’t, or if you see error messages, please read the FAQ: + +https://wiki.freeradius.org/guide/FAQ + +If the server says `Ready to process requests.`, then it is running +properly. From another shell (or another window), type + +[source,shell] +---- +radtest test test localhost 0 testing123 +---- + +You should see the server print out more messages as it receives the +request, and responds to it. The `radtest` program should receive the +response within a few seconds. It doesn’t matter if the authentication +request is accepted or rejected, what matters is that the server +received the request, and responded to it. + +You can now edit the configuration files for your local system. You will +usually want to start with `sites-enabled/default` for main +configurations. To set which NASes (clients) can communicate with this +server, edit `raddb/clients.conf`. Please read the configuration files +carefully, as many configuration options are only documented in comments +in the file. + +Note that is is _highly_ recommended that you use some sort of version +control system to manage your configuration, such as git or Subversion. +You should then make small changes to the configuration, checking in and +testing as you go. When a config change causes the server to stop +working, you will be able to easily step back and find out what update +broke the configuration. + +It is also considered a best practice to maintain a staging or +development environment. This allows you to test and integrate your +changes without impacting your active production environment. You should +make the appropirate investment in order to properly support a critical +resource such as your authentication servers. + +Configuring and running the server MAY be complicated. Many modules have +`man` pages. See `man rlm_pap`, or `man rlm_*` for information. +Please read the documentation in the doc/ directory. The comments in the +configuration files also contain a lot of documentation. + +If you have any additional issues, the FAQ is also a good place to +start. + +https://wiki.freeradius.org/guide/FAQ diff --git a/doc/antora/modules/installation/pages/upgrade.adoc b/doc/antora/modules/installation/pages/upgrade.adoc new file mode 100644 index 0000000..67874c8 --- /dev/null +++ b/doc/antora/modules/installation/pages/upgrade.adoc @@ -0,0 +1,737 @@ += Upgrading from v2 to v3 + +The configuration for 3.0 is *largely* compatible with the 2.x.x +configuration. However, it is NOT possible to simply use the 2.x.x +configuration as-is. Instead, you should re-create it. + +== Security + +A number of configuration items have moved into the "security" subsection of +radiusd.conf. If you use these, you should move them. Otherwise, they can +be ignored. + +The list of moved options is: + +* chroot +* user +* group +* allow_core_dumps +* reject_delay +* status_server + +These entries should be moved from "radiusd.conf" to the "security" +subsection of that file. + +== Naming + +Many names used by configuration items were inconsistent in earlier +versions of the server. These names have been unified in version 3.0. + +If a file is being referenced or created the config item `filename` +is used. + +If a file is being created, the initial permissions are set by the +`permissions` config item. + +If a directory hierarchy needs to be created, the permissions are set +by `dir_permissions`. + +If an external host is referenced in the context of a module the +`server` config item is used. + +Unless the config item is a well recognised portmanteau +(as `filename` is for example), it must be written as multiple +distinct words separated by underscores `_`. + +The configuration items `file`, `script_file`, `module`, +`detail`, `detailfile`, `attrsfile`, `perm`, `dirperm`, +`detailperm`, and `hostname` are deprecated. As well as any false +portmanteaus, and configuration items that used hyphens as word +delimiters. e.g. `foo-bar` has been changed to `foo_bar`. Please +update your module configuration to use the new syntax. + +In most cases the server will tell you the replacement config item to +use. As always, run the server in debugging mode to see these +messages. + +== Modules Directory + +As of version 3.0, the `modules/` directory no longer exists. + +Instead, all "example" modules have been put into the +`mods-available/` directory. Modules which can be loaded by the +server are placed in the `mods-enabled/` directory. All of the +modules in that directory will be loaded. This means that the +`instantiate` section of radiusd.conf is less important. The only +reason to list a module in the `instantiate` section is to force +ordering when the modules are loaded. + +Modules can be enabled by creating a soft link. For module `foo`, do: + +[source,shell] +---- +cd raddb/mods-enabled +ln -s ../mods-available/foo +---- + +To create "local" versions of the modules, we suggest copying the file +instead. This leaves the original file (with documentation) in the +`mods-available/` directory. Local changes should go into the +`mods-enabled/` directory. + +Module-specific configuration files are now in the `mods-config/` +directory. This change allows for better organization, and means that +there are fewer files in the main `raddb` directory. See +`mods-config/README.rst` for more details. + +== Changed Modules + +The following modules have been changed in this version. + +=== rlm_sql + +The SQL configuration has been moved from `sql.conf` to +`mods-available/sql`. The `sqlippool.conf` file has also been +moved to `mods-available/sqlippool`. + +The SQL module configuration has been changed. The old connection +pool options are no longer accepted: + +---- +num_sql_socks +connect_failure_retry_delay +lifetime +max_queries +---- + +Instead, a connection pool configuration is used. This configuration +contains all of the functionality of the previous configuration, but +in a more generic form. It also is used in multiple modules, meaning +that there are fewer different configuration items. The mapping +between the configuration items is: + +---- +num_sql_socks -> pool { max } +connect_failure_retry_delay -> pool { retry_delay } +lifetime -> pool { lifetime } +max_queries -> pool { uses } +---- + +The pool configuration adds a number of new configuration options, +which allow the administrator to better control how FreeRADIUS uses +SQL connection pools. + +The following parameters have been changed: + +---- +trace -> removed +tracefile -> logfile +---- + +The logfile is intended to log SQL queries performed. If you need to +debug the server, use debugging mode. If `logfile` is set, then +*all* SQL queries will go to `logfile`. + +You can now use a NULL SQL database: + +.Example +---- +driver = rlm_sql_null +---- + +This is an empty driver which will always return "success". It is +intended to be used to replace the `sql_log` module, and to work in +conjunction with the `radsqlrelay` program. Simply take your normal +configuration for raddb/mods-enabled/sql, and set: + +.Example +---- +driver = rlm_sql_null +... +logfile = ${radacctdir}/sql.log +---- + +All of the SQL queries will be logged to that file. The connection +pool does not need to be configured for the `null` SQL driver. It +can be left as-is, or deleted from the SQL configuration file. + + +=== rlm_sql_sybase + +The `rlm_sql_sybase` module has been renamed to `rlm_sql_freetds` +and the old `rlm_sql_freetds` module has been removed. + +`rlm_sql_sybase` used the newer ct-lib API, and `rlm_sql_freetds` +used an older API and was incomplete. + +The new `rlm_sql_freetds` module now also supports database +selection on connection startup so `use` statements no longer +have to be included in queries. + + +=== sql/dialup.conf + +Queries for post-auth and accounting calls have been re-arranged. The +SQL module will now expand the 'reference' configuration item in the +appropriate sub-section, and resolve this to a configuration +item. This behaviour is similar to rlm_linelog. This dynamic +expansion allows for a dynamic mapping between accounting types and +SQL queries. Previously, the mapping was fixed. Any "new" accounting +type was ignored by the module. Now, support for any accounting type +can be added by just adding a new target, as below. + +Queries from v2.x.x may be manually copied to the new v3.0 +`dialup.conf` file (`raddb/sql/main/<dialect>/queries.conf`). +When doing this you may also need to update references to the +accounting tables, as their definitions will now be outside of +the subsection containing the query. + +The mapping from old "fixed" query to new "dynamic" query is as follows: + +---- +accounting_onoff_query -> accounting.type.accounting-on.query +accounting_update_query -> accounting.type.interim-update.query +accounting_update_query_alt +> accounting.type.interim-update.query +accounting_start_query -> accounting.type.start.query +accounting_start_query_alt +> accounting.type.start.query +accounting_stop_query -> accounting.type.stop.query +accounting_stop_query_alt +> accounting.type.stop.query +postauth_query -> post-auth.query +---- + +Alternatively a 2.x.x config may be patched to work with the +3.0 module by adding the following: + +.Example +[source,unlang] +---- + accounting { + reference = "%{tolower:type.%{Acct-Status-Type}.query}" + type { + accounting-on { + query = "${....accounting_onoff_query}" + } + accounting-off { + query = "${....accounting_onoff_query}" + } + start { + query = "${....accounting_start_query}" + query = "${....accounting_start_query_alt}" + } + interim-update { + query = "${....accounting_update_query}" + query = "${....accounting_update_query_alt}" + } + stop { + query = "${....accounting_stop_query}" + query = "${....accounting_stop_query_alt}" + } + } + } + post-auth { + query = "${..postauth_query}" + } +---- + +In general, it is safer to migrate the configuration rather than +trying to "patch" it, to make it look like a v2 configuration. + +Note that the sub-sections holding the queries are labelled +`accounting-on`, and not `accounting_on`. The reason is that the +names of these sections are taken directly from the +`Accounting-Request` packet, and the `Acct-Status-Type` field. +The `sql` module looks at the value of that field, and then looks +for a section of that name, in order to find the query to use. + +That process means that the server can be extended to support any new +value of `Acct-Status-Type`, simply by adding a named sub-section, +and a query. This behavior is preferable to that of v2, which had +hard-coded queries for certain `Acct-Status-Type` values, and was +ignored all other values. + +=== rlm_ldap + +The LDAP module configuration has been substantially changed. Please +read `raddb/mods-available/ldap`. It now uses a connection pool, +just like the SQL module. + +Many of the configuration items remain the same, but they have been +moved into subsections. This change is largely cosmetic, but it makes +the configuration clearer. Instead of having a large set of random +configuration items, they are now organized into logical groups. + +You will need to read your old LDAP configuration, and migrate it +manually to the new configuration. Simply copying the old +configuration WILL NOT WORK. + +Users upgrading from 2.x.x who used to call the ldap module in +`post-auth` should now set `edir_autz = yes`, and remove the `ldap` +module from the `post-auth` section. + +=== rlm_ldap and LDAP-Group + +In 2.x.x the registration of the `LDAP-Group` pair comparison was done +by the last instance of rlm_ldap to be instantiated. In 3.0 this has +changed so that only the default `ldap {}` instance registers +`LDAP-Group`. + +If `<instance>-LDAP-Group` is already used throughout your configuration +no changes will be needed. + +=== rlm_ldap authentication + +In 2.x.x the LDAP module had a `set_auth_type` configuration item, +which forced `Auth-Type := ldap`. This was removed in 3.x.x as it +often did not work, and was not consistent with the rest of the +server. We generally recommend that LDAP should be used as a +database, and that FreeRADIUS should do authentication. + +The only reason to use `Auth-Type := ldap` is when the LDAP server +will not supply the "known good" password to FreeRADIUS, *and* where +the Access-Request contains User-Password. This situation happens +only for Active Directory. If you think you need to force `Auth-Type +:= ldap` in other situations, you are very likely to be wrong. + +The following is an example of what should be inserted into the +`authorize {}` and `authenticate {}` sections of the relevant +virtual-servers, to get functionality equivalent to v2.x: + +.Example +[source,unlang] +---- +authorize { + ... + ldap + if ((ok || updated) && User-Password) { + update control { + Auth-Type := ldap + } +... +} +authenticate { + ... + Auth-Type ldap { + ldap + } +... +} +---- + +=== rlm_eap + +The EAP configuration has been moved from `eap.conf` to +`mods-available/eap`. A new `pwd` subsection has been added for +EAP-PWD. + + +=== rlm_expiration & rlm_logintime + +The rlm_expiration and rlm_logintime modules no longer add a `Reply-Message`, +the same behaviour can be achieved checking the return code of the module and +adding the `Reply-Message` with unlang: + +.Example +[source,unlang] +---- +expiration +if (userlock) { + update reply { + Reply-Message := "Your account has expired" + } +} +---- + + +=== rlm_unix + +The `unix` module does not have an `authenticate` section. So you +cannot set `Auth-Type := System`. The `unix` module has also been +deleted from the examples in `sites-available/`. Listing it there +has been deprecated for many years. + +The PAP module can do crypt authentication. It should be used instead +of Unix authentication. + +The Unix module still can pull the passwords from `/etc/passwd`, or +`/etc/shadow`. This is done by listing it in the `authorize` +section, as is done in the examples in `sites-available/`. However, +some systems using NIS or NSS will not supply passwords to the +`unix` module. For those systems, we recommend putting users and +passwords into a database, instead of relying on `/etc/passwd`. + + +=== rlm_preprocess + +In 2.x.x `huntroups` and `users` files were loaded from default locations +without being configured explicitly. Since 3.x.x you need to set +`huntgroups` and `users` configuration item(s) in module section in order +to get them being processed. + + +== New Modules + +=== rlm_date + +Instances of rlm_date register an xlat method which can translate +integer and date values to an arbitrarily formatted date time +string, or an arbitrarily formated time string to an integer, +depending on the attribute type passed. + + +=== rlm_rest + +The `rest` module is used to translate RADIUS requests into +RESTfull HTTP requests. Currently supported body types are JSON +and POST. + + +=== rlm_unpack + +The `unpack` module is used to turn data buried inside of binary +attributes. e.g. if we have `Class = 0x00000001020304` then: + +.Example +[source,unlang] +---- +Tmp-Integer-0 := "%{unpack:&Class 4 short}" +---- + +will unpack octets 4 and 5 as a "short", which has value 0x0304. +All integers are assumed to be in network byte order. + + +=== rlm_yubikey + +The `yubikey` module can be used to forward yubikey OTP token +values to a Yubico validation server, or decrypt the token +using a PSK. + + +== Deleted Modules + +The following modules have been deleted, and are no longer supported +in Version 3. If you are using one of these modules, your +configuration can probably be changed to not need it. Otherwise email +the freeradius-devel list, and ask about the module. + + +=== rlm_acct_unique + +This module has been replaced by the "acct_unique" policy. See +raddb/policy.d/accounting. + +The method for calculating the value of acct_unique has changed. +However, as this method was configurable, this change should not +matter. The only issue is in having a v2 and v3 server writing to the +same database at the same time. They will calculate different values +for Acct-Unique-Id. + + +=== rlm_acctlog + +You should use rlm_linelog instead. That module has a superset of the +acctlog functionality. + + +=== rlm_attr_rewrite + +The attr_rewrite module looked for an attribute, and then re-wrote it, +or created a new attribute. All of that can be done in "unlang". + +A sample configuration in "unlang" is: + +.Example +[source,unlang] +---- +if (request:Calling-Station-Id) { + update request { + Calling-Station-Id := "...." + } +} +---- + +We suggest updating all uses of attr_rewrite to use unlang instead. + + +=== rlm_checkval + +The checkval module compared two attributes. All of that can be done in "unlang": + +.Example +[source,unlang] +---- +if (&request:Calling-Station-Id == &control:Calling-Station-Id) { + ok +} +---- + +We suggest updating all uses of checkval to use unlang instead. + + +=== rlm_dbm + +No one seems to use it. There is no sample configuration for it. +There is no speed advantage to using it over the "files" module. +Modern systems are fast enough that 10K entries can be read from the +"users" file in about 10ms. If you need more users than that, use a +real database such as SQL. + + +=== rlm_fastusers + +No one seems to use it. It has been deprecated since Version 2.0.0. +The "files" module was rewritten so that the "fastusers" module was no +longer necessary. + + +=== rlm_policy + +No one seems to use it. Almost all of its functionality is available +via `unlang`. + + +=== rlm_sim_files + +The rlm_sim_files module has been deleted. It was never marked "stable", +and was never used in a production environment. There are better ways +to test EAP. + +If you want similar functionality, see rlm_passwd. It can read CSV +files, and create attributes from them. + + +=== rlm_sql_log + +This has been replaced with the "null" sql driver. See +`raddb/mods-available/sql` for an example configuration. + +The main SQL module has more functionality than rlm_sql_log, and +results in less code in the server. + +== Other Functionality + +The following is a list of new / changed functionality. + +=== RadSec + +RadSec (or RADIUS over TLS) is now supported. RADIUS over bare TCP +is also supported, but is recommended only for secure networks. + +See `sites-available/tls` for complete details on using TLS. The server +can both receive incoming TLS connections, and also originate outgoing +TLS connections. + +The TLS configuration is taken from the old EAP-TLS configuration. It +is largely identical to the old EAP-TLS configuration, so it should be +simple to use and configure. It re-uses much of the EAP-TLS code, +so it is well-tested and reliable. + +Once RadSec is enabled, normal debugging mode will not work. This is +because the TLS code requires threading to work properly. Instead of doing: + +.Example +[source,shell] +---- +radiusd -X +---- + +you will need to do: + +.Example +[source,shell] +---- +radiusd -fxx -l stdout +---- + +That's the price to pay for using RadSec. This limitation may be +lifted in a future version of the server. + + +=== PAP and User-Password + +From version 3.0 onwards the server no longer supports authenticating +against a cleartext password in the 'User-Password' attribute. Any +occurences of this (for instance, in the users file) should now be changed +to 'Cleartext-Password' instead. + +e.g. change entries like this: + +---- +bob User-Password == "hello" +---- + +to ones like this: + +---- +bob Cleartext-Password := "hello" +---- + +If this is not done, authentication will likely fail. The server will +also print a helpful message in debugging mode. + +If it really is impossible to do this, the following unlang inserted above +the call to the pap module may be used to copy User-Password to the correct +attribute: + +.Example +[source,unlang] +---- +if (!control:Cleartext-Password && control:User-Password) { + update control { + Cleartext-Password := "%{control:User-Password}" + } +} +---- + +However, this should only be seen as a temporary, not permanent, fix. +It is better to fix your databases to use the correct configuration. + + +== Unlang + + +The unlang policy language is compatible with v2, but has a number of +new features. See `man unlang` for complete documentation. + + +=== Errors + +Many more errors are caught when the server is starting up. Syntax +errors in `unlang` are caught, and a helpful error message is +printed. The error message points to the exact place where the error +occurred: + +---- + ./raddb/sites-enabled/default[230]: Parse error in condition + ERROR: if (User-Name ! "bob") { + ERROR: ^ Invalid operator +---- + +`update` sections are more generic. Instead of doing `update +reply`, you can do the following: + +.Example +[source,unlang] +---- +update { + reply:Class := 0x0000 + control:Cleartext-Password := "hello" +} +---- + +This change means that you need fewer `update` sections. + + +=== Comparisons + +Attribute comparisons can be done via the `&` operator. When you +needed to compare two attributes, the old comparison style was: + +.Example +[source,unlang] +---- +if (User-Name == "%{control:Tmp-String-0}") { +---- + +This syntax is inefficient, as the `Tmp-String-0` attribute would be +printed to an intermediate string, causing unnecessary work. You can +now instead compare the two attributes directly: + +.Example +[source,unlang] +---- +if (&User-Name == &control:Tmp-String-0) { +---- + +See `man unlang` for more details. + +=== Casts + +Casts are now permitted. This allows you to force type-specific +comparisons: + +.Example +[source,unlang] +---- +if (<ipaddr>"%{sql: SELECT...}" == 127.0.0.1) { +---- + +This forces the string returned by the SELECT to be treated as an IP +address, and compare to `127.0.0.1`. Previously, the comparison +would have been done as a simple string comparison. + + +=== Networks + +IP networks are now supported: + + if (127.0.0.1/32 == 127.0.0.1) { + +Will be `true`. The various comparison operators can be used to +check IP network membership:: + +.Example +[source,unlang] +---- +if (127/8 > 127.0.0.1) { +---- + +Returns `true`, because `127.0.0.1` is within the `127/8` +network. However, the following comparison will return `false`:: + +.Example +[source,unlang] +---- +if (127/8 > 192.168.0.1) { +---- + +because `192.168.0.1` is outside of the `127/8` network. + + +=== Optimization + +As `unlang` is now pre-compiled, many compile-time optimizations are +done. This means that the debug output may not be exactly the same as +what is in the configuration files: + + if (0 && (User-Name == "bob')) { + +The result will always be `false`, as the `if 0` prevents the +following `&& ...` from being evaluated. + +Not only that, but the entire contents of that section will be ignored +entirely: + +.Example +[source,unlang] +---- +if (0) { + this_module_does_not_exist + and_this_one_does_not_exist_either +} +---- + +In v2, that configuration would result in a parse error, as there is +no module called `this_module_does_not_exist`. In v3, that text is +ignored. This ability allows you to have dynamic configurations where +certain parts are used (or not) depending on compile-time configuration. + +Similarly, conditions which always evaluate to `true` will be +optimized away: + + +.Example +[source,unlang] +---- +if (1) { + files +} +---- + +That configuration will never show the `if (1)` output in debugging mode. + +=== Dialup_admin + +The dialup_admin directory has been removed. No one stepped forward +to maintain it, and the code had not been changed in many years. + diff --git a/doc/antora/modules/unlang/.gitignore b/doc/antora/modules/unlang/.gitignore new file mode 100644 index 0000000..c5722d7 --- /dev/null +++ b/doc/antora/modules/unlang/.gitignore @@ -0,0 +1 @@ +!*.adoc diff --git a/doc/antora/modules/unlang/nav.adoc b/doc/antora/modules/unlang/nav.adoc new file mode 100644 index 0000000..77be328 --- /dev/null +++ b/doc/antora/modules/unlang/nav.adoc @@ -0,0 +1,51 @@ +* xref:index.adoc[Unlang Policy Language] + +** xref:list.adoc[Attribute Lists] +** xref:attr.adoc[Attribute References] +** xref:return_codes.adoc[Return Codes] + +** xref:keywords.adoc[Keywords] +*** xref:break.adoc[break] +*** xref:case.adoc[case] +*** xref:else.adoc[else] +*** xref:elsif.adoc[elsif] +*** xref:foreach.adoc[foreach] +*** xref:group.adoc[group] +*** xref:if.adoc[if] +*** xref:load-balance.adoc[load-balance] +*** xref:redundant-load-balance.adoc[redundant-load-balance] +*** xref:redundant.adoc[redundant] +*** xref:return.adoc[return] +*** xref:switch.adoc[switch] +*** xref:update.adoc[update] + +** xref:module.adoc[Modules] +*** xref:module_method.adoc[Module Methods] +*** xref:module_builtin.adoc[Built-in Modules] + +** xref:type/index.adoc[Data Types] +*** xref:type/index.adoc[List of Data Types] +*** xref:type/ip.adoc[IP Addresses] +*** xref:type/numb.adoc[Numbers] +*** xref:type/string/single.adoc[Single Quoted Strings] +*** xref:type/string/double.adoc[Double Quoted Strings] +*** xref:type/string/backticks.adoc[Backtick-quoted string] +*** xref:type/string/unquoted.adoc[Unquoted Strings] + +** xref:condition/index.adoc[Conditional Expressions] +*** xref:condition/cmp.adoc[Comparisons] +*** xref:condition/operands.adoc[Operands] +*** xref:condition/return_code.adoc[The Return Code Operator] +*** xref:condition/eq.adoc[The '==' Operator] +*** xref:condition/and.adoc[The '&&' Operator] +*** xref:condition/or.adoc[The '||' Operator] +*** xref:condition/not.adoc[The '!' Operator] +*** xref:condition/para.adoc[The '( )' Operator] +*** xref:condition/regex.adoc[Regular Expressions] + +** xref:xlat/index.adoc[String Expansion] +*** xref:xlat/alternation.adoc[Alternation Syntax] +*** xref:xlat/builtin.adoc[Built-in Expansions] +*** xref:xlat/character.adoc[Single Letter Expansions] +*** xref:xlat/attribute.adoc[Attribute References] +*** xref:xlat/module.adoc[Module References] diff --git a/doc/antora/modules/unlang/pages/attr.adoc b/doc/antora/modules/unlang/pages/attr.adoc new file mode 100644 index 0000000..70afce4 --- /dev/null +++ b/doc/antora/modules/unlang/pages/attr.adoc @@ -0,0 +1,77 @@ += &Attribute References + +.Syntax +[source,unlang] +---- +[&]Attribute-Name +---- + +The `&Attribute-Name` operator returns a reference to the named +attribute. + +When used as an existence check in a condition, the condition +evaluates to `true` if the attribute exists. Otherwise, the condition +evaluates to `false`. + +When used elsewhere, such as in xref:switch.adoc[switch], it returns +the value of the named attribute. + +.Examples +[source,unlang] +---- +&User-Name +&NAS-IP-Address +---- + +== Lists + +The attribute reference can also be qualified with a +xref:list.adoc[list] reference. When no list is given, the server +looks in the input packet list for the named attribute. + +.Examples + +[source,unlang] +---- +&request:User-Name +&reply:NAS-IP-Address +---- + +== Array References + +.Syntax +[source,unlang] +---- +&Attribute-Name[<integer>] +---- + +When an attribute appears multiple times in a list, this syntax allows +you to address the attributes as if they were array entries. The +`<integer>` value defines which attribute to address. The `[0]` value +refers to the first attributes, `[1]` refers to the second attribute, +etc. + +.Examples +[source,unlang] +---- +&EAP-Message[1] +&reply:NAS-IP-Address[2] +---- + +== Removing ambuguity from the configuration files + +The server does not use the `&` character to distinguish attribute names +from other strings. + +Without the `&`, it is more difficult to parse the configuration file +clearly. You could interpret a string as `hello-there` +either as a literal string "hello-there", or as a reference to an +attribute named `hello-there`. + +Adding the leading `&` character means that attribute references are +now easily distinguishable from literal strings. The use of the leading +`&` character is highly recommended. + + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/break.adoc b/doc/antora/modules/unlang/pages/break.adoc new file mode 100644 index 0000000..01783ea --- /dev/null +++ b/doc/antora/modules/unlang/pages/break.adoc @@ -0,0 +1,28 @@ += The break statement + +.Syntax +[source,unlang] +---- +break +---- + +The `break` statement is used to exit an enclosing +xref:foreach.adoc[foreach] loop. The `break` statement only be used +inside of a xref:foreach.adoc[foreach] loop. + +.Example +[source,unlang] +---- +foreach &Class { + if (&Foreach-Variable-0 == 0xabcdef) { + break + } + + update reply { + Reply-Message += "Contains %{Foreach-Variable-0}" + } +} +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/case.adoc b/doc/antora/modules/unlang/pages/case.adoc new file mode 100644 index 0000000..ba2b5fe --- /dev/null +++ b/doc/antora/modules/unlang/pages/case.adoc @@ -0,0 +1,44 @@ += The case Statement + +.Syntax +[source,unlang] +---- +case [ <match> ] { + [ statements ] +} +---- + +The `case` statement is used to match data inside of a +xref:switch.adoc[switch] statement. The `case` statement cannot be used +outside of a xref:switch.adoc[switch] statement. + + +The `<match>` text can be an attribute reference such as `&User-Name`, +or it can be a xref:type/string/index.adoc[string]. If the match +text is a dynamically expanded string, then the match is performed on +the output of the string expansion. + +If no `<match>` text is given, it means that the `case` statement is +the "default" and will match all which is not matched by another +`case` statement inside of the same xref:switch.adoc[switch]. + +.Example +[source,unlang] +---- +switch &User-Name { + case "bob" { + reject + } + + case &Filter-Id { + reject + } + + case { + ok + } +} +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/condition/and.adoc b/doc/antora/modules/unlang/pages/condition/and.adoc new file mode 100644 index 0000000..50b3deb --- /dev/null +++ b/doc/antora/modules/unlang/pages/condition/and.adoc @@ -0,0 +1,21 @@ += The && Operator + +.Syntax +[source,unlang] +---- +(condition-1 && condition-2) +---- + +The `&&` operator performs a short-circuit "and" evaluation of the +two conditions. This operator evaluates _condition-1_ and returns +`false` if _condition-1_ returns `false`. Only if _condition-1_ +returns `true` is _condition-2_ evaluated and its result returned. + +.Examples +[source,unlang] +---- +if (&User-Name && &EAP-Message) { ... +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/condition/cmp.adoc b/doc/antora/modules/unlang/pages/condition/cmp.adoc new file mode 100644 index 0000000..4138b86 --- /dev/null +++ b/doc/antora/modules/unlang/pages/condition/cmp.adoc @@ -0,0 +1,104 @@ += Comparisons + +.Syntax +[source,unlang] +---- +lhs OP rhs +---- + +The most common use-case for conditions is to perform comparisons. +The `lhs` and `rhs` of a conditional comparison can be +xref:attr.adoc[&Attribute-Name] or xref:type/index.adoc[data]. The +the `OP` is an operator, commonly `==` or `\<=`. It is used to +control how the two other portions of the condition are compared. + +== The Comparison Operators + +The comparison operators are given below. + +[options="header"] +|===== +| Operator | Description +| < | less than +| \<= | less than or equals +| == | equals +| != | not equals +| >= | greater than or equals +| > | greater than +| xref:condition/regex.adoc[=~] | regular expression matches +| xref:condition/regex.adoc[!~] | regular expression does not match +|===== + +The comparison operators perform _type-specific_ comparisons. The +only exceptions are the xref:condition/regex.adoc[regular expression] operators, +which interpret the `lhs` as a printable string, and the `rhs` as a +regular expression. + +== IP Address Comparisons + +The type-specific comparisons operate as expected for most data types. +The only exception is data types that are IP addresses or IP address +prefixes. For those data types, the comparisons are done via the +following rules: + +* Any unqualified IP address is assumed to have a /32 prefix (IPv4) + or a /128 prefix (IPv6). + +* If the prefixes of the left and right sides are equal, then the comparisons + are performed on the IP address portion. + +* If the prefixes of the left and right sides are not equal, then the + comparisons are performed as _set membership checks_. + +The syntax allows conditions such as `192.0.2.1 < 192.0.2/24`. This +condition will return `true`, as the IP address `192.0.2.1' is within +the network `192.0.2/24`. + +== Casting + +In some situations, it is useful to force the left side to be +interpreted as a particular data type. + +[NOTE] +The data types used by the cast *must* be a type taken from the RADIUS +dictionaries, e.g., `ipaddr`, `integer`, etc. These types are not the +same as the xref:type/index.adoc[data types] used in the +configuration files. + +.Syntax +[source,unlang] +---- +<cast>lhs OP rhs +---- + +The `cast` text can be any one of the standard RADIUS dictionary data +types, as with the following example: + +.Example +[source,unlang] +---- +<ipaddr>&Class == 127.0.0.1 +---- + +In this example, the `Class` attribute is treated as if it was an IPv4 +address and is compared to the address `127.0.0.1` + +Casting is most useful when the left side of a comparison is a +dynamically expanded string. The cast ensures that the comparison is +done in a type-safe manner, instead of performing a string comparison. + +.Example +[source,unlang] +---- +<integer>`/bin/echo 00` == 0 +---- + +In this example, the string output of the `echo` program is interpreted as an +integer. It is then compared to the right side via integer +comparisons. Since the integer `00` is equivalent to the integer `0`, +the comparison will match. If the comparison had been performed via +string equality checks, then the comparison would fail, because the +strings `00` and `0` are different. + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/condition/eq.adoc b/doc/antora/modules/unlang/pages/condition/eq.adoc new file mode 100644 index 0000000..d9e51f3 --- /dev/null +++ b/doc/antora/modules/unlang/pages/condition/eq.adoc @@ -0,0 +1,30 @@ += The == Operator + +.Syntax +`(data-1 == data-2)` + +The `==` operator compares the result of evaluating `data-1` and +`data-2`. As discussed in xref:type/index.adoc[Data Types], the `data-1` +field may be interpreted as a reference to an attribute. + +The `data-2` field is interpreted in a type-specific manner. For +example, if `data-1` refers to an attribute of type `ipaddr`, then +`data-2` is evaluated as an IP address. If `data-1` refers to an +attribute of type `integer`, then `data-2` is evaluated as an integer +or as a named enumeration defined by a `VALUE` statement in a +dictionary. Similarly, if `data-1` refers to an attribute of type +`date`, then `data-2` will be interpreted as a date string. + +If the resulting data evaluates to be the same, then the operator +returns `true`; otherwise, it returns `false`. + +.Example +[source,unlang] +---- +if (User-Name == "bob") { + ... +} +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/condition/index.adoc b/doc/antora/modules/unlang/pages/condition/index.adoc new file mode 100644 index 0000000..b9d9d5f --- /dev/null +++ b/doc/antora/modules/unlang/pages/condition/index.adoc @@ -0,0 +1,85 @@ += Conditional Expressions + +Conditions are evaluated when parsing xref:if.adoc[if] and +xref:elsif.adoc[elsif] statements. These conditions allow the server to +make complex decisions based on one of a number of possible criteria. + +.Syntax +[source,unlang] +---- +if ( condition ) { ... + +elsif ( condition ) { ... +---- + +Conditions are expressed using the following syntax: + +[options="header"] +|===== +| Syntax | Description +| xref:attr.adoc[&Attribute-Name] | Check for attribute existence. +| xref:condition/return_code.adoc[rcode] | Check return code of a previous module. +| xref:condition/operands.adoc[data] | Check value of data. +| xref:condition/cmp.adoc[lhs OP rhs] | Compare two kinds of data. +| xref:condition/para.adoc[( condition )] | Check sub-condition +| xref:condition/not.adoc[! condition] | Negate a conditional check +| xref:condition/and.adoc[( condition ) && ...] | Check a condition AND the next one +| xref:condition/or.adoc[( condition ) \|\| ...] | Check a condition OR the next one +|===== + + +.Examples +[source,unlang] +---- +if ( &User-Name == "bob" ) { + ... +} + +if ( &Framed-IP-Address == 127.0.0.1 ) { + ... +} + +if ( &Calling-Station-Id == "%{sql:SELECT ...}" ) { + ... +} +---- + +== Load-time Syntax Checks + +The server performs a number of checks when it loads the configuration +files. Unlike version 2, all of the conditions are syntax checked +when the server loads. This checking greatly aids in creating +configurations that are correct. Where the configuration is +incorrect, a descriptive error is produced. + +This error contains the filename and line number of the syntax error. +In addition, it will print out a portion of the line that caused the +error and will point to the exact character where the error was seen. +These descriptive messages mean that most errors are easy to find and fix. + +== Load-time Optimizations + +The server performs a number of optimizations when it loads the +configuration files. Conditions that have static values are +evaluated and replaced with the result of the conditional comparison. + +.Example +[source,unlang] +---- +if ( 0 == 1 ) { + ... +} +---- + +The condition `0 == 1` is static and will evaluate to `false`. Since +it evaluates to `false`, the configuration inside of the `if` +statement is ignored. Any modules referenced inside of the `if` +statement will not be loaded. + +This optimization is most useful for creating configurations that +selectively load (or not) certain policies. If the condition above +was used in version 2, then the configuration inside of the `if` statement +would be loaded, even though it would never be used. + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/condition/not.adoc b/doc/antora/modules/unlang/pages/condition/not.adoc new file mode 100644 index 0000000..bde038e --- /dev/null +++ b/doc/antora/modules/unlang/pages/condition/not.adoc @@ -0,0 +1,19 @@ += The ! Operator + +.Syntax +[source,unlang] +---- +! condition +---- + +The `!` operator negates the result of the following condition. It +returns `true` when _condition_ returns `false`. It returns `false` +when _condition_ returns `true`. + +.Examples + +`(! (foo == bar))` + +`! &User-Name` + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/condition/operands.adoc b/doc/antora/modules/unlang/pages/condition/operands.adoc new file mode 100644 index 0000000..4a2d00b --- /dev/null +++ b/doc/antora/modules/unlang/pages/condition/operands.adoc @@ -0,0 +1,37 @@ += Operands + +.Syntax +[source,unlang] +---- +string +integer +"double-quoted string" +'single-quoted string' +`back-quoted string` +---- + +Any text not matching xref:attr.adoc[&Attribute-Name] or +xref:condition/return_code.adoc[Return Code] is interpreted as a value for a +particular xref:type/index.adoc[data type]. + +Double-quoted strings and back-quoted strings are dynamically expanded +before the condition is evaluated. Single-quoted strings are static +literals and are not dynamically expanded. + +When used as an existence check, the condition evaluates to `true` if +the data is non-zero. Otherwise, the condition evaluates to `false`. + +For integer existence checks, `0` is `false`; all other values are `true`. + +For string existence checks, an empty string is `false`. All other +strings are `true`. + +All other data types are disallowed in existence checks. + +.Examples + +`"hello there"` + +`5` + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/condition/or.adoc b/doc/antora/modules/unlang/pages/condition/or.adoc new file mode 100644 index 0000000..80c2cb4 --- /dev/null +++ b/doc/antora/modules/unlang/pages/condition/or.adoc @@ -0,0 +1,21 @@ += The || Operator + +.Syntax +[source,unlang] +---- +(expression-1 || expression-2) +---- + +The `||` operator performs a short-circuit "or" evaluation of the two +expressions. This operator evaluates _condition-1_ and returns `true` +if _condition-1_ returns true. Only if _condition-1_ returns `false` +is _condition-2_ evaluated and its result returned. + +.Examples +[source,unlang] +---- +if (&User-Name || &NAS-IP-Address) { ... +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/condition/para.adoc b/doc/antora/modules/unlang/pages/condition/para.adoc new file mode 100644 index 0000000..bdb3f01 --- /dev/null +++ b/doc/antora/modules/unlang/pages/condition/para.adoc @@ -0,0 +1,19 @@ += The ( ) Operator + +.Syntax +[source,unlang] +---- +( condition ) +---- + +The `( )` operator returns the result of evaluating the given +`condition`. It is used to clarify policies or to explicitly define +conditional precedence. + +.Examples + +`(foo)` + +`(bar || (baz && dub))` + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/condition/regex.adoc b/doc/antora/modules/unlang/pages/condition/regex.adoc new file mode 100644 index 0000000..038faa6 --- /dev/null +++ b/doc/antora/modules/unlang/pages/condition/regex.adoc @@ -0,0 +1,180 @@ += Regular Expressions + +.Syntax +==== +[source,unlang] +---- +(<subject> =~ /<pattern>/) +(<subject> =~ /<pattern>/[imsux]) + +(<subject> !~ /<pattern>/) +(<subject> !~ /<pattern>/[imsux]) +---- +==== + +== Matching +The regular expression operators perform regular expression matching +on the data. The `<subject>` field can be an attribute reference or data, +as with the other xref:condition/cmp.adoc[comparison] operators. The `/<pattern>/` +field must be a valid regular expression. + +The `=~` operator evaluates to `true` when `data` matches the +`/<pattern>/`. Otherwise, it evaluates to `false`. + +The `!~` operator evaluates to `true` when `data` does not match the +`/<pattern>/`. Otherwise, it evaluates to `true`. + +The regular expression comparison is performed on the _string representation_ +of the left side of the comparison. That is, if the left side is an +xref:type/numb.adoc[integer], the regular expression will behave is if the +value `0` was the literal string `"0"`. Similarly, if the left side is an +xref:attr.adoc[&Attribute-Name], then the regular expression will behave is if +the attribute was printed to a string, and the match was performed on the +resulting string. + +.Checking if the `User-Name` attribute contains a realm of example.com +==== +[source,unlang] +---- +if (&User-Name =~ /@example\.com$/) { + ... +} +---- +==== + +== Dialects + +The syntax of the regular expression is defined by the regular +expression library available on the local system. + +FreeRADIUS currently supports: + +* link:https://www.pcre.org/original/doc/html/[libpcre] and +link:https://www.pcre.org/current/doc/html/[libpcre2] both of which +provide +link:https://en.wikipedia.org/wiki/Perl_Compatible_Regular_Expressions[Perl +Compatible Regular expressions]. +* Regex support provided by the local libc implementation, usually +link:http://en.wikipedia.org/wiki/Regular_expression#POSIX_basic_and_extended[ +Posix regular expressions]. + +[TIP] +==== +Use the output of `radiusd -Xxv` to determine which regular expression library is in use. + +.... +... +Debug : regex-pcre : no +Debug : regex-pcre2 : yes +Debug : regex-posix : no +Debug : regex-posix-extended : no +Debug : regex-binsafe : yes +... +Debug : pcre2 : 10.33 (2019-04-16) - retrieved at build time +.... +==== + +[WARNING] +==== +Depending on the regular expression library or libc implementation the server +was built against, the pattern matching function available may not be binary +safe (see `regex-binsafe` in the output of `radiusd -Xxv`). + +If a binary safe regex match function is not available, and a match is +attempted against a subject that contains one or more `NUL` ('\0') bytes, the +match will be aborted, any condition that uses the result will evaluate to false, +and a warning will be emitted. +==== + +== Flags + +The regular expression `/<pattern>/` may be followed by one or more flag +characters. Again, which flags are available depends on the regular expression +library the server was built with. Multiple flags may be specified per +`/pattern/`. + +.Flags and their uses + +[options="header"] +|===== +| Flag Character | Available with | Effect +| `i` | All | Enable case-insensitive matching. +| `m` | All | '^' and '$' match newlines within the subject. +| `s` | libpcre[2] | '.' matches anything, including newlines. +| `u` | libpcre[2] | Treats subjects as UTF8. Invalid UTF8 + sequences will result in the match failing. + |`x` | libpcre[2] | Allows comments in expressions by ignoring + whitespace, and text between '#' and the next + newline character. +|===== + +== Subcapture groups + +When the `=~` or `!~` operators are used, then parentheses in the regular +expression will sub capture groups, which contain part of the subject string. + +The special expansion `%{0}` expands to the portion of the subject that +matched. The expansions + +`%{1}`..`%{32}` expand to the contents of any subcapture groups. + +When using libpcre[2], named capture groups may also be accessed using the +built-in expansion + +`%{regex:<named capture group>}`. + +Please see the xref:xlat/builtin.adoc#_0_32[xlat documentation] for +more information on regular expression matching. + +.Extracting the 'user' portion of a realm qualified string +==== +[source,unlang] +---- +if (&User-Name =~ /^(.*)@example\.com$/) { + update reply { + Reply-Message := "Hello %{1}" + } +} +---- +==== + +== Pre-Compiled vs Runtime Compiled + +When the server starts any regular expressions comparisons it finds will be +pre-compiled, and if support is available, JIT'd (converted to machine code) +to ensure fast execution. + +If a pattern contains a xref:xlat/index.adoc[string expansion], the pattern +cannot be compiled on startup, and will be compiled at runtime each time the +expression is evaluated. The server will also turn off JITing for runtime +compiled expressions, as the overhead is greater than the time that would be +saved during evaluation. + +.A runtime compiled regular expression +==== +[source,unlang] +---- +if (&User-Name =~ /^@%{Tmp-String-0}$/) { + ... +} +---- +==== + +To ensure optimal performance you should limit the number of patterns +containing xref:xlat/index.adoc[string expansions], and if using PCRE, combine +multiple expressions operating on the same subject into a single expression +using the PCRE alternation '|' operator. + +.Using multiple string expansions and the PCRE alternation operator +==== +[source,unlang] +---- +if (&User-Name =~ /^@(%{Tmp-String-0}|%{Tmp-String-1})$/) { + ... +} +---- +==== + + +// Licenced under CC-by-NC 4.0. +// Copyright (C) 2020 Network RADIUS SAS. +// Copyright (C) 2019 Arran Cudbard-Bell <a.cudbardb@freeradius.org> +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/condition/return_codes.adoc b/doc/antora/modules/unlang/pages/condition/return_codes.adoc new file mode 100644 index 0000000..ebc49ed --- /dev/null +++ b/doc/antora/modules/unlang/pages/condition/return_codes.adoc @@ -0,0 +1,35 @@ += The return code Operator + +.Syntax +[source,unlang] +---- +rcode +---- + +The Unlang interpreter tracks the return code of any module, string expansion +or keyword that has been called. + +This return code can be checked in any condition. If the saved return code +matches the `code` given here, then the condition evaluates to `true`. +Otherwise, it evaluates to `false`. + +rcodes cannot be set in a condition. rcodes cannot be compared with anything else. + +The list of valid return codes is as follows: + +.Return Codes + +include::partial$rcode_table.adoc[] + +.Examples + +[source,unlang] +---- +sql +if (notfound) { + ... +} +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/default.adoc b/doc/antora/modules/unlang/pages/default.adoc new file mode 100644 index 0000000..3b298f6 --- /dev/null +++ b/doc/antora/modules/unlang/pages/default.adoc @@ -0,0 +1,47 @@ += The case Statement + +.Syntax +[source,unlang] +---- +case [ <match> ] { + [ statements ] +} +---- + +The `case` statement is used to match data inside of a +xref:switch.adoc[switch] statement. The `case` statement cannot be used +outside of a xref:switch.adoc[switch] statement. + + +The `<match>` text can be an attribute reference such as `&User-Name`, +or it can be a xref:type/string/index.adoc[string]. If the match +text is a dynamically expanded string, then the match is performed on +the output of the string expansion. + +The keyword `default` can be used to specify the default action to +take inside of a xref:switch.adoc[switch] statement. + +If no `<match>` text is given, it means that the `case` statement is +the "default" and will match all which is not matched by another +`case` statement inside of the same xref:switch.adoc[switch]. + +.Example +[source,unlang] +---- +switch &User-Name { + case "bob" { + reject + } + + case &Filter-Id { + reject + } + + default { + ok + } +} +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/else.adoc b/doc/antora/modules/unlang/pages/else.adoc new file mode 100644 index 0000000..a795d0e --- /dev/null +++ b/doc/antora/modules/unlang/pages/else.adoc @@ -0,0 +1,30 @@ += The else Statement + +.Syntax +[source,unlang] +---- +if (condition) { + [ statements ] +} +else { + [ statements ] +} +---- + +An xref:if.adoc[if] statement can have an `else` clause. If _condition_ +evaluates to `false`, the statements in the xref:if.adoc[if] subsection are skipped +and the statements within the `else` subsection are executed. + +.Example +[source,unlang] +---- +if (&User-Name == "bob") { + reject +} +else { + ok +} +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/elsif.adoc b/doc/antora/modules/unlang/pages/elsif.adoc new file mode 100644 index 0000000..ff5799c --- /dev/null +++ b/doc/antora/modules/unlang/pages/elsif.adoc @@ -0,0 +1,43 @@ += The elsif Statement + +.Syntax +[source,unlang] +---- +if (condition-1) { + [ statements-1 ] +} +elsif (condition-2) { + [ statements-2 ] +} +else { + [ statements-3 ] +} +---- + +An `elsif` statement is used to evaluate a subsequent +xref:condition/index.adoc[condition] after a preceding xref:if.adoc[if] statement +evaluates to `false`. In the example above, when _condition-1_ +evaluates to false, then _statements-1_ are skipped and _condition-2_ +is checked. When _condition-2_ evaluates true, then _statements-2_ +are executed. When _condition-2_ evaluates false, then +_statements-2_ are skipped and _statements-3_ are executed. + +As with xref:if.adoc[if], an `elsif` clause does not need to be followed by +an xref:else.adoc[else] statement. However, any xref:else.adoc[else] statement +must be the last statement in an `elsif` chain. An arbitrary number of +`elsif` statements can be chained together to create a series of +conditional checks and statements. + +.Example +[source,unlang] +---- +if (&User-Name == "bob") { + reject +} +elsif (&User-Name == "doug") { + ok +} +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/foreach.adoc b/doc/antora/modules/unlang/pages/foreach.adoc new file mode 100644 index 0000000..6ed3ddf --- /dev/null +++ b/doc/antora/modules/unlang/pages/foreach.adoc @@ -0,0 +1,40 @@ += The foreach Statement + +.Syntax +[source,unlang] +---- +foreach <attribute-reference> { + [ statements ] +} +---- + +The `foreach` statement loops over a set of attributes as given by +`<attribute-reference>`. The loop can be exited early by using the +xref:break.adoc[break] keyword. + +<attribute-reference>:: + +The xref:attr.adoc[attribute reference] which will will be looped +over. The reference can be to one attribute, to an array, a child, or +be a subset. + +Inside of the `foreach` block, the attribute that is being looped over +can be referenced as `Foreach-Variable-0`, through +`Foreach-Variable-9`. The last digit is the depth of the loop, +starting at "0". The loops can be nested up to eight (8) deep, though +this is not recommended. + +The attributes being looped over cannot be modified or deleted. + +.Example +[source,unlang] +---- +foreach &Class { + update reply { + Reply-Message += "Contains %{Foreach-Variable-0}" + } +} +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/group.adoc b/doc/antora/modules/unlang/pages/group.adoc new file mode 100644 index 0000000..98801fd --- /dev/null +++ b/doc/antora/modules/unlang/pages/group.adoc @@ -0,0 +1,39 @@ += The group Statement + +.Syntax +[source,unlang] +---- +group { + [ statements ] +} +---- + +The `group` statement collects a series of statements into a list. +The default processing sections of the server (`authorize`, +`accounting`, etc.) are also `group` statements. Those sections are +given different name for management reasons, but they behave +internally exactly like a `group`. + +[ statements ]:: The `unlang` commands which will be executed. + +All of the statements inside of the `group` are executed in sequence. +The `group` statement is not normally used, as the statements within +it can just be placed inside of the enclosing section. However, the +`group` statement is included in the `unlang` syntax for completeness. + +.Examples + +[source,unlang] +---- +group { + sql + ldap + file + if (updated) { + ... + } +} +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/if.adoc b/doc/antora/modules/unlang/pages/if.adoc new file mode 100644 index 0000000..ea549ef --- /dev/null +++ b/doc/antora/modules/unlang/pages/if.adoc @@ -0,0 +1,29 @@ += The if Statement + +.Syntax +[source,unlang] +---- +if (condition) { + [ statements ] +} +---- + +.Description +The `if` statement evaluates a xref:condition/index.adoc[condition]. When the +_condition_ evaluates to `true`, the statements within the subsection +are executed. When the _condition_ evaluates to `false`, those +statements are skipped. + +An `if` statement can optionally be followed by an xref:else.adoc[else] or +an xref:elsif.adoc[elsif] statement. + +.Example +[source,unlang] +---- +if (&User-Name == "bob") { + reject +} +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/index.adoc b/doc/antora/modules/unlang/pages/index.adoc new file mode 100644 index 0000000..fc812f8 --- /dev/null +++ b/doc/antora/modules/unlang/pages/index.adoc @@ -0,0 +1,162 @@ += Unlang Policy Language + +The server supports a simple processing language called "Unlang", +which is short for "un-language". The original intention of using an +"un-language" was to avoid creating yet another programming language. +Instead, the `unlang` syntax allows for simple _if / then / else_ +checks, and attribute editing. It does not support recursion, +subroutines, or more complex flow controls. + +Where more complicated functionality is required, we recommend using +the `lua`, `perl` or `python` modules. Those modules allow the insertion of +full-featured scripts at any point in the packet processing. + +NOTE: The documentation is this directory is _reference_ +documentation. That is, it describes the syntax of `unlang` keywords, +using minimal examples. The reference documentation does not, +however, describe _when_ to use the keywords, or _how_ to create +policies. Please see the xref:howto:index.adoc[howto] directory for +more in-depth "how to" guides. + +The documentation is organized so that each item is on its own page. +The page beings with a description of the item, followed by some text +explaining what the item does. The page concludes with one or more +examples of using the item in `unlang` policies. + +The `unlang` processing can be split into some high-level concepts. + +== Keywords + +xref:keywords.adoc[Keywords], which are the basic statements of the +language, e.g., xref:load-balance.adoc[load-balance], +xref:if.adoc[if], xref:else.adoc[else], etc. + +.Example +[source,unlang] +---- +load-balance { + sql1 + sql2 + sql3 +} +---- + +== Conditional Expressions + +xref:condition/index.adoc[Conditional expressions], which are used to check +if conditions evaluate to _true_ or _false_. Conditional expressions +can be used to control the flow of processing. + +.Example +[source,unlang] +---- +if ((&User-Name == "bob") && (&Calling-Station-Id == "00:01:03:04:05")) { + ... +} +---- + +== Update Statements + +xref:update.adoc[update] statements are used to edit attributes and +lists of attributes. + +Most request packets will result in reply packets that contain +additional information for the requestor. The `update` section allows +policies to add attributes to requests, replies, or to other places. + +.Example +[source,unlang] +---- +update reply { + &Session-Timeout := 3600 + &Framed-IP-Address := 192.0.2.4 +} +---- + +== String Expansions + +xref:xlat/index.adoc[String expansion] Using `%{...}` to perform dynamic +string expansions. (also known as xref:xlat/index.adoc[xlat]) + +String expansions are usually performed in order to get additional +information which is not immediately available to the policy. This +information can be taken from almost any source, including other +attributes, databases, and scripts. + +.Example +[source,unlang] +---- +update reply { + &Framed-IP-Address := "%{sql:SELECT static_ip from table WHERE user = '%{User-Name}'}" +} +---- + +== Data Types + +Each attribute used by the server has an associated +xref:type/index.adoc[data type]. The `unlang` interpreter enforces +restrictions on assignments, so that only valid data types can be +assigned to an attribute. Invalid assignments result in a run-time +error. + +.Example +[source,unlang] +---- +update reply { + &Framed-IP-Address := 192.0.2.4 + &Session-Timeout := 5 + &Reply-Message := "hello" +} +---- + +== Design Goals of Unlang + +The goal of `unlang` is to allow simple policies to be written with +minimal effort. Conditional checks can be performed by the policies, +which can then update the request or response attributes based on the +results of those checks. `unlang` can only be used in a processing +section, it cannot be used anywhere else, including in configuration +sections for a client or a module. The reason for this limitation is +that the language is intended to perform specific actions on requests +and responses. The client and module sections contain definitions for +a client or module; they do not define how a request is processed. + +`unlang` uses the same the basic syntax as the configuration files. +The syntax of the configuration file for lines, comments, sections, +sub-section, etc., all apply to `unlang`. + +Where `unlang` differs from the basic configuration file format is in +complexity and operation. The normal configuration files are +_declarative_ and they are _static_. That is, they declare variables +and values for those variables. Those values do not change when the +server is running. + +In contrast, `unlang` performs run-time processing of requests. +Conditional statements such as xref:if.adoc[if] are evaluated for every +packet that is received. Attribute editing statements such as +xref:update.adoc[update] can be used to edit attribute contents or lists +of attributes. + +.Example +[source,unlang] +---- +# First, the keyword 'if' + +# followed by condition which checks that the User-Name +# attribute has value "bob" + +if (&User-Name == "bob") { + # keyword "update" + + # followed by instructions to add the Reply-Message + # attribute to the "reply" list, with contents + # "Hello, bob" + + update reply { + Reply-Message := "Hello, bob" + } +} +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/keywords.adoc b/doc/antora/modules/unlang/pages/keywords.adoc new file mode 100644 index 0000000..e6411ea --- /dev/null +++ b/doc/antora/modules/unlang/pages/keywords.adoc @@ -0,0 +1,78 @@ += Keywords + +The following tables list the keywords used in `Unlang`. These keywords +implement the "flow control" of the policies. + +== Flow Control Keywords + +The _flow control_ keywords allow _if / then / else_ checks, simple +looping, etc. + +.Flow Control +[options="header"] +[cols="30%,70%"] +|===== +| Keyword | Description +| xref:break.adoc[break] | Exit early from a `foreach` loop. +| xref:case.adoc[case] | Match inside of a `switch`. +| xref:else.adoc[else] | Do something when an `if` does not match. +| xref:elsif.adoc[elsif] | Check for condition when a previous `if` does not match. +| xref:foreach.adoc[foreach] | Loop over a list of attributes. +| xref:if.adoc[if] | Check for a condition, and execute a sub-policy if it matches. +| xref:return.adoc[return] | Immediately stop processing a section. +| xref:switch.adoc[switch] | Check for multiple values. +|===== + +== Attribute Editing Keywords + +The _attribute editing_ keywords allow policies to add, delete, and +modify attributes in any list or packet. + +.Attribute Editing +[options="header"] +[cols="30%,70%"] +|===== +| Keyword | Description +| xref:update.adoc[update] | Add or filter attributes to a list +|===== + +== Grouping Keywords + +The _grouping_ keywords allow policies to be organized into groups, +including load-balancing. + +.Grouping +[options="header"] +[cols="30%,70%"] +|===== +| Keyword | Description +| xref:group.adoc[group] | Group a series of statements. +| xref:load-balance.adoc[load-balance] | Define a load balancing group without fail-over. +| xref:redundant.adoc[redundant] | Define a redundant group with fail-over. +| xref:redundant-load-balance.adoc[redundant-load-balance] | Define a redundant group with fail-over and load balancing. +|===== + +== Module Keywords + +The _module_ keywords refer pre-packaged libraries that implement +specific functionality, such as connecting to SQL, LDAP, etc. The +name used here is not the literal string `module`. Instead, it is the +name of an instance of a pre-packaged module such as `sql`, or `ldap`, or +`eap`. + +The documentation below describes how to reference modules. That is, +how to use `sql`, etc. in the policies. Please see +`raddb/mods-available/` for configuration examples for each module. + +.Modules +[options="header"] +[cols="30%,70%"] +|===== +| Keyword | Description +| xref:module.adoc[<module>] | Execute a named module, e.g., `sql`. +| xref:module_method.adoc[<module>.<method>] | Execute a particular method of a named module, e.g., `pap.authorize` +| xref:module_builtin.adoc[reject] | Built-in modules, e.g., `reject`, or `ok`, etc. +|===== + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/list.adoc b/doc/antora/modules/unlang/pages/list.adoc new file mode 100644 index 0000000..a55a54f --- /dev/null +++ b/doc/antora/modules/unlang/pages/list.adoc @@ -0,0 +1,72 @@ += Attribute Lists + +An attribute list contains a set of attributes. The allowed lists +are: + +`request`:: Attributes in the incoming request packet. + +`reply`:: Attributes in the outgoing reply packet. + +`control`:: Attributes in the internal "control" list that is +associated with the request. ++ +The `control` attributes are used to manage how the request is +processed. These attributes are never sent in any packet. + +`session-state`:: Attributes which are maintained across multi-packet +exchanges. + +`proxy-request`:: Attributes in the proxied request packet to a home server. + +`proxy-reply`:: Attributes in the reply packet from the home server. + +`coa`:: Attributes in a CoA-Request packet which is sent to a home server. + +`disconnect`:: Attributes in a Disconnect-Request packet which is sent to a home server. + +There must be a colon `:` after the list name and before the attribute name. +This syntax helps the server to distinguish between list names and attribute +names. + +With the exception of `session-state`, all of the above lists are +ephemeral. That is, they exist for one packet exchange, and only one +packet exchange. When a reply is sent for a request, the above lists +and all attributes are deleted. There is no way to reference an +attribute from a previous packet. We recommend using a database to +track complex state. + +The `coa` and `disconnect` lists can only be used when the server +receives an Access-Request or Accounting-Request. Use `request` and +`reply` instead of `coa` when the server receives a CoA-Request or +Disconnect-Request packet. + +Adding one or more attributes to either of the `coa` or `disconnect` +list causes server to originate a CoA-Request or Disconnect-Request +packet. That packet is sent when the current Access-Request or +Accounting-Request has been finished, and a reply sent to the NAS. +See `raddb/sites-available/originate-coa` for additional information. + +In some cases, requests are associated a multi-packet exchange. For +those situations, the `session-state` list is automatically saved when +a reply is sent, and it is automatically restored when the next packet +in sequence comes in. Once the packet exchange has been finished, the +`session-state` list is deleted. + +In some cases, there is a parent-child relationship between requests. +In those situations, it is possible for the policy rules in the child +to refer to attributes in the parent. This reference can be made by +prefixing the _<list>_ name with the `parent` qualifier. The key word +`outer` is also a synonym for `parent`. If there are multiple +parent-child relationships, the `parent` qualifier can be repeated. + +There is, however, no way for the parent to refer to the child. When +the child is running, the parent is suspended. Once the child +finishes, it is deleted, and is no longer accessible to the parent. + +.Examples +`&parent.request.User-Name` + +`&parent.reply.Reply-Message` + +`&parent.parent.session-state.Filter-Id` + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/load-balance.adoc b/doc/antora/modules/unlang/pages/load-balance.adoc new file mode 100644 index 0000000..d64b161 --- /dev/null +++ b/doc/antora/modules/unlang/pages/load-balance.adoc @@ -0,0 +1,32 @@ += The load-balance Statement + +.Syntax +[source,unlang] +---- +load-balance { + [ statements ] +} +---- + +The `load-balance` section is similar to the `redundant` section +except that only one module in the subsection is ever called. + +In general, the +xref:redundant-load-balance.adoc[redundant-load-balance] statement is +more useful than this one. + +[ statements ]:: One or more `unlang` commands. Only one of the +statements is executed. + +.Examples + +[source,unlang] +---- +load-balance &User-Name { + sql1 + sql2 +} +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/module.adoc b/doc/antora/modules/unlang/pages/module.adoc new file mode 100644 index 0000000..fd18f2f --- /dev/null +++ b/doc/antora/modules/unlang/pages/module.adoc @@ -0,0 +1,86 @@ += Modules + +.Syntax +[source,unlang] +---- +<module> +---- + +The `<module>` statement is a reference to the named module. Common +module names include `pap`, `chap`, `files`, `eap`, and `sql`. The +`modules { ... }` subsection of `radiusd.conf` contains all of the +valid modules. + +When processing reaches a named module, the pre-compiled module is +called. The module may succeed or fail and will return a status code +to the `unlang` interpreter detailing success or failure. + +.Example +[source,unlang] +---- +chap +sql +---- + +== Module Return Codes + +When a module is called, it returns one of the following codes to +the interpreter; the meaning of each code is detailed to the right of +the source, below: + +.Module Return Codes + +The below table describes the purpose of the rcodes that may be returned +by a module call. In this case the 'operation' referenced in the table is +the current module call. + +include::partial$rcode_table.adoc[] + +These return codes can be used in a subsequent +xref:condition/index.adoc[conditional expression] thus allowing policies to +perform different actions based on the behaviour of the modules. + +.Example +[source,unlang] +---- +sql +if (notfound) { + update reply { + Reply-Message = "We don't know who you are" + } + reject +} +---- + +== Module Return Code priority overrides + +In the case of modules, rcodes can be modified on a per-call basis. + +Module priority overrides are specified in a block inline with the module call. +The format of an override is `<rcode> = (0+|<rcode>|return)` - That is, +a number greater than or equal to 0, the priority of another rcode, or the special +priority `return` which causes the current section to immediately exit. + +[source, unlang] +---- +ldap { <1> + fail = 1 <2> + ok = handled <3> + reject = return <4> +} +---- + +<1> Call to the `ldap` module. +<2> Sets the priority of the `fail` rcode to be `1`. If the priority + of the rcode for the request is `0`, then the request request rcode + will be set to `fail` if the module returns `fail`. +<3> Sets the priority of the `ok` rcode to be whatever the default is for + `handled` in this section. As the default for `handled` is usually + `return`. If `ok` is returned, the current section will immediately + exit. +<4> Sets the priority of `reject` to be `return`. If the module returns + `reject`, the current section will immediately exit. + + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/module_builtin.adoc b/doc/antora/modules/unlang/pages/module_builtin.adoc new file mode 100644 index 0000000..f21a128 --- /dev/null +++ b/doc/antora/modules/unlang/pages/module_builtin.adoc @@ -0,0 +1,42 @@ += Built-in Modules + +In some cases, it is useful to reject a request immediately or perform another +action on it. The built-in modules can be used to perform these actions. These +modules are named for the return codes given in the xref:module.adoc[module] +section. + +In practice, these modules are implemented by the `always` module and +exist so that a success or failure can be forced during the processing +of a policy. + +The names and behaviours of these modules are given below: + +`fail`:: +Causes the request to be treated as if a database failure had +occurred. + +`noop`:: +Do nothing. This also serves as an instruction to the +configurable failover tracking that nothing was done in the current +section. + +`ok`:: +Instructs the server that the request was processed properly. This keyword can be used to over-ride earlier failures if the local +administrator determines that the failures are not catastrophic. + +`reject`:: +Causes the request to be immediately rejected. + +.Example +[source,unlang] +---- +if (!&User-Name) { + update reply { + Reply-Message := "We don't know who you are" + } + reject +} +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/module_method.adoc b/doc/antora/modules/unlang/pages/module_method.adoc new file mode 100644 index 0000000..98cd375 --- /dev/null +++ b/doc/antora/modules/unlang/pages/module_method.adoc @@ -0,0 +1,27 @@ += Module methods + +.Syntax +[source,unlang] +---- +<module>.<method> +---- + +This variant of xref:module.adoc[<module>] is used in one processing +section. It calls a module using the method of another processing +section. For example, it can be used to call a module's `authorize` +method while processing the `post-auth` section. + +The `<module>` portion must refer to an existing module; the +`<method>` portion must refer to processing method supported by that +module. Typically, the names of the processing method will be the +same as the processing sections. + +.Example +[source,unlang] +---- +sql.recv.Accounting-Request +files.recv.Access-Request +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/redundant-load-balance.adoc b/doc/antora/modules/unlang/pages/redundant-load-balance.adoc new file mode 100644 index 0000000..2322f72 --- /dev/null +++ b/doc/antora/modules/unlang/pages/redundant-load-balance.adoc @@ -0,0 +1,39 @@ += The redundant-load-balance Statement + +.Syntax +[source,unlang] +---- +redundant-load-balance { + [ statements ] +} +---- + +The `redundant-load-balance` section operates as a combination of the +xref:redundant.adoc[redundant] and xref:load-balance.adoc[load-balance] +sections. + +[ statements ]:: One or more `unlang` commands. ++ +If the selected statement succeeds, then the server stops processing +the `redundant-load-balance` section. If, however, that statement fails, +then the next statement in the list is chosen (wrapping around to the +top). This process continues until either one statement succeeds or all +of the statements have failed. ++ +All of the statements in the list should be modules, and of the same +type (e.g., `ldap` or `sql`). All of the statements in the list should +behave identically, otherwise different requests will be processed +through different modules and will give different results. + +.Example +[source,unlang] +---- +redundant-load-balance &User-Name { + sql1 + sql2 + sql3 +} +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/redundant.adoc b/doc/antora/modules/unlang/pages/redundant.adoc new file mode 100644 index 0000000..e837d1f --- /dev/null +++ b/doc/antora/modules/unlang/pages/redundant.adoc @@ -0,0 +1,42 @@ += The redundant Statement + +.Syntax +[source,unlang] +---- +redundant { + [ statements ] +} +---- + +The `redundant` section executes a series of statements in sequence. +As soon as one statement succeeds, the rest of the section is skipped. + +[ statements ]:: One or more `unlang` commands. Processing starts +from the first statement in the list. ++ +If the selected statement succeeds, then the server stops processing +the `redundant` section. If, however, that statement fails, then the +next statement in the list is chosen. This process continues until +either one statement succeeds or all of the statements have failed. ++ +All of the statements in the list should be modules, and of the same +type (e.g., `ldap` or `sql`). All of the statements in the list should +behave identically, otherwise different requests will be processed +through different modules and will give different results. + +In general, we recommend using the +xref:redundant-load-balance.adoc[redundant-load-balance] statement +instead of `redundant`. + +.Example +[source,unlang] +---- +redundant { + sql1 + sql2 + sql3 +} +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/return.adoc b/doc/antora/modules/unlang/pages/return.adoc new file mode 100644 index 0000000..aea1bc2 --- /dev/null +++ b/doc/antora/modules/unlang/pages/return.adoc @@ -0,0 +1,36 @@ += The return Statement + +.Syntax +[source,unlang] +---- +return +---- + +The `return` statement is used to exit a processing section such as +`authorize`. It behaves similarly to the +xref:break.adoc[break] statement, except that it is not limited to +being used inside of a xref:foreach.adoc[foreach] loop. + +The `return` statement is not strictly necessary. It is mainly used +to simplify the layout of `unlang` policies. If the `return` +statement did not exist, then every xref:if.adoc[if] statement might need +to have a matching xref:else.adoc[else] statement. + +The `return` statement will also exit a policy which is defined in the +`policy { ... } ` subsection. This behavior allows policies to be +treated as a function call. Any `return` inside of the policy section +will only return from that policy. The `return` will _not_ return +from the enclosing processing section which called the policy. + +.Example +[source,unlang] +---- +sql +if (&reply.Filter-Id == "hello") { + return +} +... +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/return_codes.adoc b/doc/antora/modules/unlang/pages/return_codes.adoc new file mode 100644 index 0000000..3b09c2d --- /dev/null +++ b/doc/antora/modules/unlang/pages/return_codes.adoc @@ -0,0 +1,17 @@ += Return codes + +Many operations in the server produce a return code (rcode). +The different rcodes give a course indication of whether a particular operation +(a module call, string expansion, or keyword) was successful. + +Unlike return values in other languages, FreeRADIUS' rcodes are are always taken +from a fixed compiled-in set. + +include::partial$rcode_table.adoc[] + +Return codes propagate through nested unlang sections based on their priority. +If a rcode returned by an operation has a higher priority than the current rcode +associated with the request, then the request rcode is overwritten. + +Return code priorities are assigned by the section the module call, expansion or +keyword was used in. diff --git a/doc/antora/modules/unlang/pages/switch.adoc b/doc/antora/modules/unlang/pages/switch.adoc new file mode 100644 index 0000000..deff703 --- /dev/null +++ b/doc/antora/modules/unlang/pages/switch.adoc @@ -0,0 +1,83 @@ += The switch Statement + +.Syntax +[source,unlang] +---- +switch <expansion> { + case <match-1> { + [ statements-1 ] + } + case <match-2> { + [ statements-2 ] + } + case { + [ statements-3 ] + } +} +---- + +A `switch` statement causes the server to evaluate _expansion_, which +can be an xref:attr.adoc[&Attribute-Name] or +xref:condition/operands.adoc[data]. The result is compared against _match-1_ +and _match-2_ to find a match. If no string matches, then the server +looks for the default xref:case.adoc[case] statement, which has no +associated match. + +The matching is done via equality. The `switch` statement is mostly +syntactic sugar and is used to simplify the visual form of the +configuration. It is mostly equivalent to the following use of +xref:if.adoc[if] statements: + +.Nearly equivalent syntax +[source,unlang] +---- +if (<expansion> == <match-1>) { + [ statements-1 ] +} +elsif (<expansion> == <match-2>) { + [ statements-2 ] +} +else { + [ statements-3 ] +} +---- + +The only difference between the two forms is that for a `switch` +statement, the _expansion_ is evaluated only once. For the equivalent +xref:if.adoc[if] statement, the _expansion_ is evaluated again for every +xref:if.adoc[if]. + +If a matching xref:case.adoc[case] is found, the statements within +that xref:case.adoc[case] are evaluated. If no matching +xref:case.adoc[case] is found, the `case` section with no "match" is +evaluated. If there is no such `case { ...}` subsection, then the +`switch` statement behaves as if the `case {...}` section was empty. + +//// +For compatibility with version 3, and empty `case` statement can also +be used instead of `default`. +//// + +The _match_ text for the xref:case.adoc[case] statement can be an +xref:attr.adoc[&Attribute-Name] or xref:type/index.adoc[data]. + +No statement other than xref:case.adoc[case] can appear in a `switch` +statement, and the xref:case.adoc[case] statement cannot appear outside of a +`switch` statement. + +.Example +[source,unlang] +---- +switch &User-Name { + case "bob" { + reject + } + + case { + ok + } +} +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/type/all_types.adoc b/doc/antora/modules/unlang/pages/type/all_types.adoc new file mode 100644 index 0000000..0bace01 --- /dev/null +++ b/doc/antora/modules/unlang/pages/type/all_types.adoc @@ -0,0 +1,80 @@ += List of Data Types + +The server support a wide range of data types, both in `unlang` and in +the dictionaries. This page outlines the names and functionality of +those data types. + +== Basic Data Types + +There are a number of "basic" data types. These data types are +fixed-size, and encapsulate simple concepts such as "integer" or "IP +address". + +Basic data types can be used in `unlang`, as they contain simple +values which can be compared, or assigned to one attribute. In most +cases, it is not necessary to know the name of the data type. It is +possible to write values in the format you expect, The server will do +"the right thing" when interpreting the values. + +.Basic Data Types +[options="header"] +[cols="15%,85%"] +|===== +| Data Type | Description +| bool | boolean +| date | calendar date +| ethernet | Ethernet address +| float32 | 32-bit floating point number +| float64 | 64-bit floating point number +| ifid | interface ID +| int8 | 8-bit signed integer +| int16 | 16-bit signed integer +| int32 | 32-bit signed integer +| int64 | 64-bit signed integer +| ipaddr | IPv4 address +| ipv6addr | IPv6 address +| ipv4prefix | IPv4 network with address and prefix length +| ipv6prefix | IPv6 network with address and prefix length +| octets | raw binary, printed as hex strings +| string | printable strings +| time_delta | difference between two calendar dates +| uint8 | 8-bit unsigned integer +| uint16 | 16-bit unsigned integer +| uint32 | 32-bit unsigned integer +| uint64 | 64-bit unsigned integer +|===== + +=== Structural Data Types + +The following data types are "structural", in that they form +parent-child relationships between attributes. These data types can +only be used in the dictionaries. They cannot be used in `unlang` +statements. + +.Structural Data Types +[options="header"] +[cols="15%,85%"] +|===== +| Data Type | Description +| struct | structure which contains fixed-width fields +| tlv | type-length-value which contains other attributes +| vsa | Encapsulation of vendor-specific attributes +|===== + +=== Protocol-Specific Data Types + +The following data types are used only in certain protocols. These +data types can be used only in the dictionaries. They cannot be used +in `unlang` statements. + +.Protocol Specific Data Types +[options="header"] +[cols="15%,15%,70%"] +|===== +| Data Type | Protocol | Description +| abinary | RADIUS | Ascend binary filters +| extended | RADIUS | attributes which "extend" the number space +|===== + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS diff --git a/doc/antora/modules/unlang/pages/type/double.adoc b/doc/antora/modules/unlang/pages/type/double.adoc new file mode 100644 index 0000000..6c3e708 --- /dev/null +++ b/doc/antora/modules/unlang/pages/type/double.adoc @@ -0,0 +1,39 @@ += Double-Quoted Strings + +.Syntax +`"string"` + +A double-quoted string is interpreted via the usual rules in +programming languages for double quoted strings. The double-quote +character can be placed in a string by escaping it with a backslash. +Carriage returns and line-feeds can also be used via the usual `\r` and +`\n` syntax. + +The main difference between the single and double quoted strings is +that the double quoted strings can be dynamically expanded. The syntax +`${...}` is used for parse-time expansion and `%{...}` is used for +run-time expansion. The difference between the two methods is that the +`${...}` form is expanded when the server loads the configuration +files and is valid anywhere in the configuration files. The `%{...}` +link:xlat.adoc[string expansion] form is valid only in conditional +expressions and attribute assignments. + +The output of the dynamic expansion can be interpreted as a string, +a number, or an IP address, depending on its context. + +Note that the interpretation of text _strongly_ depends on the +context. The text `"0000"` can be interpreted as a data type +"integer", having value zero, or a data type "string", having value +`"0000"`. In general when a particular piece of text is used, it is +used with the context of a known attribute. That attribute has a +link:data.adoc[data type], and the text will be interpreted as that +data type. + +.Examples + +`"word"` + +`"a string"` + +`"this has embedded\ncharacters"` + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/type/index.adoc b/doc/antora/modules/unlang/pages/type/index.adoc new file mode 100644 index 0000000..7d0d70f --- /dev/null +++ b/doc/antora/modules/unlang/pages/type/index.adoc @@ -0,0 +1,117 @@ += Data Types + +Unlang supports a number of data types. These data types are used in +conditional expressions or when assigning a value to an attribute. + +== Using Data Types + +The server support a wide range of data types, as given in the +xref:unlang/type/all_types.adoc[list of data types] page. The choice +of which data type applies is determined by the context in which that +data type is used. This context is usually taken from an attribute +which is being assigned a value. + +The `unlang` interpreter uses pre-defined attributes which are defined +in dictionaries. The dictionaries define both a name, and a data type +for the attributes. In the interpreter, then, attributes can be +assigned a value or compared to a value, without specifying the data +type. The interpreter knows how to parse the value by using the data +type assigned to the attribute. + +The result is that in most cases, it is not necessary to know the name +of the data types. It is possible to write values in the format you +expect, and he server will do "the right thing" when interpreting the +values. + +.Attributes with Different Data Types +[source,unlang] +---- +Framed-IP-Address = 192.0.2.1 +Framed-IPv6-Address = 2001:db8:: +Reply-Message = "This is a reply" +Port-Limit = 5 +Boolean = true +Octets-Thing = 0xabcdef0102030405 +MAC-Address = 00:01:02:03:04:05 +---- + +== Parsing Data Types + +The interpreter is flexible when parsing data types. So long as the +value can be parsed as the given data type without error, the value +will be accepted. + +For example, a particular attribute may be of data type `ipaddr` in +order to store IPv4 addresses. The interpreter will then accept the +following strings as valid IPv4 addresses: + +`192.168.0.2`:: xref:type/string/unquoted.adoc[Unquoted text], interpreted as the data type + +`'192.168.0.2'`:: xref:type/string/single.adoc[Single-quoted string], the contents of the string are parsed as the data type. ++ +The single-quoted string form is most useful when the data type +contains special characters that may otherwise confuse the parser. + +`"192.168.0.2"`:: xref:type/string/double.adoc[Double-quoted string]. ++ +The contents of the string are dynamically expanded as described in +the xref:unlang/xlat/index.adoc[dynamic expansion] page. The +resulting output is then interpreted as the given data type. + +`{backtick}/bin/echo 192.168.0.2{backtick}`:: xref:type/string/backticks.adoc[backtick-quoted string]. +Run a script, and parse the resulting string as the data type. + +Similar processing rules are applied when parsing assignments and +comparisons, for all attributes and data types. + +=== Casting Data Types + +In some cases, it is necessary to parse values which do not refer to +attributes. This situation usually occurs when two values need to be +compared, as in the following example: + +[source,unlang] +---- +if ("%{sql:SELECT ipaddress FROM table WHERE user=%{User-Name}}" == 192.0.2.1) } + .... +} +---- + +Since there is no attribute on either side of the `==` operator, the +interpreter has no way of knowing that the string `192.0.2.1` is an IP +address. There is unfortunately no way of automatically parsing +strings in order to determine the data type to use. Any such +automatic parsing would work most of the time, but it would have +error cases where the parsing was incorrect. + +The solution is to resolve these ambiguities by allowing the values to +be cast to a particular type. Casting a value to a type tells the +interpreter how that value should be parsed. Casting is done by +prefixing a value with the type name, surrounded by angle brackets; +`<...>`. + +.Syntax +---- +<...>value +---- + +We can add a cast to the above example, as follows: + +[source,unlang] +---- +if ("%{sql:SELECT ipaddress FROM table WHERE user=%{User-Name}}" == <ipaddr>192.0.2.1) } + .... +} +---- + +In this example, we prefix the IP address with the string `<ipaddr>`. +The interpreter then knows that the value `192.0.2.` should be +interpreted as the data type `ipaddr`, and not as the literal string +`"192.0.2."`. + +For a full list of data types which can be used in a cast, please see +the xref:unlang/type/all_types.adoc[list of data types] page, and the +"Basic Type Types" section. + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/type/ip.adoc b/doc/antora/modules/unlang/pages/type/ip.adoc new file mode 100644 index 0000000..fc25ae8 --- /dev/null +++ b/doc/antora/modules/unlang/pages/type/ip.adoc @@ -0,0 +1,15 @@ += IP Addresses + +.Examples + +`192.0.2.16` + +`::1` + +`example.com` + +Depending on the context, a "simple word", as above, may be +interpreted as an IPv4 or an IPv6 address. This interpretation is +usually done when the string is used in the context of an attribute, +or to compare two addresses or assign an address to an attribute. + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/type/numb.adoc b/doc/antora/modules/unlang/pages/type/numb.adoc new file mode 100644 index 0000000..284cf81 --- /dev/null +++ b/doc/antora/modules/unlang/pages/type/numb.adoc @@ -0,0 +1,11 @@ += Numbers + +.Examples + +`0` + +`563` + +Numbers are unsigned integers that are composed of decimal digits. + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/type/string/backticks.adoc b/doc/antora/modules/unlang/pages/type/string/backticks.adoc new file mode 100644 index 0000000..9372b4c --- /dev/null +++ b/doc/antora/modules/unlang/pages/type/string/backticks.adoc @@ -0,0 +1,38 @@ += Backtick-quoted string + +.Syntax +`{backtick}string{backtick}` + +The backtick operator is used to perform a run-time expansion +similar to what is done with the Unix shell. The contents of the string +are split into one or more sub-strings, based on intermediate +whitespace. Each substring is then expanded as described above for +double quoted strings. The resulting set of strings is used to execute a +program with the associated arguments. + +The output of the program is recorded, and the resulting data is +used in place of the input string value. Where the output is composed of +multiple lines, any carriage returns and line feeds are replaced by +spaces. + +For safety reasons, the full path to the executed program should be +given. In addition, the string is split into arguments _before_ the +substrings are dynamically expanded. This step is done both to allow +the substrings to contain spaces, and to prevent spaces in the +expanded substrings from affecting the number of command-line +arguments. + +For performance reasons, we recommend that the use of back-quoted +strings be kept to a minimum. Executing external programs is +relatively expensive, and executing a large number of programs for +every request can quickly use all of the CPU time in a server. If many +programs need to be executed, it is suggested that alternative ways to +achieve the same result be found. In some cases, using a real +programming language such as `lua`, `perl` or `python` may be better. + +.Examples + +`{backtick}/bin/echo hello{backtick}` + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/type/string/double.adoc b/doc/antora/modules/unlang/pages/type/string/double.adoc new file mode 100644 index 0000000..ea87bc5 --- /dev/null +++ b/doc/antora/modules/unlang/pages/type/string/double.adoc @@ -0,0 +1,68 @@ += Double Quoted Strings + +.Syntax +`"string"` + +A double-quoted string allows escape sequences and xref:xlat/index.adoc[dynamic +expansions]. As with xref:type/string/single.adoc[single-quoted strings], text +within double quotes can include spaces. + +The main difference between the single and double quoted strings is +that the double quoted strings can be dynamically expanded. The syntax +`${...}` is used for parse-time expansion and `%{...}` is used for +run-time expansion. The difference between the two methods is that the +`${...}` form is expanded when the server loads the configuration +files and is valid anywhere in the configuration files. The `%{...}` +xref:xlat/index.adoc[string expansion] form is valid only in conditional +expressions and attribute assignments. + +The output of the dynamic expansion can be interpreted as a string, +a number, or an IP address, depending on its context. + +Note that the interpretation of text _strongly_ depends on the +context. The text `"0000"` can be interpreted as a data type +"integer", having value zero, or a data type "string", having value +`"0000"`. In general when a particular piece of text is used, it is +used with the context of a known attribute. That attribute has a +xref:type/index.adoc[data type], and the text will be interpreted as that +data type. + +NOTE: Most values retrieved from external datastores will be treated implicitly +as double-quoted strings. + +== Escape sequences + +Escape sequences allow the inclusion of characters that may be difficult to +represent in datastores, or the FreeRADIUS configuration files. + +.Escape sequences and their descriptions +[options="header", cols="15%,85%"] +|===== +| Escape sequence | Character represented +| `\\` | Literal backslash (0x5c) +| `\r` | Carriage return (0x0d) +| `\n` | Line feed (0x0a) +| `\t` | Horizontal tab (0x09) +| `\"` | Double quote (0x22) +| `\x<hex><hex>` | A byte whose numerical value is given by `<hex><hex>` interpreted as a hexadecimal number. +| `\x<oct><oct><oct>` | A byte whose numerical value is given by `<oct><oct><oct>` interpreted as an octal number. +|===== + +.Examples + +`"word"` + +`"a string"' + +`"foo\"bar\""` + +`"this is a long string"` + +`"this has embedded\ncharacters"` + +`"attribute\tvalue\nusername\t%{User-Name}\nreply-message\t%{reply.Reply-Message}"` +`"The result of 'SELECT * FROM foo WHERE 1' is: %{sql:SELECT * FROM foo WHERE 1}"` + +// Licenced under CC-by-NC 4.0. +// Copyright (C) 2019 Arran Cudbard-Bell <a.cudbardb@freeradius.org> +// Copyright (C) 2019 The FreeRADIUS project. +// Copyright (C) 2020 Network RADIUS SAS. + + + + diff --git a/doc/antora/modules/unlang/pages/type/string/escaping.adoc b/doc/antora/modules/unlang/pages/type/string/escaping.adoc new file mode 100644 index 0000000..e63a498 --- /dev/null +++ b/doc/antora/modules/unlang/pages/type/string/escaping.adoc @@ -0,0 +1,14 @@ += Character Escaping + +The quotation characters in the above string data types can be +escaped by using the backslash, or `\,` character. The backslash +character itself can be created by using `\\`. Carriage returns and +line feeds can be created by using `\n` and `\r`. + +.Examples + +`"I say \"hello\" to you"` + +`"This is split\nacross two lines"` + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/type/string/single.adoc b/doc/antora/modules/unlang/pages/type/string/single.adoc new file mode 100644 index 0000000..fa2ac05 --- /dev/null +++ b/doc/antora/modules/unlang/pages/type/string/single.adoc @@ -0,0 +1,19 @@ += Single Quoted Strings + +.Syntax +`'string'` + +A single-quoted string is interpreted without any dynamic string +expansion. The quotes allow the string to contain spaces, among other +special characters. The single quote character can be placed in such a +string by escaping it with a backslash. + +.Examples + +`'hello'` + +`'foo bar`' + +`'foo\\'bar'` + +`'this is a long string'` + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/type/string/unquoted.adoc b/doc/antora/modules/unlang/pages/type/string/unquoted.adoc new file mode 100644 index 0000000..9dd6e55 --- /dev/null +++ b/doc/antora/modules/unlang/pages/type/string/unquoted.adoc @@ -0,0 +1,21 @@ += Unquoted Strings + +Where a series of characters cannot be parsed as a decimal number, +they are interpreted as a simple string composed of one word. This +word is delimited by spaces, or by other tokens, such as `)` in +conditional expressions. + +This unquoted text is interpreted as simple strings and are generally +equivalent to placing the string in single quotes. + +The interpretation of the text depends on the context, which is +usually defined by an attribute which has a xref:type/index.adoc[data type]. + +.Examples + +`Hello` + +`192.168.0.1` + +`00:01:02:03:04:05` + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/update.adoc b/doc/antora/modules/unlang/pages/update.adoc new file mode 100644 index 0000000..645f4d8 --- /dev/null +++ b/doc/antora/modules/unlang/pages/update.adoc @@ -0,0 +1,160 @@ += The update Statement + +.Syntax +[source,unlang] +---- +update [ <list> ] { + <server-attribute> <op> <value> + ... +} +---- + +The `update` statement adds attributes to, or edits the attributes in, +the named _<list>_. + +The `update` statement consists of the following syntax elements: + +<list>:: The attribute list which will be updated. The list is +usually `request`, `reply`, or `control`. ++ +If the _<list>_ qualifier is omitted, then each entry inside of the +`update` section *must* be prefixed with a list name. For example, +`&request.User-Name ...` + +<server-attribute>:: The server attribute which is assigned the +_<value>_. + +<op>:: The operator such as `=`, `:=`, etc. + +<value>:: The value which is assigned to the attribute. If the field +is a double-quoted string, it undergoes xref:xlat/index.adoc[string +expansion], and the resulting value is assigned to the attribute. + +The update process is atomic, in that either all of the attributes are +modified, or none of them are modified. If the `update` fails for any +reason, then all of the results are discarded, and the `update` does +not affect any server attributes. + +.Example +[source,unlang] +---- +update reply { + &Reply-Message := "Hello!" + &Framed-IP-Address := 192.0.2.4 +} +---- + +== Lists + +The _<list>_ field sets the attribute list that will be updated. If +the _<list>_ qualifier is omitted, then each entry inside of the +`update` section *must* be prefixed with a list name. For example, +`&request.User-Name ...` + +Please see the xref:list.adoc[list] page for valid list names. + +== Server Attributes + +The _<server-attribute>_ field is an attribute name, such as +`&Reply-Message`. The attribute name may also be prefixed with a +_<list>_ qualifier, which overrides the _<list>_ given at the start +of the `update` section. + +NOTE: In version 3, the leading `&` is optional but recommended. + +== Editing Operators + +The `<op>` field is used to define how the attribute is processed. +Different operators allow attributes to be added, deleted, or +replaced, as defined below. + +.Editing Operators +[options="header"] +[cols="10%,90%"] +|===== +| Operator | Description +| = | Add the attribute to the list, if and only if an attribute of +the same name is not already present in that list. +| := | Add the attribute to the list. If any attribute of the same +name is already present in that list, its value is replaced with the +value of the current attribute. +| += | Add the attribute to the tail of the list, even if attributes +of the same name are already present in the list. +| ^= | Add the attribute to the head of the list, even if attributes +of the same name are already present in the list. +| -= | Remove all attributes from the list that match _<value>_. +| !* | Delete all occurances of the attribute, no matter what the value. +|===== + +== Filtering Operators + +The following operators may also be used in addition to the ones +listed above. These operators use the _<server-attribute>_ and +_<value>_ fields to enforce limits on all attributes in the given +_<list>_, and to edit attributes which have a matching +_<server-attribute>_ name. All other attributes are ignored. + +.Filtering Operators +[options="header] +[cols="10%,90%"] +|===== +| Operator | Description +| == | Keep only the attributes in the list that match _<value>_ +| < | Keep only the attributes in the list that have values less than _<value>_. +| \<= | Keep only the attributes in the list that have values less than or equal to _<value>_. +| > | Keep only the attributes in the list that have values greater than _<value>_. +| >= | Keep only the attributes in the list that have values greater than or equal to _<value>_. +| =~ | Keep only the attributes in the list which match the regular expression given in _<value>_. +| !~ | Keep only the attributes in the list which do not match the regular expression given in _<value>_. +|===== + +The `==` operator is very different from the `=` operator listed +above. The `=` operator is used to add new attributes to the list, +while the `==` operator removes all attributes that do not match the +given value. + +The comparison operators `<`, `<=`, `>`, and `>=` have some additional +side effects. Any non-matching value is replaced by the _<value>_ +given here. If no attribute exists, it is created with the given +_<value>_. + +For IP addresses, the operators `>`, `>=`, `<`, and `\<=` check for +membership in a network. The _<value>_ field should then be a IP +network, given in `address/mask` format. + +.Example +[source,unlang] +---- +update reply { + &Session-timeout := 86400 +} +---- + +.Example +[source,unlang] +---- +update reply { + &Reply-Message += "Rejected: Also, realm does not end with ac.uk" +} +---- + +== Values + +The _<value>_ field is the value which is assigned to the +_<server-attribute>_. The interpretation of the _<value>_ field +depends on the data type of the contents. For example, if the string +`"192.0.2.1"` is assigned to an attribute of the `string` data type, +then the result is an ASCII string containing that value. However, if +the same string is assigned to an attribute of the `ipaddr` data type, +then the result is a 32-bit IPv4 address, with binary value `0xc0000201`. + +.Example +[source,unlang] +---- +update reply { + &Session-Timeout <= 3600 +} +---- + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/xlat/alternation.adoc b/doc/antora/modules/unlang/pages/xlat/alternation.adoc new file mode 100644 index 0000000..adb7604 --- /dev/null +++ b/doc/antora/modules/unlang/pages/xlat/alternation.adoc @@ -0,0 +1,24 @@ += Alternation Syntax + +Alternation syntax similar to that used in Unix shells may also be +used: + +`%{%{Foo}:-bar}` + +This code returns the value of `%{Foo}`, if it has a value. +Otherwise, it returns a literal string bar. + +`%{%{Foo}:-%{Bar}}` + +This code returns the value of `%{Foo}`, if it has a value. +Otherwise, it returns the expansion of `%{Bar}`. + +These conditional expansions can be nested to almost any depth, such +as with `%{%{One}:-%{%{Two}:-%{Three}}}`. + +.Examples +`%{%{Stripped-User-Name}:-%{User-Name}}` + +`%{%{Framed-IP-Address}:-<none>}` + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/xlat/attribute.adoc b/doc/antora/modules/unlang/pages/xlat/attribute.adoc new file mode 100644 index 0000000..a3ee29b --- /dev/null +++ b/doc/antora/modules/unlang/pages/xlat/attribute.adoc @@ -0,0 +1,54 @@ += Attribute References + +Attributes in a list may be referenced via one of the following two +syntaxes: + +`%{Attribute-Name}` + +`%{<list>:Attribute-Name}` + +The `<list>:` prefix is optional. If given, it must be a valid +reference to an xref:list.adoc[attribute list]. + +If the `<list>:` prefix is omitted, then the `request` list is +assumed. + +For EAP methods with tunneled authentication sessions (i.e. PEAP and +EAP-TTLS), the inner tunnel session can refer to a list for the outer +session by prefixing the list name with `outer.` ; for example, +`outer.request`. + +When a reference is encountered, the given list is examined for an +attribute of the given name. If found, the variable reference in the +string is replaced with the value of that attribute. Otherwise, the +reference is replacedd with an empty string. + +.Examples + +`%{User-Name}` + +`%{request.User-Name} # same as above` + +`%{reply.User-Name}` + +`%{outer.request.User-Name} # from inside of a TTLS/PEAP tunnel` + +Examples of using references inside of a string: + +`"Hello %{User-Name}"` + +`"You, %{User-Name} are not allowed to use %{NAS-IP-Address}"` + +== Additional Variations + +`%{Attribute-Name[#]}`:: +Returns an integer containing the number of named attributes + +`%{Attribute-Name[0]}`:: + +When an attribute appears multiple times in a list, this syntax allows +you to address the attributes as with array entries. `[0]` refers to +the first attributes, `[1]` refers to the second attribute, etc. + +`%{Attribute-Name[*]}`:: + +Returns a comma-separated string containing all values for the named +attributes. + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/xlat/builtin.adoc b/doc/antora/modules/unlang/pages/xlat/builtin.adoc new file mode 100644 index 0000000..f236a57 --- /dev/null +++ b/doc/antora/modules/unlang/pages/xlat/builtin.adoc @@ -0,0 +1,891 @@ += Built-In Expansions + +In addition to storing attribute references, the server has a number +of built-in expansions. These expansions act largely as functions +which operate on inputs, and produce an output. + + + +== Attribute Manipulation + +=== %{length: ... } + +The `length` expansion returns the size of the input as an integer. +When the input is a string, then the output is identical to the +`strlen` expansion. + +When the input is an attribute reference, the output is the size of +the attributes data as encoded "on the wire". + +.Return: _size_ + +.Determining the length of fixed and variable length attributes +==== +[source,unlang] +---- +update control { + &Tmp-String-0 := "Caipirinha" + &Framed-IP-Address := 192.0.2.1 +} + +update reply { + &Reply-Message := "The length of %{control:Tmp-String-0} is %{length:&control:Tmp-String-0}" + &Reply-Message += "The length of %{control:Framed-IP-Address} is %{length:&control:Framed-IP-Address}" +} +---- + +.Output +.... +The length of Caipirinha is 10 +The length of 192.168.0.2 is 4 +.... +==== + +`length` is built in to the server core. + + + +=== %{integer:<&ref>} + +Print the value of the attribute an integer. + +In normal operation, `integer` attributes are printed using the name +given by a `VALUE` statement in a dictionary. Similarly, date +attributes are printed as dates, i.e., "January 1 2010. + +The `integer` expansion applies only to attributes which can be +converted to an integer. For all other inputs, it returns `0`. + +A common usage is to find the difference between two dates. + +For example, if a request contains `Service-Type = Login-User`, the +expansion of `%{integer:&Service-Type}` will yield `1`, which is the +value associated with the `Login-User` name. Using +`%{integer:&Event-Timestamp}` will return the event timestamp as an +unsigned 32-bit number. + +.Return: _string_ + +.Determining the integer value of an enumerated attribute +==== +[source,unlang] +---- +update { + &control:Service-Type := Login-User +} +update reply { + &Reply-Message := "The value of Service-Type is %{integer:&control:Service-Type}" +} +---- + +.Output + +``` +The value of Service-Type is 1 +``` +==== + +`integer` is built in to the server core. + + + +=== %{rand:<number>} + +Generate random number from `0` to `<number>-1`. + +.Return: _uint64_ + +.Generating a random number between 0 and 511 +==== +[source,unlang] +---- +update reply { + &Reply-Message := "The random number is %{rand:512}" +} +---- + +.Output + +``` +The random number is 347 +``` +==== + +`rand` is provided by the `rlm_expr` module. + + + +=== %{tag:<attribute ref>} + +CAUTION: This expansion is deprecated and will likely be removed. + +Returns a list of tags for any attributes found using ``<attribute ref>``. + +.Return: _int8_ + +.Determining the tag value of the second instance of the `radius.Tunnel-Server-Endpoint` attribute +==== +[source,unlang] +---- +update request { + &Tunnel-Server-Endpoint := '192.0.1.1' + &Tunnel-Server-Endpoint:1 := '192.0.5.2' + &Tunnel-Server-Endpoint:1 += '192.0.3.8' + &Tunnel-Server-Endpoint:2 := '192.0.2.1' + &Tunnel-Server-Endpoint:2 += '192.0.3.4' +} + +update reply { + &Reply-Message := "The tag value of the second instance of Tunnel-Server-Enpoint is %{request:Tunnel-Server-Endpoint[1]}" +} +---- + +.Output + +``` +The tag value of the second instance of Tunnel-Server-Enpoint is 192.0.5.2 +``` +==== + +`tag` is built in to the server core. + + + +=== %{string:<data>} + +Convert input to a string (if possible). For _octets_ type attributes, this +means interpreting the data as a UTF8 string, and inserting octal escape +sequences where appropriate. + +For other types, this means printing the value in its _presentation_ format, +i.e. dotted quads for IPv4 addresses, link:https://en.wikipedia.org/wiki/ISO_8601[ISO 8601] +time for date types, enumeration values for attributes such as `radius.Service-Type` etc. + +.Return: _string_ + +.String interpolation using the raw octets value of Tmp-Octets-0, and the stringified version +==== +[source,unlang] +---- +update control { + &Tmp-Octets-0 := 0x7465737431 +} +update reply { + &Reply-Message := "The string value of %{control:Tmp-Octets-0} is %{string:%{control:Tmp-Octets-0}}" +} +---- +==== + +.Output + +``` +The string value of 0x7465737431 is test1 +``` + +`string` is built in to the server core. + + + +== Server Manipulation + +=== %{config:<key>} + +Refers to a variable in the configuration file. See the documentation +on configuration file references. + +.Return: _string_ + +.Example + +[source,unlang] +---- +"Server installed in %{config:prefix}" +"Module rlm_exec.shell_escape = %{config:modules.exec.shell_escape}" +---- + +.Output + +``` +Server installed in /opt/freeradius +Module rlm_exec.shell_escape = yes +``` + +`config` is built in to the server core. + + + +=== %{client:<key>} + +Refers to a variable that was defined in the client section for the +current client. See the sections `client { ... }` in `clients.conf`. + +.Return: _string_ + +.Example + +[source,unlang] +---- +"The client ipaddr is %{client:ipaddr}" +---- + +.Output + +``` +The client ipaddr is 192.168.5.9 +``` + +`client` is built in to the server core. + + + +=== %{debug:<level>} + +Dynamically change the debug level to something high, recording the old level. + +.Return: _string_ + +.Example + +[source,unlang] +---- +authorize { + if (&request:User-Name == "bob") { + "%{debug:4}" + } else { + "%{debug:0}" + } + ... +} +---- + +.Output (_extra informations only for that condition_) + +``` +... +(0) authorize { +(0) if (&request:User-Name == "bob") { +(0) EXPAND %{debug:4} +(0) --> 2 +(0) } # if (&request:User-Name == "bob") (...) +(0) filter_username { +(0) if (&State) { +(0) ... +(0) } +... +``` + +`debug` is built in to the server core. + + + +=== %{debug_attr:<list:[index]>} + +Print to debug output all instances of current attribute, or all attributes in a list. +expands to a zero-length string. + +.Return: _string_ + +.Example + +[source,unlang] +---- +authorize { + if (&request:User-Name == "bob") { + "%{debug_attr:request[*]}" + } + ... +} +---- + +.Output + +``` +... +(0) authorize { +(0) if (&request:User-Name == "bob") { +(0) Attributes matching "request[*]" +(0) &request:User-Name = bob +(0) &request:User-Password = hello +(0) &request:NAS-IP-Address = 127.0.1.1 +(0) &request:NAS-Port = 1 +(0) &request:Message-Authenticator = 0x9210ee447a9f4c522f5300eb8fc15e14 +(0) EXPAND %{debug_attr:request[*]} +(0) } # if (&request:User-Name == "bob") (...) +... +``` + +`debug_attr` is built in to the server core. + + + +== String manipulation + +=== %{lpad:<&ref> <val> <char>} + +Left-pad a string. + +.Return: _string_ + +.Example + +[source,unlang] +---- +update control { + &Tmp-String-0 := "123" +} +update reply { + &Reply-Message := "Maximum should be %{lpad:&control:Tmp-String-0 11 0}" +} +---- + +.Output + +``` +Maximum should be 00000000123 +``` + +`lpad` is provided by the `rlm_expr` module. + + + +=== %{rpad:<&ref> <val> <char>} + +Right-pad a string. + +.Return: _string_ + +.Example + +[source,unlang] +---- +update control { + &Tmp-String-0 := "123" +} +update reply { + &Reply-Message := "Maximum should be %{rpad:&control:Tmp-String-0 11 0}" +} +---- + +.Output + +``` +Maximum should be 12300000000 +``` + +`rpad` is provided by the `rlm_expr` module. + + + +=== %{pairs:<&list:[*]>} + +Serialize attributes as comma-delimited string. + +.Return: _string_ + +.Example + +[source,unlang] +---- +update { + &control:Tmp-String-0 := "This is a string" + &control:Tmp-String-0 += "This is another one" +} + +update reply { + &Reply-Message := "Serialize output: %{pairs:&control[*]}" +} +---- + +.Output + +``` +Serialize output: Tmp-String-0 = \"This is a string\"Tmp-String-0 = \"This is another one\" +``` + +`pairs` is provided by the `rlm_expr` module. + + + +=== %{randstr: ...} + +Get random string built from character classes. + +.Return: _string_ + +.Example + +[source,unlang] +---- +update reply { + &Reply-Message := "The random string output is %{randstr:aaaaaaaa}" +} +---- + +.Output + +``` +The random string output is 4Uq0gPyG +``` + +`randstr` is provided by the `rlm_expr` module. + + + +=== %{strlen: ... } + +Length of given string. + +.Return: _integer_ + +.Example + +[source,unlang] +---- +update control { + &Tmp-String-0 := "Caipirinha" +} +update reply { + &Reply-Message := "The length of %{control:Tmp-String-0} is %{strlen:&control:Tmp-String-0}" +} +---- + +.Output + +``` +The length of Caipirinha is 21 +``` + +`strlen` is built in to the server core. + + + +=== %{tolower: ... } + +Dynamically expands the string and returns the lowercase version of +it. This definition is only available in version 2.1.10 and later. + +.Return: _string_ + +.Example + +[source,unlang] +---- +update control { + &Tmp-String-0 := "CAIPIRINHA" +} +update reply { + &Reply-Message := "tolower of %{control:Tmp-String-0} is %{tolower:%{control:Tmp-String-0}}" +} +---- + +.Output + +``` +tolower of CAIPIRINHA is caipirinha +``` + +`tolower` is provided by the `rlm_expr` module. + + + +=== %{toupper: ... } + +Dynamically expands the string and returns the uppercase version of +it. This definition is only available in version 2.1.10 and later. + +.Return: _string_ + +.Example + +[source,unlang] +---- +update control { + &Tmp-String-0 := "caipirinha" +} +update reply { + &Reply-Message := "toupper of %{control:Tmp-String-0} is %{toupper:%{control:Tmp-String-0}}" +} +---- + +.Output + +``` +toupper of caipirinha is CAIPIRINHA +``` + +`toupper` is provided by the `rlm_expr` module. + + + +== String Conversion + +=== %{base64: ... } + +Encode a string using Base64. + +.Return: _string_ + +.Example + +[source,unlang] +---- +update control { + &Tmp-String-0 := "Caipirinha" +} +update reply { + &Reply-Message := "The base64 of %{control:Tmp-String-0} is %{base64:%{control:Tmp-String-0}}" +} +---- + +.Output + +``` +The base64 of foo is Q2FpcGlyaW5oYQ== +``` + +`base64` is provided by the `rlm_expr` module. + + + +=== %{base64tohex: ... } + +Decode a base64 string (e.g. previously encoded using `base64`) to +hex. + +.Return: _string_ + +.Example + +[source,unlang] +---- +update control { + &Tmp-String-0 := "Q2FpcGlyaW5oYQ==" +} +update reply { + &Reply-Message := "The base64tohex of %{control:Tmp-String-0} is %{base64tohex:%{control:Tmp-String-0}}" +} +---- + +.Output + +``` +The base64decode of Q2FpcGlyaW5oYQ== is 436169706972696e6861 +``` + +`base64tohex` is provided by the `rlm_expr` module. + + + +=== %{hex: ... } + +Convert to hex. + +.Return: _string_ + +.Example + +[source,unlang] +---- +update control { + &Tmp-String-0 := "12345" +} +update reply { + &Reply-Message := "The value of %{control:Tmp-String-0} in hex is %{hex:%{control:Tmp-String-0}}" +} +---- + +.Output + +``` +The value of 12345 in hex is 3132333435 +``` + +`hex` is built in to the server core. + + + +=== %{urlquote: ... } + +Quote URL special characters. + +.Return: _string_. + +.Example + +[source,unlang] +---- +update { + &control:Tmp-String-0 := "http://example.org/" +} +update reply { + &Reply-Message += "The urlquote of %{control:Tmp-String-0} is %{urlquote:%{control:Tmp-String-0}}" +} +---- + +.Output + +``` +The urlquote of http://example.org/ is http%3A%2F%2Fexample.org%2F +``` + +`urlquote` is provided by the `rlm_expr` module. + + + +=== %{urlunquote: ... } + +Unquote URL special characters. + +.Return: _string_. + +.Example + +[source,unlang] +---- +update { + &control:Tmp-String-0 := "http%%3A%%2F%%2Fexample.org%%2F" # Attention for the double %. +} +update reply { + &Reply-Message += "The urlunquote of %{control:Tmp-String-0} is %{urlunquote:%{control:Tmp-String-0}}" +} +---- + +.Output + +``` +The urlunquote of http%3A%2F%2Fexample.org%2F is http://example.org/ +``` + +`urlunquote` is provided by the `rlm_expr` module. + + + +== Hashing and Encryption + +=== %{hmacmd5:<shared_key> <string>} + +Generate `HMAC-MD5` of string. + +.Return: _octal_ + +.Example + +[source,unlang] +---- +update { + &control:Tmp-String-0 := "mykey" + &control:Tmp-String-1 := "Caipirinha" +} +update { + &control:Tmp-Octets-0 := "%{hmacmd5:%{control:Tmp-String-0} %{control:Tmp-String-1}}" +} + +update reply { + &Reply-Message := "The HMAC-MD5 of %{control:Tmp-String-1} in octets is %{control:Tmp-Octets-0}" + &Reply-Message += "The HMAC-MD5 of %{control:Tmp-String-1} in hex is %{hex:control:Tmp-Octets-0}" +} +---- + +.Output + +``` +The HMAC-MD5 of Caipirinha in octets is \317}\264@K\216\371\035\304\367\202,c\376\341\203 +The HMAC-MD5 of Caipirinha in hex is 636f6e74726f6c3a546d702d4f63746574732d30 +``` + +`hmacmd5` is provided by the `rlm_expr` module. + + + +=== %{hmacsha1:<shared_key> <string>} + +Generate `HMAC-SHA1` of string. + +.Return: _octal_ + +.Example + +[source,unlang] +---- +update { + &control:Tmp-String-0 := "mykey" + &control:Tmp-String-1 := "Caipirinha" +} +update { + &control:Tmp-Octets-0 := "%{hmacsha1:%{control:Tmp-String-0} %{control:Tmp-String-1}}" +} + +update reply { + &Reply-Message := "The HMAC-SHA1 of %{control:Tmp-String-1} in octets is %{control:Tmp-Octets-0}" + &Reply-Message += "The HMAC-SHA1 of %{control:Tmp-String-1} in hex is %{hex:control:Tmp-Octets-0}" +} +---- + +.Output + +``` +The HMAC-SHA1 of Caipirinha in octets is \311\007\212\234j\355\207\035\225\256\372ʙ>R\"\341\351O) +The HMAC-SHA1 of Caipirinha in hex is 636f6e74726f6c3a546d702d4f63746574732d30 +``` + +`hmacsha1` is provided by the `rlm_expr` module. + + + +=== %{md5: ... } + +Dynamically expands the string and performs an MD5 hash on it. The +result is binary data. + +.Return: _binary data_ + +.Example + +[source,unlang] +---- +update control { + &Tmp-String-0 := "Caipirinha" +} +update reply { + &Reply-Message := "md5 of %{control:Tmp-String-0} is octal=%{md5:%{control:Tmp-String-0}}" + &Reply-Message := "md5 of %{control:Tmp-String-0} is hex=%{hex:%{md5:%{control:Tmp-String-0}}}" +} +---- + +.Output + +``` +md5 of Caipirinha is octal=\024\204\013md||\230\243\3472\3703\330n\251 +md5 of Caipirinha is hex=14840b6d647c7c98a3e732f833d86ea9 +``` + +`md5` is provided by the `rlm_expr` module. + + + +== Miscellaneous Expansions + +=== +%{0}+..+%{32}+ + +`%{0}` expands to the portion of the subject that matched the last regular +expression evaluated. `%{1}`..`%{32}` expand to the contents of any capture +groups in the pattern. + +Every time a regular expression is evaluated, whether it matches or not, +the numbered capture group values will be cleared. + + + +=== +%{regex:<named capture group>}+ + +Return named subcapture value from the last regular expression evaluated. + +Results of named capture groups are available using the `%{regex:<named capture +group>}` expansion. They will also be accessible using the numbered expansions +described xref:builtin.adoc#_0_32[above]. + +Every time a regular expression is evaluated, whether it matches or not, +the named capture group values will be cleared. + +[NOTE] +==== +This expansion is only available if the server is built with libpcre or libpcre2. +Use the output of `radiusd -Xxv` to determine which regular expression library in use. + +.... +... +Debug : regex-pcre : no +Debug : regex-pcre2 : yes +Debug : regex-posix : no +Debug : regex-posix-extended : no +Debug : regex-binsafe : yes +... +Debug : pcre2 : 10.33 (2019-04-16) - retrieved at build time +.... +==== + +`regex` is built in to the server core. + + + +=== +%{nexttime:<time>}+ + +Calculate number of seconds until next n hour(`s`), day(`s`), week(`s`), year(`s`). + +.Return: _string_ + +.Example: + +With the current time at 16:18, `%{nexttime:1h}` will expand to `2520`. + +[source,unlang] +---- +update reply { + &Reply-Message := "You should wait for %{nexttime:1h}s" +} +---- + +.Output + +``` +You should wait for 2520s +``` + +`nexttime` is provided by the `rlm_expr` module. + + + +=== +%{Packet-Type}+ + +The packet type (`Access-Request`, etc.) + + + +=== +%{Packet-Src-IP-Address} and %{Packet-Src-IPv6-Address}+ + +The source IPv4 or IPv6 address of the packet. See also the expansions +`%{client:ipaddr}` and `%{client:ipv6addr}`. The two expansions +should be identical, unless `%{client:ipaddr}` contains a DNS hostname. + + + +=== +%{Packet-Dst-IP-Address} and %{Packet-Dst-IPv6-Address}+ + +The destination IPv4 or IPv6 address of the packet. See also the +expansions `%{listen:ipaddr}` and `%{listen:ipv6addr}`. If the socket +is listening on a "wildcard" address, then these two expansions will be +different, as follows: the `%{listen:ipaddr}` will be the wildcard +address and `%{Packet-DST-IP-Address}` will be the unicast address to +which the packet was sent. + + + +=== +%{Packet-Src-Port} and %{Packet-Dst-Port}+ + +The source/destination ports associated with the packet. + +.Return: _string_. + +.Example + +[source,unlang] +---- +update control { + &Tmp-String-0 := "user@example.com" +} + +if (&control:Tmp-String-0 =~ /^(?<login>(.*))@(?<domain>(.*))$/) { + update reply { + &Reply-Message := "The %{control:Tmp-String-0} { login=%{regex:login}, domain=%{regex:domain} }" + } +} +---- + +.Output + +``` +The user@example.com { login=user, domain=example.com } +``` + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/xlat/character.adoc b/doc/antora/modules/unlang/pages/xlat/character.adoc new file mode 100644 index 0000000..84a148c --- /dev/null +++ b/doc/antora/modules/unlang/pages/xlat/character.adoc @@ -0,0 +1,80 @@ += Single Letter Expansions + +The following are single letter expansions. These expansions do not +use the typical `%{...}` format. Instead, they are short-cuts for +simple, common cases. + +== Miscellaneous + +`%%`:: + +Returns `%`. + + +== Current Time + +`%c`:: + +The current Unix epoch time in seconds. This is an unsigned decimal number. +It should be used with time-based calculations. + +`%C`:: + +The microsecond component of the current epoch time. This is an unsigned +decimal number. It should be used with time-based calculations. + + +== Request Time + +`%l`:: + +The Unix timestamp of when the request was received. This is an unsigned +decimal number. It should be used with time-based calculations. + +`%Y`:: + +Four-digit year when the request was received. + +`%m`:: + +Numeric month when the request was received. + +`%d`:: + +Numeric day of the month when the request was received. + +`%H`:: + +Hour of the day when the request was received. + +`%G`:: + +Minute component of the time when the request was received. + +`%e`:: + +Second component of the time when the request was received. + +`%M`:: + +Microsecond component of the time when the request was received. + +`%D`:: + +Request date in the format `YYYYMMDD`. + +`%S`:: + +Request timestamp in SQL format, `YYYY-mmm-ddd HH:MM:SS`. + +`%t`:: + +Request timestamp in _ctime_ format, `Www Mmm dd HH:MM:SS YYYY`. + +`%T`:: + +Request timestamp in ISO format, `YYYY-mm-ddTHH:MM:SS.000`. + + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/xlat/index.adoc b/doc/antora/modules/unlang/pages/xlat/index.adoc new file mode 100644 index 0000000..b42f725 --- /dev/null +++ b/doc/antora/modules/unlang/pages/xlat/index.adoc @@ -0,0 +1,56 @@ += String Expansion + +String expansion is a feature that allows strings to dynamically +define their value at run time. For historical reasons, these string +expansions are called "xlats". + +String expansion is performed via the following syntax: + +`%{...}` + +Where the `%{` signals the start of a dynamic expansion, and `}` +signals the end of the dynamic expansion. The contents of the +expansion can be many things: + +.String Expansions +[options="header"] +|===== +| Keyword | Description +| xref:xlat/attribute.adoc[attributes] | Expand the value of a named attribute. +| xref:xlat/character.adoc[single character] | Single character expansions. +| xref:xlat/module.adoc[modules] | Pass a string to a module such as `sql`. +| xref:xlat/alternation.adoc[condition] | Conditionally expand a string. +| xref:xlat/builtin.adoc[built-in expansions] | Such as string length, tolower, etc... +|===== + +This feature is used to create policies which refer to concepts rather +than to specific values. For example, a policy can be created that +refers to the User-Name in a request, via: + +`%{User-Name}` + +This string expansion is done only for double-quoted strings and for +the back-tick operator. + +== Caveats + +Unlike other languages, there is no way to define new variables. All +of the string expansions must refer to attributes that already exist, +or to modules that will return a string value. + +== Character Escaping + +Some characters need to be escaped within a dynamically expanded +string `%{...}`. The `%` character is used for variable expansion, so a +literal `%` character can be created by using `%%`. + +Other than within a dynamically expanded string, very little +character escaping is needed. The rules of the enclosing string context +determine whether or not a space or " character needs to be escaped. + +.Example + +`Reply-Message := "%{User-Name} with a literal %%` + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/pages/xlat/module.adoc b/doc/antora/modules/unlang/pages/xlat/module.adoc new file mode 100644 index 0000000..3ce4322 --- /dev/null +++ b/doc/antora/modules/unlang/pages/xlat/module.adoc @@ -0,0 +1,18 @@ += Module References + +Individual modules may be referenced via the following syntax: + +`%{module:string}` + +These references are allowed only by a small number of modules that +usually perform database lookups. The module name is the actual name of +the module, as described earlier. The string portion is specific to each +module and is not documented here. It is, however, usually dynamically +expanded to allow for additional flexibility. + +.Examples + +`%{sql:SELECT name FROM mytable WHERE username = %{User-Name}}` + +// Copyright (C) 2020 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +// Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/doc/antora/modules/unlang/partials/rcode_table.adoc b/doc/antora/modules/unlang/partials/rcode_table.adoc new file mode 100644 index 0000000..e114e74 --- /dev/null +++ b/doc/antora/modules/unlang/partials/rcode_table.adoc @@ -0,0 +1,39 @@ +[options="header"] +[cols="15%,85%"] +|===== +| Return code | Description +| `fail` | The operation failed. Usually as a result of an + external dependency like a database being unavailable + or an internal error. +| `handled` | The request has been "handled", no further policies + in the current section should be called, and the section + should immediately exit. +| `invalid` | The request, or operation, was invalid. In the case of + requests this usually indicates absent or malformed + attribute values. +| `noop` | The operation did nothing. +| `notfound` | A 'lookup' operation returned no results. +| `ok` | Operation completed successfully but did not change any + attributes in the request. +| `reject` | The operation indicates the current request should be + 'rejected'. What this actually means is different from + protocol to protocol. It usually means that access to + the requested resource should be denied, or that the + current request should be NAKd. Usually returned when + provided credentials were invalid. +| `updated` | The operation completed successfully and updated one + or more attributes in the request. +| `disallow` | Access to a particular resource is + denied. This is similar to `reject` but is the result + of an authorizational check failing, as opposed to + credentials being incorrect. +| `yield` | Returned by an operation when execution of a request should + be suspended. +|===== + +[NOTE] +==== +In versions ≤ v3.0.x the `disallow` rcode was called `userlock`. `disallow` and +`userlock` have an identical meaning. `disallow` will be returned in any +instance where `userlock` was returned in v3.0.x +==== |