Skip to content

Commit

Permalink
glinet: init
Browse files Browse the repository at this point in the history
  • Loading branch information
mazzz1y committed Dec 22, 2024
1 parent be2cb8c commit 20d4fee
Show file tree
Hide file tree
Showing 16 changed files with 456 additions and 54 deletions.
12 changes: 6 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
FROM golang:1.23-alpine3.20 AS build
FROM golang:1.23-alpine3.21 AS build

ENV CGO_ENABLED 0
ENV CGO_ENABLED=0
COPY . /app

ARG VERSION
RUN cd /app && \
go build -ldflags="-s -w -X main.version=$VERSION" -trimpath -o /keenetic-auth-gw ./cmd/keenetic-auth-gw
go build -ldflags="-s -w -X main.version=$VERSION" -trimpath -o /router-auth-gw ./cmd/router-auth-gw

FROM alpine:3.20
COPY --from=build /keenetic-auth-gw /keenetic-auth-gw
FROM alpine:3.21
COPY --from=build /router-auth-gw /router-auth-gw
USER 1337

CMD ["/keenetic-auth-gw", "-c", "config.yaml", "start"]
CMD ["/router-auth-gw", "-c", "config.yaml", "start"]
53 changes: 35 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
# Keenetic Auth Gateway
# Router Auth Gateway

Authentication gateway for Keenetic devices.
Authentication gateway for router devices.

## Features

This app provides numerous options and supports multiple devices for complex configurations.
With this tool, you can:

- **Multiple Server Instances**: Supports multiple entry points, each listening on different local ports with unique settings.
- **Authentication Methods**: Includes basic authentication and forwarded authentication headers, enabling integration with OAuth2 Proxy, Authelia, Authentik, and other SSO solutions.
- **Device Configuration**: Supports multiple device configurations.
- **Proxy**: Configurable HTTP/SOCKS proxy for each device or globally via http_proxy variables
- Bypass router authentication.
- Use basic authentication instead of proprietary authentication mechanisms for your router.
- Utilize SSO for all of your devices and map your OAuth users to internal router users (using forwarded auth headers).
- Expose only a single endpoint (e.g., for Wake-on-LAN).

This app provides numerous options and supports multiple devices for complex configurations:

- Multiple Server Instances: Supports multiple entry points, each listening on different local ports with unique settings.
- Authentication Methods: Includes basic authentication and forwarding of authentication headers, enabling integration with OAuth2 Proxy, Authelia, Authentik, and other SSO solutions.
- Device Configuration: Supports multiple device configurations.
- Proxy: Configurable HTTP/SOCKS proxy for each device or globally via `http_proxy` variables.

