Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
66aa2fa
Initial changes to support admin and support groups for OIDC authenti…
mchrisb03 Jul 14, 2026
c908b13
Merge pull request #170 from swissmakers/main
swissmakers Jul 15, 2026
50d9ec5
Add "None" as an SMTP Authentication Method
mchrisb03 Jul 16, 2026
95361c2
Added an option for SSH connections to establish a reverse tunnel for…
mchrisb03 Jul 17, 2026
b3527fc
Provide ability to specify multiple comma separated email addresses i…
mchrisb03 Jul 20, 2026
37da329
Also check is callback secret was changed, then the action file also …
Jun 28, 2026
a0714aa
We want to remember what server was coosen last, before a browser rel…
Jun 28, 2026
5692d2e
Fix deadlock on debug value reading
Jul 22, 2026
2b870f4
Make the actionban more robust, add tests also add missing config ban…
Jul 22, 2026
c926d5f
Sync all connectors on server, fail2ban-ui restarts so all configs ar…
Jul 22, 2026
4c62d04
Use crypto sha256 to create real sha265 session secrets
Jul 22, 2026
6d68d96
Fix old licese-header to also GPL 3.0
Jul 22, 2026
4dadc31
Update the docs about the bantime factor changes and unsecure -k opti…
Jul 22, 2026
af2cb66
Update gitignore
Jul 22, 2026
4235593
Implement our ValidateIP function to validate all IP inputs against it
Jul 22, 2026
dff4c5e
Add saveguards for filter and jail naming
Jul 22, 2026
1440464
Change chmod for the actionfile from 644 to 600 as a security measurment
Jul 22, 2026
e8819a8
Add missing BantimeMaxtime, BantimeFactor, and BantimeOveralljails in…
Jul 22, 2026
7b4f4e7
Translate all currently left / hardcoded toast messages or Modal text…
Jul 22, 2026
ae2eb2b
Replace the httpClient from opnsence and pfsence to a common function…
Jul 22, 2026
a0eba84
Bump version to v1.5.1
Jul 22, 2026
57e0708
Introduce secret masking and restoring, we want only POST back change…
Jul 22, 2026
5120b8f
Add seperate route to directly search on the ban-bucket form each fai…
Jul 22, 2026
8d56a1f
Errors on the log-boroadcasting itself must directly output the error…
Jul 22, 2026
bed5f5b
Upgrade crypto to v0.52.0
Jul 22, 2026
7a3287f
Also upgrade the 'net' module to v0.55 to mitigate go-net HTML parser…
Jul 22, 2026
4f04413
Fix conflicts of the pull-request of mchrisb03 that introduces ssh-re…
Jul 22, 2026
5865bb3
Merge pull request #172 from swissmakers/pr171-integration
swissmakers Jul 22, 2026
edea7a3
Add event-retension for the saved objects in the SQLite database. It …
Jul 22, 2026
acd5b23
Do not query again if the summary of the same server was already load…
Jul 22, 2026
a7643fb
Add little since filter to stop planner to wlk the whole table
Jul 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,3 @@ fail2ban-ui.db*
node_modules/
package-lock.json
.tailwind-build/

# Server specific test-files
server
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ Three alert providers, Email (SMTP), Webhook, and Elasticsearch, with country fi

[![Global Settings](screenshots/4.4_Settings_GlobalSettings.png)](screenshots/4.4_Settings_GlobalSettings.png)

Global Fail2Ban defaults: `bantime`, `findtime`, `maxretry`, and the `banaction` backend (nftables, firewalld, iptables).
Global Fail2Ban defaults: `bantime`, `findtime`, `maxretry`, and the `banaction` backend (nftables, firewalld, iptables). When bantime increment is enabled, the escalation behavior can be tuned with `bantime.rndtime`, `bantime.maxtime` (cap for escalating bans), `bantime.factor` (escalation multiplier), and `bantime.overalljails` (count repeat offenses across all jails).

## Contributing

