diff --git a/README.md b/README.md index 1d134387..5e5919d3 100644 --- a/README.md +++ b/README.md @@ -378,6 +378,7 @@ A built-in HTTP server listens on port `8000` to modify the state of the contain - `http://:8000/openvpn/actions/restart` restarts the openvpn process - `http://:8000/unbound/actions/restart` re-downloads the DNS files (crypto and block lists) and restarts the unbound process - `http://:8000/openvpn/portforwarded` to get your port forwarded as JSON. You can use **jq** to parse JSON on linux. +- `http://:8000/openvpn/settings` to get your openvpn settings as a JSON object. ## Development and contributing diff --git a/cmd/gluetun/main.go b/cmd/gluetun/main.go index b7b189cf..c9907ef9 100644 --- a/cmd/gluetun/main.go +++ b/cmd/gluetun/main.go @@ -129,6 +129,7 @@ func _main(background context.Context, args []string) int { ovpnConf, firewallConf, logger, client, fileManager, streamMerger, fatalOnError) restartOpenvpn := openvpnLooper.Restart portForward := openvpnLooper.PortForward + getOpenvpnSettings := openvpnLooper.GetSettings getPortForwarded := openvpnLooper.GetPortForwarded // wait for restartOpenvpn go openvpnLooper.Run(ctx, wg) @@ -177,7 +178,7 @@ func _main(background context.Context, args []string) int { } }() - httpServer := server.New("0.0.0.0:8000", logger, restartOpenvpn, restartUnbound, getPortForwarded) + httpServer := server.New("0.0.0.0:8000", logger, restartOpenvpn, restartUnbound, getOpenvpnSettings, getPortForwarded) go httpServer.Run(ctx, wg) // Start openvpn for the first time diff --git a/internal/models/alias.go b/internal/models/alias.go index 65c88c46..e0aa2087 100644 --- a/internal/models/alias.go +++ b/internal/models/alias.go @@ -1,5 +1,10 @@ package models +import ( + "fmt" + "strings" +) + type ( // VPNDevice is the device name used to tunnel using Openvpn VPNDevice string @@ -14,7 +19,45 @@ type ( // TinyProxyLogLevel is the log level for TinyProxy TinyProxyLogLevel string // VPNProvider is the name of the VPN provider to be used - VPNProvider string // TODO + VPNProvider string // NetworkProtocol contains the network protocol to be used to communicate with the VPN servers NetworkProtocol string ) + +func marshalJSONString(s string) (data []byte, err error) { + return []byte(fmt.Sprintf("%q", s)), nil +} + +func unmarshalJSONString(data []byte) (s string) { + s = string(data) + s = strings.TrimPrefix(s, "\"") + s = strings.TrimSuffix(s, "\"") + return s +} + +func (v *VPNProvider) MarshalJSON() ([]byte, error) { + return marshalJSONString(string(*v)) +} + +func (v *VPNProvider) UnmarshalJSON(data []byte) error { + *v = VPNProvider(unmarshalJSONString(data)) + return nil +} + +func (n *NetworkProtocol) MarshalJSON() ([]byte, error) { + return marshalJSONString(string(*n)) +} + +func (n *NetworkProtocol) UnmarshalJSON(data []byte) error { + *n = NetworkProtocol(unmarshalJSONString(data)) + return nil +} + +func (f *Filepath) MarshalJSON() ([]byte, error) { + return marshalJSONString(string(*f)) +} + +func (f *Filepath) UnmarshalJSON(data []byte) error { + *f = Filepath(unmarshalJSONString(data)) + return nil +} diff --git a/internal/models/alias_test.go b/internal/models/alias_test.go new file mode 100644 index 00000000..b17ce091 --- /dev/null +++ b/internal/models/alias_test.go @@ -0,0 +1,41 @@ +package models + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_VPNProvider_JSON(t *testing.T) { + t.Parallel() + v := VPNProvider("name") + data, err := v.MarshalJSON() + require.NoError(t, err) + assert.Equal(t, []byte{0x22, 0x6e, 0x61, 0x6d, 0x65, 0x22}, data) + err = v.UnmarshalJSON(data) + require.NoError(t, err) + assert.Equal(t, VPNProvider("name"), v) +} + +func Test_NetworkProtocol_JSON(t *testing.T) { + t.Parallel() + v := NetworkProtocol("name") + data, err := v.MarshalJSON() + require.NoError(t, err) + assert.Equal(t, []byte{0x22, 0x6e, 0x61, 0x6d, 0x65, 0x22}, data) + err = v.UnmarshalJSON(data) + require.NoError(t, err) + assert.Equal(t, NetworkProtocol("name"), v) +} + +func Test_Filepath_JSON(t *testing.T) { + t.Parallel() + v := Filepath("name") + data, err := v.MarshalJSON() + require.NoError(t, err) + assert.Equal(t, []byte{0x22, 0x6e, 0x61, 0x6d, 0x65, 0x22}, data) + err = v.UnmarshalJSON(data) + require.NoError(t, err) + assert.Equal(t, Filepath("name"), v) +} diff --git a/internal/models/selection.go b/internal/models/selection.go index fe538e34..0fc54fdb 100644 --- a/internal/models/selection.go +++ b/internal/models/selection.go @@ -8,48 +8,48 @@ import ( // ProviderSettings contains settings specific to a VPN provider type ProviderSettings struct { - Name VPNProvider - ServerSelection ServerSelection - ExtraConfigOptions ExtraConfigOptions - PortForwarding PortForwarding + Name VPNProvider `json:"name"` + ServerSelection ServerSelection `json:"serverSelection"` + ExtraConfigOptions ExtraConfigOptions `json:"extraConfig"` + PortForwarding PortForwarding `json:"portForwarding"` } type ServerSelection struct { //nolint:maligned // Common - Protocol NetworkProtocol - TargetIP net.IP + Protocol NetworkProtocol `json:"networkProtocol"` + TargetIP net.IP `json:"targetIP,omitempty"` // Cyberghost, PIA, Surfshark, Windscribe, Vyprvpn, NordVPN - Region string + Region string `json:"region"` // Cyberghost - Group string + Group string `json:"group"` // Mullvad - Country string - City string - ISP string - Owned bool + Country string `json:"country"` + City string `json:"city"` + ISP string `json:"isp"` + Owned bool `json:"owned"` // Mullvad, Windscribe - CustomPort uint16 - - // PIA - EncryptionPreset string + CustomPort uint16 `json:"customPort"` // NordVPN - Number uint16 + Number uint16 `json:"number"` + + // PIA + EncryptionPreset string `json:"encryptionPreset"` } type ExtraConfigOptions struct { - ClientKey string // Cyberghost - EncryptionPreset string // PIA + ClientKey string `json:"-"` // Cyberghost + EncryptionPreset string `json:"encryptionPreset"` // PIA } // PortForwarding contains settings for port forwarding type PortForwarding struct { - Enabled bool - Filepath Filepath + Enabled bool `json:"enabled"` + Filepath Filepath `json:"filepath"` } func (p *PortForwarding) String() string { diff --git a/internal/server/openvpn.go b/internal/server/openvpn.go index 0f28e194..94e1074a 100644 --- a/internal/server/openvpn.go +++ b/internal/server/openvpn.go @@ -21,3 +21,16 @@ func (s *server) handleGetPortForwarded(w http.ResponseWriter) { } } +func (s *server) handleGetOpenvpnSettings(w http.ResponseWriter) { + settings := s.getOpenvpnSettings() + data, err := json.Marshal(settings) + if err != nil { + s.logger.Warn(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + if _, err := w.Write(data); err != nil { + s.logger.Warn(err) + w.WriteHeader(http.StatusInternalServerError) + } +} diff --git a/internal/server/server.go b/internal/server/server.go index 48ab5043..905d528e 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -8,6 +8,7 @@ import ( "time" "github.com/qdm12/golibs/logging" + "github.com/qdm12/private-internet-access-docker/internal/settings" ) type Server interface { @@ -15,21 +16,23 @@ type Server interface { } type server struct { - address string - logger logging.Logger - restartOpenvpn func() - restartUnbound func() - getPortForwarded func() uint16 + address string + logger logging.Logger + restartOpenvpn func() + restartUnbound func() + getOpenvpnSettings func() settings.OpenVPN + getPortForwarded func() uint16 } func New(address string, logger logging.Logger, restartOpenvpn, restartUnbound func(), - getPortForwarded func() uint16) Server { + getOpenvpnSettings func() settings.OpenVPN, getPortForwarded func() uint16) Server { return &server{ - address: address, - logger: logger.WithPrefix("http server: "), - restartOpenvpn: restartOpenvpn, - restartUnbound: restartUnbound, - getPortForwarded: getPortForwarded, + address: address, + logger: logger.WithPrefix("http server: "), + restartOpenvpn: restartOpenvpn, + restartUnbound: restartUnbound, + getOpenvpnSettings: getOpenvpnSettings, + getPortForwarded: getPortForwarded, } } @@ -66,6 +69,8 @@ func (s *server) makeHandler() http.HandlerFunc { s.restartUnbound() case "/openvpn/portforwarded": s.handleGetPortForwarded(w) + case "/openvpn/settings": + s.handleGetOpenvpnSettings(w) default: routeDoesNotExist(s.logger, w, r) } diff --git a/internal/settings/openvpn.go b/internal/settings/openvpn.go index f414ce5d..db61211d 100644 --- a/internal/settings/openvpn.go +++ b/internal/settings/openvpn.go @@ -11,13 +11,13 @@ import ( // OpenVPN contains settings to configure the OpenVPN client type OpenVPN struct { - User string - Password string - Verbosity int - Root bool - Cipher string - Auth string - Provider models.ProviderSettings + User string `json:"user"` + Password string `json:"-"` + Verbosity int `json:"verbosity"` + Root bool `json:"runAsRoot"` + Cipher string `json:"cipher"` + Auth string `json:"auth"` + Provider models.ProviderSettings `json:"provider"` } // GetOpenVPNSettings obtains the OpenVPN settings using the params functions diff --git a/internal/settings/openvpn_test.go b/internal/settings/openvpn_test.go new file mode 100644 index 00000000..8b0f3861 --- /dev/null +++ b/internal/settings/openvpn_test.go @@ -0,0 +1,27 @@ +package settings + +import ( + "encoding/json" + "testing" + + "github.com/qdm12/private-internet-access-docker/internal/models" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_OpenVPN_JSON(t *testing.T) { + t.Parallel() + in := OpenVPN{ + Root: true, + Provider: models.ProviderSettings{ + Name: "name", + }, + } + data, err := json.Marshal(in) + require.NoError(t, err) + assert.Equal(t, `{"user":"","verbosity":0,"runAsRoot":true,"cipher":"","auth":"","provider":{"name":"name","serverSelection":{"networkProtocol":"","region":"","group":"","country":"","city":"","isp":"","owned":false,"customPort":0,"number":0,"encryptionPreset":""},"extraConfig":{"encryptionPreset":""},"portForwarding":{"enabled":false,"filepath":""}}}`, string(data)) + var out OpenVPN + err = json.Unmarshal(data, &out) + require.NoError(t, err) + assert.Equal(t, in, out) +}