{"id":13813489,"url":"https://github.com/NHAS/wag","last_synced_at":"2025-05-15T00:33:33.346Z","repository":{"id":50228185,"uuid":"517558374","full_name":"NHAS/wag","owner":"NHAS","description":"Simple Wireguard 2FA","archived":false,"fork":false,"pushed_at":"2024-10-29T21:37:53.000Z","size":10918,"stargazers_count":513,"open_issues_count":9,"forks_count":28,"subscribers_count":11,"default_branch":"main","last_synced_at":"2024-10-29T23:42:44.570Z","etag":null,"topics":["2fa","firewall","linux","management-portal","mfa","network","networking","privacy","security","self-hosted","ui","virtual-network","vpn","vpn-server","wireguard","wireguard-admin","wireguard-vpn"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/NHAS.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"NHAS"}},"created_at":"2022-07-25T07:15:54.000Z","updated_at":"2024-10-24T04:56:17.000Z","dependencies_parsed_at":"2023-02-19T06:15:31.481Z","dependency_job_id":"954b24e6-d1dd-44d0-8657-58594701f8cc","html_url":"https://github.com/NHAS/wag","commit_stats":null,"previous_names":[],"tags_count":68,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NHAS%2Fwag","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NHAS%2Fwag/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NHAS%2Fwag/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NHAS%2Fwag/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/NHAS","download_url":"https://codeload.github.com/NHAS/wag/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225319245,"owners_count":17455733,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["2fa","firewall","linux","management-portal","mfa","network","networking","privacy","security","self-hosted","ui","virtual-network","vpn","vpn-server","wireguard","wireguard-admin","wireguard-vpn"],"created_at":"2024-08-04T04:01:19.335Z","updated_at":"2024-11-19T08:30:39.753Z","avatar_url":"https://github.com/NHAS.png","language":"Go","readme":"# Wag\n\nWag adds MFA, route restriction and device enrolment to wireguard.    \n\nKey Features:\n- Define routes which require MFA authorisation, or public always accessible routes\n- Easy API for registering new clients\n- High Availability\n- Multiple MFA options, including webauthn, oidc and more\n\n![image](https://github.com/NHAS/wag/assets/6820641/3a2b2dd9-15af-40c4-bb81-3e479d48425a)\n\n![image](https://github.com/NHAS/wag/assets/6820641/89976794-10af-493a-b8c4-3d02f50417ce)\n\n![image](https://github.com/NHAS/wag/assets/6820641/83cae3c0-bb19-4aa0-846f-c045387a0910)\n\n![image](https://github.com/NHAS/wag/assets/6820641/ff237473-d522-451e-8529-92bd111d4b96)\n\n\n# Sponsorship \n\nThis work was very kindly supported by \u003ca href='https://www.aurainfosec.com/'\u003eAura Information Security\u003c/a\u003e. \n\n![image](https://user-images.githubusercontent.com/6820641/181147262-c7baa5a5-36b2-4153-b01f-5064226ec56e.png)\n\n\n# Requirements\n\n\n`iptables` and `libpam` must be installed.  \nWag must be run as root, to manage `iptables` and the `wireguard` device.  \n   \nForwarding must be enabled in `sysctl`.  \n  \n```\nsysctl -w net.ipv4.ip_forward=1\n```\n\nWag does not need `wg-quick` or other equalivent as long as the kernel supports wireguard.  \n\n# Setup instructions\n\nBoth options require a kernel newer than 5.9+\n  \nBinary release (requires glibc 2.31+):  \n```\ncurl -L $(curl -s https://api.github.com/repos/NHAS/wag/releases/latest | jq -M -r '.assets[0].browser_download_url') -o wag\nsudo ./wag gen-config\n\nsudo ./wag start -config \u003cgenerated_config_name\u003e\n```\n  \nFrom source (will require `go1.19`, `npm`, `gulp`, `clang`, `llvm-strip`, `libbpf`):  \n```\ngit clone git@github.com:NHAS/wag.git\ncd wag\nmake\n\ncp example_config.json config.json\n\nsudo ./wag start\n```\n\nIf running behind a reverse proxy, `X-Forwarded-For` must be set.\n\n# Management\n\nThe root user is able to manage the wag server with the following command:\n  \n```\nwag subcommand [-options]\n```\n\nSupported commands: `start`, `cleanup`, `reload`, `version`, `firewall`, `registration`, `devices`, `users`, `webadmin`, `gen-config`\n  \n`start`: starts the wag server  \n```\nUsage of start:\n  Start wag server (does not daemonise)\n  -join string\n        Cluster join token\n  -config string\n        Configuration file location (default \"./config.json\")\n```\n\n`cleanup`: Will remove all firewall forwards, and shutdown the wireguard device  \n\n`reload`: Reloads ACLs from configuration\n\n`version`: Display the version of wag\n\n`firewall`: Get firewall rules\n```  \nUsage of firewall:\n  -list\n        List firewall rules\n  -socket string\n        Wag socket to act on (default \"/tmp/wag.sock\")\n\n``` \n\n`registration`:  Deals with creating, deleting and listing the registration tokens\n```\nUsage of registration:\n  -add\n        Create a new enrolment token\n  -del\n        Delete existing enrolment token\n  -group value\n        Manually set user group (can supply multiple -group, or use -groups for , delimited group list, useful for OIDC)\n  -groups string\n        Set user groups manually, ',' delimited list of groups, useful for OIDC\n  -list\n        List tokens\n  -overwrite string\n        Add registration token for an existing user device, will overwrite wireguard public key (but not 2FA)\n  -socket string\n        Wag socket to act on (default \"/tmp/wag.sock\")\n  -token string\n        Manually set registration token (Optional)\n  -username string\n        User to add device to\n```  \n\n`devices`: Manages devices  \n```\nUsage of devices:\n  -address string\n        Address of device\n  -del\n        Remove device and block wireguard access\n  -list\n        List wireguard devices\n  -lock\n        Lock device access to mfa routes\n  -mfa_sessions\n        Get list of devices with active authorised sessions\n  -socket string\n        Wag control socket to act on (default \"/tmp/wag.sock\")\n  -unlock\n        Unlock device\n  -username string\n        Owner of device (indicates that command acts on all devices owned by user)\n```\n  \n`users`: Manages users MFA and can delete all users devices\n```\nUsage of users:\n  -del\n        Delete user and all associated devices\n  -list\n        List users, if '-username' supply will filter by user\n  -lockaccount\n        Lock account disable authention from any device, deauthenticates user active sessions\n  -reset-mfa\n        Reset MFA details, invalids all session and set MFA to be shown\n  -socket string\n        Wag socket location, (default \"/tmp/wag.sock\")\n  -unlockaccount\n        Unlock a locked account, does not unlock specific device locks (use device -unlock -username \u003c\u003e for that)\n  -username string\n        Username to act upon\n```\n\n`webadmin`: Manages the administrative users for the web UI\n```\nUsage of webadmin:\n  -add\n        Add web administrator user (requires -password)\n  -del\n        Delete admin user\n  -list\n        List web administration users, if '-username' supply will filter by user\n  -lockaccount\n        Lock admin account disable login for this web administrator user\n  -password string\n        Username to act upon\n  -socket string\n        Wag instance control socket (default \"/tmp/wag.sock\")\n  -unlockaccount\n        Unlock a web administrator account\n  -username string\n        Admin Username to act upon\n```\n\n# User guide\n\n## Installing wag\n\n1. Copy `wag`, `config.json` to `/opt/wag`\n2. Generate a wireguard private key with `wg genkey` set `PrivateKey` in the example config to it\n3. Copy (or link) `wag.service` to `/etc/systemd/system/` and start/enable the service\n\n## Creating new registration tokens\n\nFirst generate a token.  \n```\n# ./wag registration -add -username tester\ntoken,username\ne83253fd9962c68f73aa5088604f3f425d58a963bfb5c0889cca54d63a34b2e3,tester\n```\n\nThen curl said token.  \n```\ncurl http://public.server.address:8080/register_device?key=e83253fd9962c68f73aa5088604f3f425d58a963bfb5c0889cca54d63a34b2e3\n```\n\nThe service will return a fully templated response:\n```\n[Interface]\nPrivateKey = \u003comitted\u003e\nAddress = 192.168.1.1\n\n[Peer]\nEndpoint =  public.server.address:51820\nPublicKey = pnvl40WiRt++0NucEGexlpfwWA8QzBYg2+8ZWZJvejA=\nAllowedIPs = 10.7.7.7/32, 192.168.1.1/32, 192.168.3.4/32, 192.168.3.5/32\nPersistentKeepAlive = 10\n```\n\nWhich can then be written to a config file. \n\n## Entering MFA  \n  \nTo authenticate the user should browse to the servers vpn address, in the example, case `192.168.1.1:8080`, where they will be prompted for their 2fa code.  \nThe configuration file specifies how long a session can live for, before expiring.  \n\n## Signing in to the Management console\n\nMake sure that you have `ManagementUI.Enabled` set as `true`, then do the following from the console:\n\n```\nsudo ./wag webadmin -add -username \u003cyour_username\u003e -password \u003cyour-password-here\u003e\n```\nThen browse to your management listening address and enter your credentials.\n\nThe web interface itself cannot add administrative users.\n\n\n# Configuration file reference\n  \n`NumberProxies`: The number of trusted reverse proxies before the client, makes wag respect the `X-Forward-For` directive and parses the client IP from it correctly\n`HelpMail`: The email address that is shown on the prompt page  \n`Lockout`: Number of times a person can attempt mfa authentication before their account locks  \n`NAT`: Turn on or off masquerading  \n`ExposePorts`: Expose ports on the VPN server to the client (adds rules to IPtables) example: [ \"443/tcp\", \"100-200/udp\" ]  \n`CheckUpdates`: If enabled (off by default) the management UI will show an alert if a new version of wag is available. This talks to api.github.com   \n`MFATemplatesDirectory`: A string path option, when set templates will be queried from disk rather than the embedded copies. Allows you to customise the MFA registration, entry, and success pages, allows custom `js` and `css` in the `MFATemplatesDirectory /custom/` directory  \n`DownloadConfigFileName`: The filename of the wireguard config that is downloaded, defaults to `wg0.conf` \n  \n`ExternalAddress`: The public address of the server, the place where wireguard is listening to the internet, and where clients can reach the `/register_device` endpoint    \n  \n`MaxSessionLifetimeMinutes`: After authenticating, a device will be allowed to talk to privileged routes for this many minutes, if -1, timeout is disabled  \n`SessionInactivityTimeoutMinutes`: If a device has not sent data in `n` minutes, it will be required to reauthenticate, if -1 timeout is disabled  \n  \n`DatabaseLocation`: Where to load the sqlite3 database from, it will be created if it does not exist  \n`Socket`: Wag control socket, changing this will allow multiple wag instances to run on the same machine  \n`Acls`: Defines the `Groups` and `Policies` that restrict routes  \n`Policies`: A map of group or user names to policy objects which contain the wag firewall \u0026 route capture rules. The most specific match governs the type of access a user has to a route, e.g if you have a `/16` defined as MFA, but one ip address in that range as allow that is `/32` then the `/32` will take precedence over the `/16`   \n`Policies.\u003cpolicy name\u003e.Mfa`: The routes and services that require Mfa to access  \n`Policies.\u003cpolicy name\u003e.Public`: Routes and services that do not require authorisation\n`Policies.\u003cpolicy name\u003e.Deny`: Deny access to this route  \n  \n`Webserver`: Object that contains the public and tunnel listening addresses of the webserver  \n\n`WebServer.Public.ListenAddress`: Listen address for endpoint  \n`WebServer.Tunnel.Port`: Port for in-vpn-tunnel webserver, this does not take a full IP address, as the tunnel listener should *never* be outside the wireguard device\n\n`WebServer.\u003cendpoint\u003e.CertPath`: TLS Certificate path for endpoint  \n`WebServer.\u003cendpoint\u003e.KeyPath`: TLS key for endpoint  \n  \n`Authenticators`: Object that contains configurations for the authentication methods wag provides  \n`Authenticators.Issuer`: TOTP issuer, the name that will get added to the TOTP app  \n`Authenticators.DomainURL`: Full url of the vpn authentication endpoint, required for `webauthn` and `oidc`\n`Authenticators.DefaultMethod`: String, default method the user will be presented, if not specified a list of methods is displayed to the user (possible values: `webauth`, `totp`, `oidc`, `pam`)    \n`Authenticators.Methods`: String array, enabled authentication methods, e.g `[\"totp\",\"webauthn\",\"oidc\", \"pam\"]`. \n\n`Authenticators.OIDC`: Object that contains `OIDC` specific configuration options\n`Authenticators.OIDC.IssuerURL`: Identity provider endpoint, e.g `http://localhost:8080/realms/account`\n`Authenticators.OIDC.ClientID`:  OIDC identifier for application\n`Authenticators.OIDC.ClientSecret`: OIDC secret\n`Authenticators.OIDC.GroupsClaimName`: Not yet used.  \n  \n`Authenticators.PAM.ServiceName`: Name of PAM-Auth file in `/etc/pam.d/`  will default to `/etc/pam.d/login` if unset or empty  \n  \n`Clustering`: Object containing the clustering details  \n`Clustering.ClusterState`: Same as the etcd cluster state setting, can be either `new`, create a new cluster, or `existing`. If you are joining an existing cluster, use `start -join` rather than this  \n`Clustering.ETCDLogLevel`: Level of logging for the embedded etcd server to emit, options `info`, `error`  \n`Clustering.Witness`: Is the node a witness node, i.e one that does not start a wireguard device, or management UI, but replicates events for the RAFT concensus  \n`Clustering.TLSManagerListenURL`: URL for generating certificates for the wag cluster, must be reachable by all nodes, typically automatically set by `start -join`  \n  \n`Wireguard`: Object that contains the wireguard device configuration  \n`Wireguard.DevName`: The wireguard device to attach or to create if it does not exist, will automatically add peers (no need to configure peers with `wg-quick`)  \n`Wireguard.ListenPort`: Port that wireguard will listen on  \n`Wireguard.PrivateKey`: The wireguard private key, can be generated with `wg genkey`  \n`Wireguard.Address`: Subnet the VPN is responsible for  \n`Wireguard.MTU`: Maximum transmissible unit defaults to 1420 if not set for IPv4 over Ethernet  \n`Wireguard.DNS`: An array of DNS servers that will be automatically used, and set as \"Allowed\" (no MFA)  \n   \n`ManagementUI`: Object that contains configurations for the webadministration portal. It is not recommend to expose this portal, I recommend setting `ListenAddress` to `127.0.0.1`/`localhost` and then use ssh forwarding to expose it  \n`ManagementUI.Enabled`: Enable the web UI  \n`ManagementUI.ListenAddress`: Listen address to expose the management UI on  \n`ManagementUI.CertPath`: TLS Certificate path for management endpoint  \n`ManagementUI.KeyPath`: TLS key for the management endpoint  \n  \nFull config example\n```json\n{\n    \"Proxied\": true,\n    \"ExposePorts\": [\n        \"443/tcp\",\n        \"100-200/udp\"\n     ],\n    \"CheckUpdates\": true,\n    \"Lockout\": 5,\n    \"NAT\": true,\n    \"HelpMail\": \"help@example.com\",\n    \"MaxSessionLifetimeMinutes\": 2,\n    \"SessionInactivityTimeoutMinutes\": 1,\n    \"ExternalAddress\": \"81.80.79.78\",\n    \"DatabaseLocation\": \"devices.db\",\n    \"Socket\":\"/tmp/wag.sock\",\n    \"Webserver\": {\n        \"Public\": {\n            \"ListenAddress\": \"192.168.121.61:8080\",\n            \"CertPath\": \"/etc/example/cert/path\",\n            \"KeyPath\": \"/etc/ssl/private/somecert.key\"\n        },\n        \"Tunnel\": {\n            \"Port\": \"8080\"\n        }\n    },\n    \"ManagementUI\": {\n        \"ListenAddress\": \"127.0.0.1:4433\",\n        \"CertPath\": \"/etc/example/cert/path\",\n        \"KeyPath\": \"/etc/ssl/private/somecert.key\",\n        \"Enabled\": true\n    },\n    \"Authenticators\": {\n        \"Issuer\": \"vpn.test\",\n        \"DomainURL\": \"https://vpn.test:8080\",\n        \"DefaultMethod\":\"webauthn\",\n        \"Methods\":[\"totp\",\"webauthn\", \"oidc\", \"pam\"],\n        \"OIDC\": {\n            \"IssuerURL\": \"http://localhost:8080/\",\n            \"ClientSecret\": \"\u003cOMITTED\u003e\",\n            \"ClientID\": \"account\",\n            \"GroupsClaimName\": \"groups\"\n        }\n    },\n    \"Clustering\": {\n        \"ClusterState\": \"new\",\n        \"ETCDLogLevel\": \"error\",\n        \"Witness\": false,\n        \"TLSManagerListenURL\": \"https://wag.server:3434\"\n    },\n    \"Wireguard\": {\n        \"DevName\": \"wg0\",\n        \"ListenPort\": 53230,\n        \"PrivateKey\": \"AN EXAMPLE KEY\",\n        \"Address\": \"192.168.1.1/24\",\n        \"MTU\": 1420,\n        \"DNS\": [\"1.1.1.1\"]\n    },\n    \"Acls\": {\n        \"Groups\": {\n            \"group:nerds\": [\n                \"daviv.test\",\n                \"franky.someone\",\n                \"any_username\"\n            ]\n        },\n        \"Policies\": {\n            \"*\": {\n                \"Mfa\": [\n                     \"10.0.0.2/32 8080/any\"\n                ],\n                \"Allow\": [\n                    \"10.7.7.7/32\",\n                    \"google.com\"\n                ]\n            },\n            \"username\": { \n                \"Mfa\": [\n                     \"someinternal.service 9100/tcp\"\n                ],\n                \"Allow\":[ \"10.0.0.1/32\"]\n            },\n            \"group:nerds\": {\n                \"Mfa\": [\n                    \"192.168.3.4/32\",\n                    \"10.0.0.0/24\",\n                    \"thing.internal 443/tcp icmp\"\n                ],\n                \"Allow\": [\n                    \"192.168.3.5/32\"\n                ],\n                \"Deny\": [\n                    \"10.0.0.5/32\"\n                 ]\n            }\n        }\n    }\n}\n```\n   \n## Defining ACL rules\n  \nThe `Policies` section allows you to define what routes should be both captured by the VPN and what ports and protocols are allowed through Wag.  \n  \nRules use the subnet prefix length to determine which rule applies. The most *specific* match is use to determine the level of user access to a route.   \nFor example:  \n```json\n \"*\": {\n                \"Mfa\": [\n                     \"10.0.0.0/16\"\n                ],\n                \"Allow\": [\n                    \"10.0.1.1/32\",\n                ]\n            },\n```\nUsers will be able to access 10.0.1.1 **without** MFA as the match is more specific. This change occured in v6.0.0, previously MFA routes would always take precedence.   \n  \n  \nAdditionally if multiple policies are defined for a single route they are composed with MFA rules taking preference.  \nFor example:  \n```json\n \"*\": {\n            \"Mfa\": [\n                  \"10.0.0.0/16\",\n                  \"10.0.1.1/32 22/tcp\",\n            ]\n  },\n \"group:users\": {\n            \"Allow\": [\n                  \"10.0.1.1/32 443/tcp\",\n            ]\n }\n```\nAll users will be able to access `22/tcp` on the `10.0.1.1/32` host, but users in the `group:users` will be able to access `443/tcp` on that host as well, along with `22/tcp` when authorized.  \n\nAs of **[version number, yet to be released]** you can now define deny rules which will block access to a route.\n\nExample: \n\n```json\n \"*\": {\n            \"Allow\": [\n                  \"10.0.0.0/16\",\n                  \"10.0.1.1/32 443/tcp\",\n            ]\n  },\n \"group:users\": {\n            \"Deny\": [\n                  \"10.0.1.1/32 443/tcp\",\n            ]\n }\n ```\n\nIts important to note that the most specific rule effectively creates a new rule \"bucket\", so if you do something like:  \n```json\n\"group:nerds\": {\n      \"Allow\": [\n            \"10.0.0.0/24 443/tcp\"\n      ],\n      \"Deny\": [\n            \"10.0.0.5/32 22/tcp\"\n      ]\n}\n```\n  \nYour clients will not be able to access `10.0.0.5/32 443/tcp`, as the only rule in the `/32` \"bucket\" is a deny rule. You can solve this by adding the following:\n```json\n\"group:nerds\": {\n      \"Allow\": [\n            \"10.0.0.0/24 443/tcp\"\n            \"10.0.0.5/32 22/tcp\"\n      ],\n      \"Deny\": [\n            \"10.0.0.5/32 22/tcp\"\n      ]\n}\n```\n  \nor  \n  \n```json\n\"group:nerds\": {\n      \"Allow\": [\n            \"10.0.0.0/24 443/tcp\"\n      ],\n      \"Deny\": [\n            \"10.0.0.0/24 22/tcp\"\n      ]\n}\n```\nAs then you're adding the deny rule to the `/24` \"bucket\".  \n  \nAdditionally, It is possible to define what services a user can access by defining port and protocol rules.  \nCurrently 3 types of port and protocol rules are supported:  \n  \n### Any \n\nWhen no other rules are defined or the `any` keyword is used wag will allow all services and port combinations.\n\nExample: \n\n```\n\"1.1.1.1\": Allows all ports and protocols to 1.1.1.1/32\n\"1.1.1.1 54/any\": Allows both tcp and udp to 1.1.1.1/32\n```\n\n### Single Service\n\nExample:\n```\n192.168.1.1 22/tcp 53/udp: Fairly self explanatory, allows you to hit 22/tcp and 53/udp on a host\n1.1.1.1 icmp: As icmp doesnt have ports really you dont need it either\n```\n\n### Ranges\nYou can also define a range of ports with a protocol. wag requires that the lower port is first. \n\nExample:\n```\n192.168.1.1 22-1024/tcp 23-53/any: Format is low port-high port/service\n```\n\n\n# Limitations\n- Only supports clients with one `AllowedIP`, which is perfect for site to site, or client -\u003e server based architecture.  \n- IPv4 only.\n- Linux only\n- Very Modern kernel 5.9+ at least (\u003e5.9 allows loops in ebpf and `bpf_link`)\n\n\n# Development \n\n## Custom templates\n\nWith the introduction of the `MFATemplatesDirectory` option, you can now specify a directory that contains template files for customising the MFA entry, registration and wireguard config file.  \nAn example of all these files can be found in the embedded variants here: `internal/webserver/resources/templates`.  \n\nWhen the option is set, you must define *all* the files this guide is a brief description of what each file is:  \n`interface.tmpl`: The wireguard configuration file that is served to clients  \n`oidc_error.html`: If a users login to the oidc provider as some issue (i.e user isnt registered for the device)  \n`prompt_mfa_totp.html`: Page for taking TOTP code entry  \n`prompt_mfa_webauthn.html`: Page for webauthn entry  \n`qrcode_registration.html`: When a client registers with the `?type=mobile` option set, shows a QR code for the wireguard app on android/ios to simply registration  \n`register_mfa_totp.html`: Registration for TOTP that should show a QR code  \n`register_mfa_webauth.html`: Page to do webauthn registration  \n`register_mfa.html`: If multiple MFA methods are registered this page is displayed giving the user an option of what method to use  \n`success.html`: This page is not a template, and is displayed when a user is successfully authed, or if they attempt to access the authorisation endpoint while being authorised   \n\n\n## Testing\n```sh\ncd internal/router\nsudo go test -v .\n```\n\nSudo is required to load the eBPF program into the kernel.\n\n## Building a release\n\n\nIf you havent build the release docker image (used because it has a stable version of glibc) do the following:\n```\ncd release_builder\nsudo docker build -t wag_builder .\ncd ..\n\nmake docker\n```\n\n## External contributions\n\nIf you're looking to add your own features, or bug fixes to wag (thank you!). Please make sure that you've written a test for your changes if possible.  \nThere are a few `_test.go` files around that give example on how to do this.  \n\nThen open a pull request and we can discuss it there.  \n\n# Donations and Support\nIf you like `wag` and use it to support your work flow, consider donating to the project. Your donations go directly towards the time and effort I put in, and the amount of support I can provide. \n\nYou can do this by either using the `Support` button on the side or the cryptocurrency wallets detailed below.\n  \nMonero (XMR):  \n`8A8TRqsBKpMMabvt5RxMhCFWcuCSZqGV5L849XQndZB4bcbgkenH8KWJUXinYbF6ySGBznLsunrd1WA8YNPiejGp3FFfPND`  \n  \nBitcoin (BTC):  \n`bc1qm9e9sfrm7l7tnq982nrm6khnsfdlay07h0dxfr`  \n","funding_links":["https://github.com/sponsors/NHAS"],"categories":["Go"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNHAS%2Fwag","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FNHAS%2Fwag","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNHAS%2Fwag/lists"}