Read and write data on external systems

backends

Backends are the way execution pipelines can read and write data on external systems.

Following backend types are supported:

The full backend configuration syntax is backend specific, but in general a backend configuration include the backend name, one or more queries and a way to define "bindings" for input arguments and "mappings" that map the query results back to the execution context.

Here is an example of a MySQL backend configuration with a single query:

backends {
    mysql "MY_DB" {
        server "MYSQL" {
            host "example.test";
            port 3306;
            database "test_db";
            username "testuser";
        }

        query "FIND_USER" {
            # SQL statement
            statement "SELECT username, password FROM USERIDS WHERE username = ?";

            # Query argument binding in order
            bindings {
                aaa.identity;
            }

            # Result value mapping
            mapping {
                # the `username` and `password` variables come from the SQL
                # query result columns and are mapped to the execution context
                # variables `user.username` and `user.password`
                user.username = username;
                user.password = password;
            }
        }
    }
}

Calling backends

Backends are called with the backend action

backend {
    name "EXAMPLE_FILE_BACKEND";
    query "FIND_USER";
}

Some backends may expose different queries. The way of defining backend queries is backend specific. For example, SQL backends use the query block to define SQL queries.

If the backend does not support multiple queries, the query name may be omitted.

Using variables as query arguments

Backend bindings reference values from the execution context. In addition to built-in context variables like aaa.identity, you can use vars.* variables to pass custom values to backend queries. Set the variable with modify before calling the backend, and reference it in the bindings block. Mappings can also set vars.* variables to store query results for use later in the pipeline.

This is useful when the query arguments do not come directly from the request but are derived, extracted, or hardcoded earlier in the pipeline. In the example below, the backend definition uses only vars.* bindings, which fully decouples it from any specific protocol. The same backend and query can then be reused across RADIUS, HTTP, and other protocol contexts without modification.

backends {
    sqlite "MY_DB" {
        query "UPSERT_USER" {
            statement """
                INSERT INTO users (username, password) VALUES (?, ?)
                ON CONFLICT(username) DO UPDATE SET password = excluded.password
                RETURNING id
            """;

            bindings {
                vars.username;
                vars.password;
            }

            mapping {
                vars.user_id = id;
            }
        }
    }
}

In the execution pipeline, set the variables before invoking the backend:

@execute {
    modify {
        vars.username = http.path.2;
        vars.password = http.body.password;
    }

    backend {
        name "MY_DB";
        query "UPSERT_USER";
    }

    # The vars.user_id variable is now available for use in the rest of the pipeline
}