Expand Down
53 changes: 46 additions & 7 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@
//
// Copyright (C) 2026 Swissmakers GmbH (https://swissmakers.ch)
//
// Licensed under the PolyForm Shield License 1.0.0.
// Licensed under the GNU General Public License, Version 3 (GPL-3.0)
// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://polyformproject.org/licenses/shield/1.0.0/
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// or in the LICENSE file in this repository.
//
// Required Notice: Copyright Swissmakers GmbH (https://swissmakers.ch)
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"context"
"fmt"
"log"
"net"
Expand All @@ -25,6 +28,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/swissmakers/fail2ban-ui/internal/auth"
"github.com/swissmakers/fail2ban-ui/internal/config"
"github.com/swissmakers/fail2ban-ui/internal/fail2ban"
"github.com/swissmakers/fail2ban-ui/internal/storage"
"github.com/swissmakers/fail2ban-ui/pkg/web"
)
Expand All @@ -36,11 +40,9 @@ import (
func main() {
settings := config.GetSettings()

// Initialize base path
web.SetBasePathFromEnv()
auth.SetSessionCookiePath(web.CookiePath())

// Initialize storage
if err := storage.Init(""); err != nil {
log.Fatalf("Failed to initialise storage: %v", err)
}
Expand All @@ -55,6 +57,41 @@ func main() {
log.Fatalf("failed to initialise fail2ban connectors: %v", err)
}

// Sync remote SSH/agent runtime config, then reload so action/callback and jail.local changes become active
go func() {
synced, failed := fail2ban.GetManager().SyncRemoteStartupConfig(context.Background(), 30*time.Second)
if synced+failed > 0 {
log.Printf("startup remote config sync complete: %d succeeded, %d failed", synced, failed)
}
}()

// Prune ban events beyond the configured retention window once at startup and then daily
go func() {
pruneBanEvents := func() {
retentionDays := config.GetSettings().EventRetentionDays
if retentionDays <= 0 {
return
}
cutoff := time.Now().UTC().AddDate(0, 0, -retentionDays)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
deleted, err := storage.PruneBanEventsBefore(ctx, cutoff)
cancel()
if err != nil {
log.Printf("warning: failed to prune ban events older than %d days: %v", retentionDays, err)
return
}
if deleted > 0 {
log.Printf("Pruned %d ban events older than %d days", deleted, retentionDays)
}
}
pruneBanEvents()
ticker := time.NewTicker(24 * time.Hour)
defer ticker.Stop()
for range ticker.C {
pruneBanEvents()
}
}()

// Initialize OIDC authentication
oidcConfig, err := config.GetOIDCConfigFromEnv()
if err != nil {
Expand All @@ -68,6 +105,8 @@ func main() {
log.Fatalf("failed to initialize OIDC: %v", err)
}
log.Println("OIDC authentication enabled")
} else {
log.Println("WARNING: OIDC authentication is DISABLED -> Run this way only in a trusted network or behind an authenticating reverse proxy (see docs/security.md).")
}

// Set Gin mode
Expand Down
6 changes: 6 additions & 0 deletions docker-compose-allinone.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ services:
# Optional: Username claim (default: preferred_username)
# The claim to use as the username (e.g., email, preferred_username, sub)
# - OIDC_USERNAME_CLAIM=preferred_username
# Optional: OIDC role-based access control
# If no admin/support roles are configured, all authenticated users have full access.
# Dot paths are supported for the role claim, e.g. realm_access.roles for Keycloak.
# - OIDC_ROLE_CLAIM=groups
# - OIDC_ADMIN_ROLES=fail2ban-admins
# - OIDC_SUPPORT_ROLES=fail2ban-support
# Optional: Provider logout URL
# If not set, the logout URL will be auto-constructed based on the provider:
# Keycloak: {issuer}/protocol/openid-connect/logout
Expand Down
6 changes: 6 additions & 0 deletions docker-compose.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ services:
# Optional: Username claim (default: preferred_username)
# The claim to use as the username (e.g., email, preferred_username, sub)
# - OIDC_USERNAME_CLAIM=preferred_username
# Optional: OIDC role-based access control
# If no admin/support roles are configured, all authenticated users have full access.
# Dot paths are supported for the role claim, e.g. realm_access.roles for Keycloak.
# - OIDC_ROLE_CLAIM=groups
# - OIDC_ADMIN_ROLES=fail2ban-admins
# - OIDC_SUPPORT_ROLES=fail2ban-support
# Optional: Provider logout URL
# If not set, the logout URL will be auto-constructed based on the provider:
# Keycloak: {issuer}/protocol/openid-connect/logout
Expand Down
1 change: 1 addition & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ This is a practical endpoint index for operators. The web frontend uses these en
## Authentication

* When OIDC is enabled, all `/api/*` endpoints, including the WebSocket, require an authenticated session - except the callback endpoints.
* Optional OIDC role-based access control can further restrict authenticated users. `admin` users can access everything; `support` users can view operational dashboard/event data and manually ban/unban IPs.
* The callback endpoints (`/api/ban`, `/api/unban`) are authenticated through the `X-Callback-Secret` header.

## Input validation
Expand Down
32 changes: 32 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ Fail2Ban UI receives ban and unban callbacks at:
|----------|-------------|
| `CALLBACK_URL` | URL reachable from every managed Fail2Ban host: scheme, host, optional port, and `BASE_PATH` if used. No trailing slash. |
| `CALLBACK_SECRET` | Shared secret validated through the `X-Callback-Secret` header. If unset, Fail2Ban UI generates one on first start. |
| `CALLBACK_INSECURE_TLS` | Default `false`. When `true` (or `1`/`yes`/`on`), the `curl` command in the generated ban action skips TLS certificate verification (`-k`) for an `https://` callback URL. Only enable this if the UI uses a self-signed certificate that the managed hosts do not trust. |

> **Upgrade note:** older releases always passed `-k` for `https://` callback URLs. TLS verification is now on by default because the callback carries the shared secret. If your UI runs with a self-signed certificate, either install the certificate on every managed host or set `CALLBACK_INSECURE_TLS=true`, otherwise ban callbacks will fail silently after upgrading. The regenerated action file is pushed to managed hosts automatically at startup.

Example:

Expand All @@ -63,6 +66,12 @@ With a subpath:
-e CALLBACK_SECRET='replace-with-a-random-secret'
```

### Reverse SSH tunnel for callbacks

SSH-connected servers can enable **reverse tunnel for events** (server form). The UI then opens a reverse tunnel (`ssh -R <port>:localhost:<port>`) alongside the SSH control connection so callbacks reach the UI even when the managed host cannot connect to it directly (NAT, firewall). The port is derived from `CALLBACK_URL` (explicit port, otherwise 443/80 by scheme).

The tunnel is only used if `CALLBACK_URL` points to `localhost`/`127.0.0.1` — the remote Fail2Ban sends its callbacks to that URL, which the tunnel forwards to the UI. With a public callback URL the callbacks bypass the tunnel; the UI logs a warning in that case. Note that a localhost callback URL applies globally, so mixing tunneled and non-tunneled remote servers is not possible.

## Privacy and telemetry controls

| Variable | Description |
Expand All @@ -82,6 +91,19 @@ With a subpath:
|----------|-------------|
| `JAIL_AUTOMIGRATION=true` | Experimental migration from a monolithic `jail.local` to `jail.d/*.local`. On production systems, migrate manually instead. |

## Global Fail2Ban defaults (UI-managed)

Configure under **Settings → Global Settings**. These values are written to the `[DEFAULT]` section of the managed `jail.local` and pushed to every managed host:

* `bantime`, `findtime`, `maxretry`, `ignoreip`, `banaction` / `banaction_allports`, firewall `chain`
* Bantime increment escalation (optional, only emitted when set):
* `bantime.rndtime` — random jitter added to escalating bans
* `bantime.maxtime` — cap for escalating bans (e.g. `5w`)
* `bantime.factor` — escalation multiplier (Fail2Ban default: `1`)
* `bantime.overalljails` — count repeat offenses across all jails instead of per jail

Duration fields accept plain seconds or Fail2Ban time suffixes (`3600`, `48h`, `5w`, `1d 12h`). `bantime` additionally accepts `-1` for permanent bans.

## Alert settings (UI-managed)

Configure under **Settings → Alert Settings**:
Expand All @@ -91,6 +113,8 @@ Configure under **Settings → Alert Settings**:
* Alert country filters
* GeoIP provider and log-line limits

> **Privacy note on the `builtin` GeoIP provider:** it resolves countries via the free ip-api.com service, which means every enriched (banned) IP address is sent to a third party — and the free tier only supports plain HTTP, so the queries travel unencrypted. For privacy-sensitive deployments use the MaxMind provider with a local GeoLite2 database instead.

For provider behavior and payloads, see [alert-providers.md](alert-providers.md) and [webhooks.md](webhooks.md).

## Threat intelligence settings (UI-managed)
Expand Down Expand Up @@ -133,6 +157,14 @@ Common optional variables:
| `OIDC_SKIP_VERIFY` | `false` | Skips TLS verification toward the provider. Development only. |
| `OIDC_SKIP_LOGINPAGE` | `false` | Skips the UI login page and redirects to the provider directly |

OIDC role-based access control is optional. When no role variables are set, every authenticated OIDC user keeps the previous full-access behavior.

| Variable | Default | Description |
|----------|---------|-------------|
| `OIDC_ROLE_CLAIM` | `groups` | Claim containing roles/groups. Dot paths are supported, for example `realm_access.roles` for Keycloak. |
| `OIDC_ADMIN_ROLES` | empty | Comma-separated OIDC role/group names that grant full admin access. |
| `OIDC_SUPPORT_ROLES` | empty | Comma-separated OIDC role/group names that grant support access: dashboard/event reads plus manual ban/unban. |

Provider notes:

* **Keycloak**: allow the redirect URI `{BASE_PATH}/auth/callback` (or `/auth/callback` at root) and the post-logout redirect `{BASE_PATH}/auth/login`.
Expand Down
14 changes: 14 additions & 0 deletions docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ Fail2Ban UI performs security-sensitive operations: it bans addresses, changes f

See [reverse-proxy.md](reverse-proxy.md) for hardened proxy examples and the WebSocket forwarding requirements.

## Authentication model

Sessions are stateless encrypted cookies (AES-GCM), logout clears the cookie but cannot revoke an already-captured session token before its expiry (`OIDC_SESSION_MAX_AGE`, default 1 hour). Keep session lifetimes short and always serve the UI over TLS.

When OIDC role-based access control is configured (`OIDC_ADMIN_ROLES` / `OIDC_SUPPORT_ROLES`), the user's roles and access level are captured at login and stored in the session cookie. Role changes at the identity provider only take effect after the user logs in again — another reason to keep session lifetimes short. Users whose roles match neither list can authenticate but are denied on every API endpoint.

The debug console (Settings → Console Output) mirrors the complete server log to every connected UI client over the WebSocket. Log lines can include client IPs, email addresses, and configuration diagnostics — leave it disabled unless actively debugging.

## Input validation

All user-supplied IP addresses are validated with Go's `net.ParseIP` and `net.ParseCIDR` before they reach any integration, command, or database query. This applies to:
Expand All @@ -39,6 +47,11 @@ Additional hardening:

* Use a long, random secret and rotate it on suspected leakage.
* Restrict network access so that only the managed Fail2Ban hosts can reach the callback endpoints.
* Serve the callback URL over `https://` with a certificate the managed hosts trust. The generated ban action verifies TLS certificates by default; `CALLBACK_INSECURE_TLS=true` disables verification and should only be used with self-signed certificates on trusted networks (see [configuration.md](configuration.md)).

## Secrets at rest

Secrets (callback secret, SMTP password, agent tokens, integration API keys) are stored in the SQLite database and embedded in the generated `action.d/ui-custom-action.conf`. Fail2Ban UI restricts both to file mode `0600` on startup. Read APIs never return stored secrets; the frontend receives a placeholder sentinel and unchanged saves keep the stored value.

## SSH connector hardening

Expand All @@ -56,6 +69,7 @@ When using the firewall integrations (MikroTik, pfSense, OPNsense):
* Use a dedicated service account on the firewall device with the minimum permissions needed: address-list management only on MikroTik; alias management only on pfSense and OPNsense.
* For pfSense and OPNsense, use a dedicated API token with limited scope.
* Restrict network access so the Fail2Ban UI host is the only source allowed to reach the firewall management interface.
* Configure the MikroTik SSH host-key fingerprint. When no fingerprint is set, the connector accepts any host key (MITM exposure); with one configured, it is verified with a constant-time comparison.

## Least privilege and file access

Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ require (
github.com/gorilla/websocket v1.5.3
github.com/likexian/whois v1.15.7
github.com/oschwald/maxminddb-golang v1.13.1
golang.org/x/crypto v0.48.0
golang.org/x/crypto v0.52.0
golang.org/x/oauth2 v0.35.0
golang.org/x/text v0.34.0
golang.org/x/text v0.37.0
modernc.org/sqlite v1.46.0
)

Expand Down Expand Up @@ -47,8 +47,8 @@ require (
go.uber.org/mock v0.6.0 // indirect
golang.org/x/arch v0.24.0 // indirect
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/net v0.55.0 // indirect
golang.org/x/sys v0.45.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
modernc.org/libc v1.68.0 // indirect
modernc.org/mathutil v1.7.1 // indirect
Expand Down
32 changes: 16 additions & 16 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -98,27 +98,27 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y=
golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o=
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
Loading