Currently supported devices:
- [Keenetic](https://keenetic.com)
- [GL.iNet](https://www.gl-inet.com)

This app is a fork of `keenetic-auth-gw`, and I decided to add support for other devices. It is open for PRs for additional device support.

*I created this app for personal use, so there may be some issues or inconsistencies, and the design can change at any time*

## Usage
### Docker
```yaml
services:
keenetic-auth-gw:
image: "ghcr.io/mazzz1y/keenetic-auth-gw:latest"
router-auth-gw:
image: "ghcr.io/mazzz1y/router-auth-gw:latest"
ports:
- 8080:8080
volumes:
Expand All @@ -27,44 +42,46 @@ services:
```yaml
entrypoints:
- listen: "127.0.0.1:8080"
device_tag: keenetic
device_tag: keenetic-home
basic_auth:
- username: xxx
password: xxx
allowed_endpoints:
- /rci/ip/hotspot/wake

- listen: "127.0.0.1:8081"
device_tag: keenetic
device_tag: keenetic-home
read_only: true # Allows only GET requests

- listen: "127.0.0.1:8082"
device_tag: keenetic-remote
device_tag: glinet-remote
# For use with OAuth2 Proxy, Authelia, and other authorization proxies.
# Requests with a valid username in the header will be forwarded without additional authorization.
# If the username is not valid, a 403 error will be returned.
forward_auth:
header: X-Forwarded-User
# Mapping of incoming usernames to internal usernames.
# For example, the user coming from the header 'mazzz1y' will be logged as the 'admin' internal user.
# For example, the user coming from the header 'mazzz1y' will be logged as the 'root' internal user.
mapping:
mazzz1y: admin
mazzz1y: root

devices:
- tag: keenetic-home
url: http://192.168.1.1
proxy_url: socks5://127.0.0.1:1085
# Users are primarily for entry points with forwarded auth header.
# In other cases, the first user in the list will be used.
users:
- username: admin
password: xxx
- username: user
password: xxx

- tag: keenetic-remote
url: https://remote-keenetic.com
- tag: glinet-remote
url: https://remote-glinet.com
# Users are primarily for entry points with forwarded auth header.
# In other cases, the first user in the list will be used.
users:
- username: admin
password: xxx
- username: user
password: xxx
```
8 changes: 4 additions & 4 deletions cmd/keenetic-auth-gw/main.go → cmd/router-auth-gw/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import (
"os"
"sync"

"github.com/mazzz1y/keenetic-auth-gw/internal/config"
"github.com/mazzz1y/keenetic-auth-gw/internal/device"
"github.com/mazzz1y/keenetic-auth-gw/internal/entrypoint"
"github.com/mazzz1y/router-auth-gw/internal/config"
"github.com/mazzz1y/router-auth-gw/internal/device"
"github.com/mazzz1y/router-auth-gw/internal/entrypoint"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
Expand All @@ -16,7 +16,7 @@ var version = "custom"

func main() {
app := &cli.App{
Name: "keenetic-auth-gw",
Name: "router-auth-gw",
Usage: "Proxy Gateway for Keenetic Routers",
Version: version,
Flags: []cli.Flag{
Expand Down
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/mazzz1y/keenetic-auth-gw
module github.com/mazzz1y/router-auth-gw

go 1.23.3

Expand All @@ -9,11 +9,15 @@ require (
gopkg.in/yaml.v3 v3.0.1
)

require golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 // indirect

require (
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gorilla/websocket v1.5.3
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/nathanaelle/password/v2 v2.0.1
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
Expand Down
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/nathanaelle/password/v2 v2.0.1 h1:ItoCTdsuIWzilYmllQPa3DR3YoCXcpfxScWLqr8Ii2s=
github.com/nathanaelle/password/v2 v2.0.1/go.mod h1:eaoT+ICQEPNtikBRIAatN8ThWwMhVG+r1jTw60BvPJk=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand All @@ -23,10 +27,17 @@ github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA=
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type EntrypointConfig struct {

type DeviceConfig struct {
Tag string `yaml:"tag"`
Type string `yaml:"type"`
URL string `yaml:"url"`
ProxyUrl string `yaml:"proxy_url,omitempty"`
Users []UserConfig `yaml:"users"`
Expand Down
8 changes: 5 additions & 3 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"os"
"testing"

"github.com/mazzz1y/keenetic-auth-gw/internal/config"
"github.com/mazzz1y/router-auth-gw/internal/config"

"github.com/stretchr/testify/assert"
)
Expand All @@ -24,6 +24,7 @@ entrypoints:
devices:
- tag: "device123"
url: "http://device.local"
type: "keenetic"
users:
- username: "user1"
password: "pass1"
Expand Down Expand Up @@ -56,8 +57,9 @@ func TestLoadConfig_Success(t *testing.T) {
},
}},
Devices: []config.DeviceConfig{{
Tag: "device123",
URL: "http://device.local",
Tag: "device123",
URL: "http://device.local",
Type: "keenetic",
Users: []config.UserConfig{{
Username: "user1",
Password: "pass1",
Expand Down
35 changes: 31 additions & 4 deletions internal/device/device.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package device

import (
"github.com/mazzz1y/keenetic-auth-gw/internal/config"
"github.com/mazzz1y/keenetic-auth-gw/pkg/keenetic"
"fmt"
"net/http"

"github.com/mazzz1y/router-auth-gw/internal/config"
"github.com/mazzz1y/router-auth-gw/pkg/glinet"
"github.com/mazzz1y/router-auth-gw/pkg/keenetic"
)

type Device struct {
Expand All @@ -12,13 +16,18 @@ type Device struct {

type User struct {
Name string
Client keenetic.ClientWrapper
Client ClientWrapper
}

type DeviceManager struct {
Devices map[string]Device
}

type ClientWrapper interface {
RequestWithAuth(method, endpoint, body string) (*http.Response, error)
Auth() error
}

func NewDeviceManager(cfg []config.DeviceConfig, auth bool) (*DeviceManager, error) {
deviceManager := &DeviceManager{
Devices: make(map[string]Device),
Expand All @@ -41,7 +50,10 @@ func NewDeviceManager(cfg []config.DeviceConfig, auth bool) (*DeviceManager, err
func initClients(c config.DeviceConfig, auth bool) ([]User, error) {
users := make([]User, len(c.Users))
for i, v := range c.Users {
client := keenetic.NewClient(c.URL, c.ProxyUrl, v.Username, v.Password)
client, err := createClient(c.Type, c.URL, c.ProxyUrl, v.Username, v.Password)
if err != nil {
return nil, err
}

if auth {
if err := client.Auth(); err != nil {
Expand All @@ -54,5 +66,20 @@ func initClients(c config.DeviceConfig, auth bool) ([]User, error) {
Client: client,
}
}

return users, nil
}

func createClient(deviceType, url, proxyUrl, username, password string) (ClientWrapper, error) {
switch deviceType {
case "keenetic":
return keenetic.NewClient(url, proxyUrl, username, password), nil
case "glinet":
return glinet.NewClient(url, proxyUrl, username, password), nil
default:
if deviceType == "" {
return nil, fmt.Errorf("you must specify a device type")
}
return nil, fmt.Errorf("unsupported device type: %s", deviceType)
}
}
8 changes: 5 additions & 3 deletions internal/device/device_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package device_test
import (
"testing"

"github.com/mazzz1y/keenetic-auth-gw/internal/config"
"github.com/mazzz1y/keenetic-auth-gw/internal/device"
"github.com/mazzz1y/router-auth-gw/internal/config"
"github.com/mazzz1y/router-auth-gw/internal/device"

"github.com/stretchr/testify/assert"
)
Expand All @@ -15,6 +15,7 @@ var mockConfig = &config.Config{
Tag: "Device1",
URL: "http://device1.local",
ProxyUrl: "http://proxy.local",
Type: "keenetic",
Users: []config.UserConfig{
{Username: "user1", Password: "pass1"},
{Username: "user2", Password: "pass2"},
Expand All @@ -26,7 +27,8 @@ var mockConfig = &config.Config{
func TestNewDeviceManager(t *testing.T) {
t.Run("Success", func(t *testing.T) {

manager, _ := device.NewDeviceManager(mockConfig.Devices, false)
manager, err := device.NewDeviceManager(mockConfig.Devices, false)
assert.NoError(t, err)
assert.Equal(t, 1, len(manager.Devices))
assert.Equal(t, "Device1", manager.Devices["Device1"].Tag)
assert.Equal(t, 2, len(manager.Devices["Device1"].Users))
Expand Down
7 changes: 3 additions & 4 deletions internal/entrypoint/entrypoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import (
"net/http"
"strings"

"github.com/mazzz1y/keenetic-auth-gw/internal/device"
"github.com/mazzz1y/keenetic-auth-gw/pkg/keenetic"
"github.com/mazzz1y/router-auth-gw/internal/device"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
Expand Down Expand Up @@ -49,7 +48,7 @@ func (e *Entrypoint) Start() error {
}

func (e *Entrypoint) handleRequest(w http.ResponseWriter, r *http.Request) {
client, ok := r.Context().Value(clientContextKey).(keenetic.ClientWrapper)
client, ok := r.Context().Value(clientContextKey).(device.ClientWrapper)
if !ok {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
e.log.Error().Msg("getting user context error")
Expand All @@ -64,7 +63,7 @@ func (e *Entrypoint) handleRequest(w http.ResponseWriter, r *http.Request) {
e.handleProxyRequest(w, r, client)
}

func (e *Entrypoint) handleProxyRequest(w http.ResponseWriter, r *http.Request, c keenetic.ClientWrapper) {
func (e *Entrypoint) handleProxyRequest(w http.ResponseWriter, r *http.Request, c device.ClientWrapper) {
proxyBody, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
Expand Down
2 changes: 1 addition & 1 deletion internal/entrypoint/entrypoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"net/http/httptest"
"testing"

"github.com/mazzz1y/keenetic-auth-gw/internal/device"
"github.com/mazzz1y/router-auth-gw/internal/device"

"github.com/stretchr/testify/assert"
)
Expand Down
Loading

0 comments on commit 20d4fee

Please sign in to comment.