diff --git a/src/pages/ipa/resources/services.mdx b/src/pages/ipa/resources/services.mdx index dfe7ede90..ad7a91677 100644 --- a/src/pages/ipa/resources/services.mdx +++ b/src/pages/ipa/resources/services.mdx @@ -537,6 +537,7 @@ echo $response; "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -635,6 +636,7 @@ echo $response; "target_id": "string", "target_type": "string", "path": "string", + "access_action": "string", "protocol": "string", "host": "string", "port": "integer", @@ -772,6 +774,11 @@ echo $response; URL path prefix for this target (HTTP only) + + + + Access action applied after this target's HTTP path prefix is selected. "inherit" uses the service authentication configuration, "bypass" skips service authentication, and "block" denies access. HTTP services only. + @@ -1056,6 +1063,7 @@ curl -X POST https://api.netbird.io/api/reverse-proxies/services \ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -1145,6 +1153,7 @@ let data = JSON.stringify({ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -1256,6 +1265,7 @@ payload = json.dumps({ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -1367,6 +1377,7 @@ func main() { "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -1497,6 +1508,7 @@ request.body = JSON.dump({ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -1590,6 +1602,7 @@ RequestBody body = RequestBody.create(mediaType, '{ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -1699,6 +1712,7 @@ curl_setopt_array($curl, array( "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -1805,6 +1819,7 @@ echo $response; "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -1901,6 +1916,7 @@ echo $response; "target_id": "string", "target_type": "string", "path": "string", + "access_action": "string", "protocol": "string", "host": "string", "port": "integer", @@ -2169,6 +2185,7 @@ echo $response; "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -2265,6 +2282,7 @@ echo $response; "target_id": "string", "target_type": "string", "path": "string", + "access_action": "string", "protocol": "string", "host": "string", "port": "integer", @@ -2409,6 +2427,11 @@ echo $response; URL path prefix for this target (HTTP only) + + + + Access action applied after this target's HTTP path prefix is selected. "inherit" uses the service authentication configuration, "bypass" skips service authentication, and "block" denies access. HTTP services only. + @@ -2693,6 +2716,7 @@ curl -X PUT https://api.netbird.io/api/reverse-proxies/services/{serviceId} \ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -2782,6 +2806,7 @@ let data = JSON.stringify({ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -2893,6 +2918,7 @@ payload = json.dumps({ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -3004,6 +3030,7 @@ func main() { "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -3134,6 +3161,7 @@ request.body = JSON.dump({ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -3227,6 +3255,7 @@ RequestBody body = RequestBody.create(mediaType, '{ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -3336,6 +3365,7 @@ curl_setopt_array($curl, array( "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -3442,6 +3472,7 @@ echo $response; "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -3538,6 +3569,7 @@ echo $response; "target_id": "string", "target_type": "string", "path": "string", + "access_action": "string", "protocol": "string", "host": "string", "port": "integer", @@ -4210,6 +4242,7 @@ echo $response; "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -4306,6 +4339,7 @@ echo $response; "target_id": "string", "target_type": "string", "path": "string", + "access_action": "string", "protocol": "string", "host": "string", "port": "integer", diff --git a/src/pages/manage/reverse-proxy/access-logs.mdx b/src/pages/manage/reverse-proxy/access-logs.mdx index bd412614f..44a3315c7 100644 --- a/src/pages/manage/reverse-proxy/access-logs.mdx +++ b/src/pages/manage/reverse-proxy/access-logs.mdx @@ -37,9 +37,10 @@ Every log entry (HTTP and L4) shares a common set of fields. Some fields are onl | **Bytes Downloaded** | Bytes sent from backend to client | Yes | Yes | | **Source IP** | The client's IP address | Yes | Yes | | **Location** | Country, city, and subdivision based on source IP geolocation | Yes | Yes | -| **Auth Method** | Raw `auth_method_used` value: `oidc` (shown as SSO in the dashboard), `password`, `pin`, or `header`. For denied requests, carries the restriction code instead (e.g. `ip_restricted`, `crowdsec_ban`). Omitted from the API response when empty | Yes | Restriction code on denials | +| **Auth Method** | Raw `auth_method_used` value: `oidc` (shown as SSO in the dashboard), `password`, `pin`, `header`, or a target access action such as `path_bypass` or `path_block`. For denied requests, carries the restriction code instead (e.g. `ip_restricted`, `crowdsec_ban`). Omitted from the API response when empty | Yes | Restriction code on denials | | **User** | The authenticated user's ID, set when `oidc` authentication was used. Omitted from the API response when empty | Yes | N/A | | **Reason** | Exactly one of two values: `Authentication failed` when authentication or an access restriction rejected the request, or `Request failed` when an authenticated request returned `4xx`/`5xx`. Set for HTTP entries only and omitted from the API response when empty. Never a specific denial code: see the note under [Deny reasons](#deny-reasons) | Yes | Omitted | +| **Metadata** | Additional decision details. Target access actions record `access_action` as `bypass` or `block`; CrowdSec records its mode and verdict here | Yes | Yes | ## Understanding log entries @@ -47,8 +48,8 @@ Every log entry (HTTP and L4) shares a common set of fields. Some fields are onl HTTP log entries fall into three categories based on the status code: -- **Allowed requests**: successful requests show a `2xx` status code along with the authentication method used to access the service. -- **Denied requests**: failed authentication or access restriction blocks show `401` or `403` status codes with `reason` set to `Authentication failed`. The specific cause (invalid password, missing SSO session, IP restricted, country restricted, CrowdSec verdict) is carried in `auth_method_used`, not in `reason`. +- **Allowed requests**: successful requests show a `2xx` status code along with the authentication method used to access the service. A target that bypasses authentication records `path_bypass`, shown as **Auth Bypassed** in the dashboard. +- **Denied requests**: failed authentication, access restriction blocks, and blocked target paths show `401` or `403` status codes with `reason` set to `Authentication failed`. The specific cause (invalid password, missing SSO session, blocked path, IP restricted, country restricted, CrowdSec verdict) is carried in `auth_method_used`, not in `reason`. A blocked target path records `path_block`, shown as **Path Blocked** in the dashboard. - **Errors**: backend errors or proxy issues show `5xx` status codes. These typically indicate that the target service is unreachable or returned an error. ### L4 log entries @@ -63,6 +64,7 @@ The following deny reasons identify why a connection was rejected. Note that for | Reason | Description | |--------|-------------| +| `path_block` | The matching HTTP target is configured to block access | | `ip_restricted` | The client IP was blocked by a CIDR access restriction | | `country_restricted` | The client's country was blocked by a country access restriction | | `geo_unavailable` | Country restrictions are configured but the GeoIP database is unavailable (fail-closed) | diff --git a/src/pages/manage/reverse-proxy/authentication.mdx b/src/pages/manage/reverse-proxy/authentication.mdx index 64f086a9f..f83617c2c 100644 --- a/src/pages/manage/reverse-proxy/authentication.mdx +++ b/src/pages/manage/reverse-proxy/authentication.mdx @@ -1,11 +1,11 @@ import {Note, Warning} from "@/components/mdx" export const description = - 'Configure SSO, password, PIN, and header authentication methods, plus IP and country access restrictions, for NetBird Reverse Proxy services.' + 'Configure service authentication, per-target access actions, and IP, country, and reputation restrictions for NetBird Reverse Proxy services.' # Reverse Proxy Authentication and Access Restrictions -NetBird Reverse Proxy supports multiple authentication methods and connection-level access restrictions to control who can access your exposed services. You can enable one or more methods on each service, or leave a service completely public. Authentication and access restrictions are configured per service in the **Authentication** tab when creating or editing a service. +NetBird Reverse Proxy supports multiple authentication methods and connection-level access restrictions to control who can access your exposed services. You can enable one or more methods on each service, leave a service completely public, or choose a different authentication action for an individual HTTP target path. Authentication and access restrictions are configured when creating or editing a service.

Authentication tab showing all available authentication methods @@ -175,6 +175,26 @@ Services can also be configured without any authentication. When no authenticati **Best for:** Public-facing websites, APIs that handle their own authentication internally, or services that are intentionally open to the internet. +## Per-target access actions + +HTTP targets can override the service authentication behavior for their matching path. Open or add a target, expand **Optional Settings**, and choose **Access**: + +| Action | Behavior | +|--------|----------| +| **Use service authentication** | Apply the authentication configured on the service. This is the default. | +| **Bypass authentication** | Skip SSO, password, PIN, and header authentication for requests routed to this target. Service-level access restrictions still apply. | +| **Block access** | Return `403` for requests routed to this target without contacting its backend. | + +For example, a service can require SSO on its `/` target, use **Bypass authentication** for `/public/`, and use **Block access** for `/admin/`. Target selection uses the longest matching, case-sensitive literal prefix. It does not support glob patterns or regular expressions. See [Path-based routing](/manage/reverse-proxy#path-based-routing) for matching examples and forwarding behavior. + +The action only controls reverse proxy authentication for the selected target. **Bypass authentication** does not bypass CIDR, country, or CrowdSec restrictions configured on the service. It also does not change path rewriting; the existing **Preserve Full Path** setting continues to control whether the matched prefix is sent to the backend. + +Targets that override the default are marked in target lists. Hover the access indicator to see its action. + + + Per-target access actions are available only for HTTP services. Upgrade all proxy instances in the selected cluster before using them. Older proxies ignore these actions and continue applying the service's authentication settings. **Bypass authentication** is unavailable for NetBird-Only services and Agent Network targets. + + ## Combining authentication methods You can enable multiple **operator auth methods** on a single service simultaneously. When more than one is active, users authenticate using **any** of the enabled methods — they choose which one to use when accessing the service. @@ -201,6 +221,8 @@ Access restrictions control which connections are allowed to reach your service Access restrictions are evaluated **before** authentication. If a connection is blocked by an access restriction rule, it is rejected immediately without any authentication check. +These restrictions also apply to targets set to **Bypass authentication**. A target action cannot relax a service-level CIDR, country, or CrowdSec denial. +

Authentication tab showing all available authentication methods

diff --git a/src/pages/manage/reverse-proxy/index.mdx b/src/pages/manage/reverse-proxy/index.mdx index a16d1ea2c..d0977a8a6 100644 --- a/src/pages/manage/reverse-proxy/index.mdx +++ b/src/pages/manage/reverse-proxy/index.mdx @@ -92,6 +92,7 @@ Target properties vary by service mode: - **Path** (optional) - a URL path prefix for path-based routing (e.g., `/api`). See [Path-based routing](#path-based-routing) below. - **Protocol** - `HTTP` or `HTTPS`, depending on what the backend service speaks - **Port** - the port on the target machine (defaults to `80` for HTTP, `443` for HTTPS) +- **Access** (optional) - use the service authentication, bypass authentication, or block access for the target's matching path. Configure this under **Optional Settings**. See [Per-target access actions](/manage/reverse-proxy/authentication#per-target-access-actions). - **Enabled/Disabled toggle** - individually enable or disable targets without removing them **L4 services (TCP, UDP, TLS):** @@ -263,6 +264,7 @@ In the **Details** tab: 6. In the target configuration, select the **type** (Peer, Host, Domain, Subnet, or Proxy Cluster), then choose the specific peer, resource, or cluster. 7. For HTTP services, set the **protocol** (HTTP or HTTPS) and **port** for the target. Optionally, enter a **path** for path-based routing. For L4 services, set the target **host/IP** and **port**. For **Proxy Cluster** targets, the host field accepts any hostname or IP the cluster's embedded proxy can resolve from its own host stack — see [Private services](/manage/reverse-proxy/bring-your-own-proxy#private-services-net-bird-only-access) for details. +8. For an HTTP target, expand **Optional Settings** and choose an **Access** action if this path should differ from the service default. **Use service authentication** is the default. See [Per-target access actions](/manage/reverse-proxy/authentication#per-target-access-actions).

Add Target configuration modal showing the unified Peer / Resource / Proxy Cluster picker @@ -405,21 +407,21 @@ The proxy's main port always runs an SNI router that peeks at the TLS ClientHell ## Path-based routing -When a service has multiple targets, you can assign each target a unique path prefix to route different URL paths to different backends. For example: +When a service has multiple targets, you can assign each target a unique path prefix to route different URL paths to different backends. Each HTTP target can also choose what happens after its path matches: -| Path | Target | Description | -|------|--------|-------------| -| `/` | Peer A (port 3000) | Main web application | -| `/api` | Peer B (port 8080) | API service | -| `/docs` | Resource C (port 80) | Documentation server | +| Path | Target | Access | Example result | +|------|--------|--------|----------------| +| `/` | Peer A (port 3000) | Use service authentication | `/account` uses the authentication configured on the service | +| `/public/` | Peer B (port 8080) | Bypass authentication | `/public/logo.svg` reaches Peer B without service authentication | +| `/admin/` | Resource C (port 80) | Block access | `/admin/users` returns `403`; Resource C is not contacted | -Incoming requests are matched against the configured path prefixes and forwarded to the corresponding target. Each path must be unique within a service. +Incoming requests are matched against the configured path prefixes. When an enabled target uses **Bypass authentication** or **Block access**, all enabled target paths must be unique within the service. If more than one prefix matches, the **longest matching prefix** wins. In the example above, `/public/logo.svg` matches both `/` and `/public/`, so the `/public/` target and its access action are selected. This is useful for consolidating multiple internal services under a single public domain, reducing the number of domains and TLS certificates you need to manage. -Prefixes match the request path as text, not by path segment, so a target on `/api` also receives `/api-docs`. To match a whole segment, end the path with a slash: a target on `/api/` receives `/api/x`, but not `/api-docs` or `/api` itself. +Prefix matching is literal and case-sensitive. Glob patterns such as `*` or `**` and regular expressions are not supported. Prefixes match the request path as text, not by path segment, so a target on `/api` also receives `/api-docs`. To match a whole segment, end the path with a slash: a target on `/api/` receives `/api/x`, but not `/api-docs` or `/api` itself. Likewise, `/Public/x` does not match `/public/`. -By default, the matched prefix is removed before the request reaches the backend: `/api/x` arrives as `/x`, and `/api-docs` as `/-docs`. To keep the full path, turn on **Preserve Full Path** for the target (`path_rewrite: preserve` in the API). +The access action does not change how paths are forwarded. By default, the matched prefix is removed before the request reaches the backend: `/api/x` arrives as `/x`, and `/api-docs` as `/-docs`. To keep the full path, use the existing **Preserve Full Path** target setting (`path_rewrite: preserve` in the API). ## Integration with Networks