Compare commits

...

141 Commits

Author SHA1 Message Date
Quentin McGaw
8f04a05b45 fix(vpnsecure): allow empty OpenVPN user+password 2022-09-11 20:18:42 +00:00
Quentin McGaw
aa53436e56 chore(lint): upgrade golangci-lint to v1.49.0
- Add linter `interfacebloat` and fix code issues
- Add linter `reassign`
- Remove deprecated linter `nosnakecase`
2022-08-24 21:48:24 +00:00
Quentin McGaw
8dfaebc737 chore(all): remove deprecated io/ioutil import 2022-08-24 21:43:37 +00:00
Quentin McGaw
062b6a276c fix(settings): read PEM files but b64 env vars
- Extract base64 data from PEM files and secret files
- Environment variables are not PEM encoded and only the base64 data
- Affects OpenVPN certificate, key and encrypted key
2022-08-24 17:48:45 +00:00
Quentin McGaw
647cd07de7 feat(surfshark): update servers data 2022-08-24 13:04:34 +00:00
Quentin McGaw
a530c84c5f fix(surshark): remove invalid retro-servers 2022-08-24 13:04:18 +00:00
Quentin McGaw
0bb320065e feat(server): patch VPN settings
- `PUT` at `/v1/vpn/settings`
- Undocumented, experimental for now
2022-08-21 23:36:48 +00:00
Quentin McGaw
d685d78e74 feat(server): add vpn route to replace /openvpn 2022-08-21 23:29:25 +00:00
Quentin McGaw
48896176e5 chore(server): do not redact openvpn credentials from response 2022-08-21 22:04:04 +00:00
Quentin McGaw
54dcf28b31 chore(server): replace 404 with 401 for unsupported routes and methods 2022-08-21 22:02:06 +00:00
Quentin McGaw
f8bf32bb34 docs(readme): add slickvpn to list of providers 2022-08-16 00:02:34 +00:00
Quentin McGaw
748923021c fix(ci): permissions for labels workflow 2022-08-15 23:58:33 +00:00
Quentin McGaw
a182e3503b feat: add VPNsecure.me support (#848)
- `OPENVPN_ENCRYPTED_KEY` environment variable 
- `OPENVPN_ENCRYPTED_KEY_SECRETFILE` environment variable 
- `OPENVPN_KEY_PASSPHRASE` environment variable 
- `OPENVPN_KEY_PASSPHRASE_SECRETFILE` environment variable 
- `PREMIUM_ONLY` environment variable
- OpenVPN user and password not required for vpnsecure provider
2022-08-15 16:54:58 -07:00
Quentin McGaw
991cfb8659 chore(ci): limit labels workflow to not forked 2022-08-15 23:53:29 +00:00
Richard Hodgson
d0dfc21e2b feat: SlickVPN Support (#961)
- `internal/updater/html` package
- Add unit tests for slickvpn updating code
- Change shared html package to be more share-able
- Split html utilities in multiple files
- Fix processing .ovpn files with prefix space

Authored by @Rohaq 
Co-authored-by: Quentin McGaw <quentin.mcgaw@gmail.com>
2022-08-15 08:25:06 -07:00
dependabot[bot]
617bd0c600 Chore(deps): Bump github.com/stretchr/testify from 1.7.5 to 1.8.0 (#1052) 2022-08-13 12:16:57 -07:00
dependabot[bot]
349b5429ba Chore(deps): Bump docker/build-push-action from 3.1.0 to 3.1.1 (#1098) 2022-08-13 12:16:44 -07:00
Quentin McGaw
8db2944749 chore(settings): OpenVPN ClientCrt -> Cert 2022-08-13 18:59:07 +00:00
Quentin McGaw
5986432a22 chore(settings): OpenVPN ClientKey -> Key 2022-08-13 18:58:09 +00:00
Quentin McGaw
652daec509 Change: OPENVPN_CLIENTKEY -> OPENVPN_KEY
- No breaking change since this was undocumented
2022-08-13 18:56:37 +00:00
Quentin McGaw
f94d4b761a Change: OPENVPN_CLIENTCRT -> OPENVPN_CERT
- No breaking change since this was undocumented
2022-08-13 18:55:29 +00:00
Quentin McGaw
1ab74e6bb3 chore: OpenVPN user and password as nullable
- Username and password can be the empty string for custom provider
2022-08-13 18:01:26 +00:00
dependabot[bot]
8e101d49a1 Chore(deps): Bump github.com/breml/rootcerts from 0.2.4 to 0.2.6 (#1058) 2022-08-12 17:01:45 -07:00
Quentin McGaw
7c08e8f607 chore(lint): add asasalint and usestdlibvars 2022-08-12 23:54:20 +00:00
Quentin McGaw
a4caa61c47 chore(lint): add nosnakecase linter 2022-08-12 23:53:47 +00:00
Quentin McGaw
ebae167815 chore(lint): golangci-lint v1.47.2 -> v1.48.0
- Remove deprecated `ifshort` linter
- Fix bad `//nolint:gomnd` comment
2022-08-12 23:52:30 +00:00
Quentin McGaw
a6f00f2fb2 chore(lint): upgrade golangci-lint to v1.47.2
- Fix Slowloris attacks on HTTP servers
- Force set default of 5 minutes for pprof read timeout
- Change `ShutdownTimeout` to time.Duration since it cannot be set to 0
2022-08-01 21:09:16 +00:00
dependabot[bot]
877617cc53 Chore(deps): Bump docker/build-push-action from 3.0.0 to 3.1.0 (#1073) 2022-07-23 20:08:34 -07:00
Quentin McGaw
2800588ef7 feat(expressvpn): update servers data 2022-07-18 18:01:08 +00:00
Quentin McGaw
f5efa42aaf chore(lint): remove some linters
- remove duplicate `predeclared`
- remove commented `varnamelen` and `wrapcheck`
2022-07-17 23:11:42 +00:00
Hey
10bd0e1505 fix(readme): typo sercice to service (#1067) 2022-07-15 19:52:04 -04:00
Quentin McGaw
a4c80b3045 chore(ci): add mocks check
- Check for missing `//go:generate` comments
- Check for outdated mocks
2022-07-04 00:39:01 +00:00
Quentin McGaw
dbb71bd695 chore(mocks): use common mocks for ivpn and ipvanish 2022-07-04 00:34:48 +00:00
dependabot[bot]
a544f6e604 Chore(deps): Bump github.com/breml/rootcerts from 0.2.3 to 0.2.4 (#1033) 2022-07-03 16:50:38 -07:00
dependabot[bot]
a18e026b70 Chore(deps): Bump github.com/stretchr/testify from 1.7.2 to 1.7.5 (#1042) 2022-07-03 16:50:27 -07:00
Quentin McGaw
0413a0a1ab chore(ci): rework docker hub description workflow
- Run only on base repository
- Rename job from `dockerHubDescription` to `docker-hub-description`
- Limit permissions of job to read only
- Remove unneeded names for steps
2022-07-03 14:31:49 +00:00
Quentin McGaw
cb6e9cb761 docs(readme): add links to add a provider 2022-07-03 13:39:47 +00:00
Quentin McGaw
420ae40901 feat(dev): Add provider example package 2022-07-02 21:04:57 +00:00
Quentin McGaw
34e67f9f99 chore(markdown): alphabetically sorted headers 2022-07-02 20:58:43 +00:00
Quentin McGaw
18c53aa597 docs(readme): simplify heading description 2022-07-02 20:58:43 +00:00
Quentin McGaw
6d2f9b9508 chore(updater): check servers have minimal information 2022-07-02 20:58:43 +00:00
Quentin McGaw
6826b05d58 chore(all): remove all package comments 2022-07-02 20:58:43 +00:00
barino28
9f959dbc6a fix(expressvpn): OpenVPN fragment option and add ciphers (#1047)
* Fragment was defined in `OpenVPNProviderSettings` but was not written to the OpenVPN configuration file.
* Added two additional ciphers to the configuration for ExpressVPN

Authored-by: barino86 <barino@mac.com>
2022-06-29 05:23:16 -07:00
Quentin McGaw
87dbae5745 hotfix(fastestvpn): re-fix Openvpn configuration
- add `auth sha256` option
- remove `remote-cert-tls server` option
2022-06-26 21:29:05 +00:00
Quentin McGaw
037f19e852 hotfix(publicip): revert back JSON to public_ip 2022-06-26 18:08:11 +00:00
Quentin McGaw
62ad8bcd8f fix(pia): set port forward file owned with PUID and PGID 2022-06-25 15:44:29 +00:00
Quentin McGaw
2805c3388a hotfix(fastestvpn): add remote-cert-tls server 2022-06-25 15:16:38 +00:00
Quentin McGaw
535297dcf5 chore: extract.PEM replaces PEM parse functions 2022-06-24 23:10:00 +00:00
Quentin McGaw
b3b6933ef4 chore(lint): review exclude rules 2022-06-20 13:36:24 +00:00
Quentin McGaw
edbbcc041a fix(protonvpn): set free field for free servers 2022-06-18 18:30:27 +00:00
Quentin McGaw
d430ebc34f feat(protonvpn): update servers data 2022-06-18 18:30:05 +00:00
Quentin McGaw
0e9abc6e1d chore(tests): modify JSON tests to not need all providers listed 2022-06-18 15:08:59 +00:00
Quentin McGaw
0c0dd10766 chore(dev): add VSCode launch.json
- Credits to @Rohaq
2022-06-18 00:17:09 +00:00
Quentin McGaw
75454be6b6 fix(pprof): override operation in global settings 2022-06-18 00:16:14 +00:00
Quentin McGaw
4952e3b74e docs(bug): fix render of logs to be plain text 2022-06-18 00:15:29 +00:00
Quentin McGaw
04b34a266c chore(deps): update go4.org/unsafe/assume-no-moving-gc
- Allow development on Go 1.18 without `ASSUME_NO_MOVING_GC_UNSAFE_RISK_IT_WITH=go1.18`
2022-06-17 23:59:39 +00:00
Quentin McGaw
89b6a031b0 chore(vpn): add check for empty public key for Wireguard 2022-06-12 15:59:48 +00:00
Quentin McGaw
d4c6a9bdb5 feat(storage): log time difference as a friendly duration 2022-06-12 15:35:04 +00:00
Quentin McGaw
cdc29d48b7 chore(ci): build PR images as pr-N (#1026)
- Remove event validity check (unneeded and was buggy)
- Remove `branch` metadata trigger since it conflicts with the PR trigger
2022-06-12 08:33:16 -07:00
dependabot[bot]
f4b464a7cf Chore(deps): Bump github.com/stretchr/testify from 1.7.1 to 1.7.2 (#1016) 2022-06-12 08:31:53 -07:00
dependabot[bot]
76690d3add Chore(deps): Bump crazy-max/ghaction-github-labeler from 3 to 4 (#1007) 2022-06-12 07:07:25 -07:00
Quentin McGaw
9898387579 feat(updater): Configurable min ratio
- `UPDATER_MIN_RATIO` variable
- `-minratio` flag for CLI operation
2022-06-12 14:03:00 +00:00
Quentin McGaw
1ea15a1a13 chore(protonvpn): document to remove SERVER_NAMES 2022-06-12 01:58:46 +00:00
Quentin McGaw
bda6707685 chore(all): remove unexported interfaces 2022-06-12 01:15:14 +00:00
Quentin McGaw
89277828ac chore(publicip): internal/publicip/ipinfo package 2022-06-12 01:11:22 +00:00
Quentin McGaw
83b4a3fe55 chore(publicip): refactoring
- Exported `Fetcher` interface
- Inject `Fetcher` to publicip loop and updaters
- Get public IP and information at the same time
- Only query ipinfo.io
- Make `MultiInfo` part of the `Fetch` object
2022-06-12 00:46:08 +00:00
Quentin McGaw
45c9e780c0 chore(updater): rename presolver to parallelResolver 2022-06-11 20:12:35 +00:00
Quentin McGaw
33b8f5f596 chore(settings): updater DNS address as string 2022-06-11 20:11:20 +00:00
Quentin McGaw
447a7c9891 updater: refactoring and set DNS server correctly
- Fix CLI operation not setting DNS server
- Fix periodic operation not setting DNS server
- Set DNS address for resolution once at start for both CLI and periodic operation
- Inject resolver to each provider instead of creating it within
- Use resolver settings on every call to `.Resolve` method, instead of passing it to constructor
- Move out minServers check from resolver
2022-06-11 19:47:46 +00:00
Quentin McGaw
1bd355ab96 chore(lint): add ireturn linter 2022-06-11 01:34:45 +00:00
Quentin McGaw
578ef768ab chore(all): return concrete types, accept interfaces
- Remove exported interfaces unused locally
- Define interfaces to accept arguments
- Return concrete types, not interfaces
2022-06-11 01:34:30 +00:00
Quentin McGaw
0378fe4a7b chore(all): Providers containing all provider objects
- Share the same providers for updater and vpn
- Initialise all providers at start
- Get from `Providers` instead of constructing on every run
2022-06-10 00:47:56 +00:00
Quentin McGaw
ebd94723c1 chore(updater): incorporate FetchServers method in Provider interface
- Each provider interface can now fetch updated servers data
- Rename each provider updater subpackage name to `updater`
- Updater constructor does not take a settings struct
- Updater update method takes in a slice of provider strings
2022-06-09 23:47:41 +00:00
Quentin McGaw
11b55abff3 fix(protonvpn): remove duplicate entry IPs 2022-06-09 22:11:40 +00:00
Quentin McGaw
7f32b43895 fix(pia): load custom PIA certificate for API 2022-06-09 17:11:24 +00:00
Quentin McGaw
899f10c35e chore(resolver): export structs instead of interfaces 2022-06-09 17:11:24 +00:00
Quentin McGaw
415cb7a945 chore(updater): create resolver in provider updater
- Pass min servers to resolve call
- Set settings when constructing resolver
- Construct resolver in each provider updater
- No more common resolver for all providers
2022-06-09 17:11:24 +00:00
Quentin McGaw
e37f557cd5 chore(provider): add Name() method per provider 2022-06-09 17:11:24 +00:00
Quentin McGaw
79f213d97a chore(updater): rename GetServers to FetchServers 2022-06-09 17:11:24 +00:00
Quentin McGaw
11e1c9f9bb feat(protonvpn): update hardcoded servers data 2022-06-09 16:41:04 +00:00
Quentin McGaw
3ff3816d77 fix(pia): restrict custom port choice 2022-06-09 16:36:59 +00:00
Quentin McGaw
c0bdae8baf fix(protonvpn): restrict custom port choice 2022-06-09 16:36:17 +00:00
Quentin McGaw
46e6bd16c9 fix(pia): remove duplicate log of pf data expiration 2022-06-09 16:34:28 +00:00
Quentin McGaw
5359257c65 hotfix(pia): port forwarding to use server name 2022-06-06 18:09:21 +00:00
Quentin McGaw
5e659dc5b3 feat(storage): add keep field for servers 2022-06-06 03:04:58 +00:00
Quentin McGaw
85e9d7d522 docs(maintenance): update document 2022-06-06 02:58:58 +00:00
Quentin McGaw
b71c8e58f4 fix(vpn): do not close wait error channel on consumer side 2022-06-06 02:56:40 +00:00
Quentin McGaw
e998372ce2 feat(ipvanish): update servers data and remove duplicates 2022-06-06 02:24:58 +00:00
Quentin McGaw
1216326867 chore(storage): common sorting for all servers 2022-06-06 02:24:54 +00:00
Quentin McGaw
f53f0cfffd feat(surfshark): update servers data 2022-06-06 01:41:00 +00:00
Quentin McGaw
f5f65d534a fix(ci): publish job trigger fixed 2022-06-05 16:01:40 +00:00
Quentin McGaw
684cef6eab hotfix(openvpn): openvpn udp specific lines added 2022-06-05 15:48:14 +00:00
Quentin McGaw
b4f6ae030d hotfix(purevpn): add missing key-direction 1 2022-06-05 15:44:33 +00:00
Quentin McGaw
e95c94294f feat(pia): update servers data 2022-06-05 15:20:03 +00:00
Quentin McGaw
36b504609b chore(all): memory and thread safe storage
- settings: get filter choices from storage for settings validation
- updater: update servers to the storage
- storage: minimal deep copying and data duplication
- storage: add merged servers mutex for thread safety
- connection: filter servers in storage
- formatter: format servers to Markdown in storage
- PIA: get server by name from storage directly
- Updater: get servers count from storage directly
- Updater: equality check done in storage, fix #882
2022-06-05 15:19:16 +00:00
Quentin McGaw
1e6b4ed5eb chore(provider): rename test functions to Test_Provider_GetConnection 2022-06-05 14:59:47 +00:00
Quentin McGaw
0549326dfb chore(updater): tiny code changes
- Remove unneeded ctx error check in cyberghost updating code
- Move global scope caser to function local scope
- Return error if updating a single provider in `UpdateServers`
- Add comments on different error paths in `UpdateServers`
2022-06-04 13:50:29 +00:00
Quentin McGaw
87c6ebe1c5 feat(purevpn): update servers data 2022-05-31 14:17:33 +00:00
Quentin McGaw
f0afac243b feat(privatevpn): update servers data 2022-05-31 14:16:41 +00:00
dependabot[bot]
53472077f4 Chore(deps): Bump docker/setup-buildx-action from 1 to 2 (#977) 2022-05-29 11:31:09 -07:00
dependabot[bot]
55afdf33e1 Chore(deps): Bump docker/setup-qemu-action from 1 to 2 (#978) 2022-05-29 11:28:05 -07:00
dependabot[bot]
d3c1f9263c Chore(deps): Bump docker/build-push-action from 2.10.0 to 3.0.0 (#979) 2022-05-29 11:27:55 -07:00
dependabot[bot]
6341d1dda6 Chore(deps): Bump docker/metadata-action from 3 to 4 (#980) 2022-05-29 11:27:44 -07:00
dependabot[bot]
e62e1883c2 Chore(deps): Bump docker/login-action from 1 to 2 (#981) 2022-05-29 11:27:33 -07:00
Quentin McGaw
501b98dbd3 chore(ci): skip workflow for required verify job 2022-05-29 17:33:35 +00:00
Derzsi Dániel
029fd1da1f feat(docker): upgrade Alpine from 3.15 to 3.16 (#1005) 2022-05-29 10:30:10 -07:00
Quentin McGaw
fd0267efef chore(ci): merge codeql job in CI workflow 2022-05-29 17:23:55 +00:00
Quentin McGaw
4414366370 chore(ci): restrict permissions to read actions+contents 2022-05-29 17:23:55 +00:00
Quentin McGaw
08553bc90b chore(ci): only publish image for qdm12/gluetun 2022-05-29 17:23:54 +00:00
Quentin McGaw
6f850c4ad4 chore(ci): merge dependabot and fork workflows in ci workflow 2022-05-29 17:23:48 +00:00
Quentin McGaw
8e1316bd8a chore(storage): minor refactoring
- Unexport `SyncServers`
- Re-generate mock file
- Remove single use function
2022-05-28 22:51:19 +00:00
Quentin McGaw
b345368257 hotfix(storage): JSON provider versioning safety 2022-05-28 22:44:14 +00:00
Quentin McGaw
90dd3b1b5c chore(storage): only pass hardcoded versions to read file 2022-05-28 22:36:16 +00:00
Quentin McGaw
22455ac76f chore(updater): shared not enough servers error 2022-05-28 22:02:18 +00:00
Quentin McGaw
eb18eaf0a9 fix(wireguard): continue on ipv6 route add permission denial 2022-05-28 21:06:21 +00:00
Quentin McGaw
90c6c8485b chore(updater): common GetServers signature
- Log warnings when running outside of CLI mode
- Remove updater CLI bool setting
- Warnings are logged in updating functions
2022-05-28 20:58:50 +00:00
Quentin McGaw
381089ebdf chore(storage): rename InfoErrorer to Infoer (bad name) 2022-05-28 16:05:19 +00:00
Quentin McGaw
292813831d chore(updater): internal/updater/loop subpackage
- Do not export updater interface
- Export updater struct
- Define local interfaces where needed
- More restrictive updater loop interface in http control server
- Inject `Updater` into updater loop as an interface
2022-05-28 16:03:59 +00:00
Quentin McGaw
991d75a1d0 chore(provider): rename all BuildConf to OpenVPNConfig 2022-05-27 22:04:14 +00:00
Quentin McGaw
d9dfb81cb4 feat(perfect privacy): update servers data 2022-05-27 21:56:52 +00:00
Quentin McGaw
67a9cacb61 hotfix(custom): allow empty servers data 2022-05-27 21:47:41 +00:00
Quentin McGaw
a91eb95456 chore(internal/provider): rename all structs to Provider 2022-05-27 18:05:04 +00:00
Quentin McGaw
a295269518 hotfix(formatter): cyberghost not forced as format 2022-05-27 17:50:14 +00:00
Quentin McGaw
42904b6749 chore(all): move sub-packages to internal/provider 2022-05-27 17:48:51 +00:00
Quentin McGaw
364f9de756 feat(env): clean env variable values
- Remove surrounding spaces
- Remove suffix new line characters
2022-05-27 17:27:54 +00:00
Quentin McGaw
7fd45cf17f feat(wireguard): add debug logs for IPv6 detection
- To debug issue #998
- Enable with `LOG_LEVEL=debug`
2022-05-27 17:27:53 +00:00
Quentin McGaw
eb71cfb144 chore(deps): upgrade gopkg.in/yaml.v3 to v3.0.1
- fix 'vulnerability' alert on github
- no impact really since it's just used in unit tests
- checked with `go mod why gopkg.in/yaml.v3`
2022-05-27 17:27:53 +00:00
Quentin McGaw
48e469917e chore(ci): remove tidy check
- Not really needed with newer `go install`
- Conflicts with Go 1.17 go.mod format
- Conflicts with manual indirect dependency upgrade
2022-05-27 17:27:53 +00:00
Quentin McGaw
4bcd8ee9f5 chore(constants): add internal/constants/openvpn package 2022-05-27 16:29:49 +00:00
Quentin McGaw
1b2bcf901a chore(surfshark): add package internal/provider/surshark/server
- Merge `internal/models/location.go` and `internal/constants/surfshark.go` into `internal/provider/surfshark/servers/locationdata.go`
2022-05-27 16:29:48 +00:00
Quentin McGaw
306de8feda chore(constants): add internal/provider/privateinternetacess/presets package 2022-05-27 16:29:48 +00:00
Quentin McGaw
e3696f1eea chore(constants): inline Openvpn values in each provider 2022-05-27 16:29:47 +00:00
Quentin McGaw
7ff14a356c chore(internal/providers): simplify OpenVPN config building 2022-05-27 16:29:47 +00:00
Quentin McGaw
4bde50fb3a chore(all): use casers instead of strings.Title
- Add `golang.org/x/text` dependency
- Update code to use `cases.Title(language.English)`
2022-05-27 16:29:41 +00:00
Quentin McGaw
bd0868d764 chore(all): provider to servers map in allServers
- Simplify formatting CLI
- Simplify updater code
- Simplify filter choices for config validation
- Simplify all servers deep copying
- Custom JSON marshaling methods for `AllServers`
- Simplify provider constructor switch
- Simplify storage merging
- Simplify storage reading and extraction
- Simplify updating code
2022-05-27 16:17:53 +00:00
Quentin McGaw
5ffe8555ba chore(lint): upgrade golangci-lint from v1.44.2 to v1.46.2
- Add linter `execinquery`
- Add linter `nosprintfhostport`
2022-05-27 00:52:25 +00:00
Quentin McGaw
78ccbb21cd change(servers.json): change provider names
- From `pia` to `private internet access`
- From `perfectprivacy` to `perfect privacy`
- From `vpnunlimited` to `vpn unlimited`
- This is done to match string constants in the code for another refactor
- Reset each of these providers servers version to `1`.
2022-05-27 00:47:58 +00:00
Quentin McGaw
92dbe1ebad chore(cli): refactor FormatServers to use provider strings 2022-05-08 19:05:36 +00:00
Quentin McGaw
2eec60cdd2 chore(custom): validate Openvpn file earlier 2022-05-07 19:33:21 +00:00
Quentin McGaw
da8c104ebd chore(internal/provider/utils): unexport functions 2022-05-07 19:33:12 +00:00
Quentin McGaw
0ef7b66047 chore(internal/provider): GetConnection test 2022-05-07 19:33:05 +00:00
530 changed files with 25895 additions and 18301 deletions

View File

@@ -54,8 +54,10 @@ body:
- PrivateVPN
- ProtonVPN
- PureVPN
- SlickVPN
- Surfshark
- TorGuard
- VPNSecure.me
- VPNUnlimited
- VyprVPN
- WeVPN
@@ -96,7 +98,7 @@ body:
attributes:
label: Share your logs
description: No sensitive information is logged out except when running with `LOG_LEVEL=debug`.
render: log
render: plain text
validations:
required: true
- type: textarea

5
.github/labels.yml vendored
View File

@@ -64,12 +64,17 @@
- name: ":cloud: PureVPN"
color: "cfe8d4"
description: ""
- name: ":cloud: SlickVPN"
color: "cfe8d4"
description: ""
- name: ":cloud: Surfshark"
color: "cfe8d4"
description: ""
- name: ":cloud: Torguard"
color: "cfe8d4"
description: ""
- name: ":cloud: VPNSecure.me"
color: "cfe8d4"
- name: ":cloud: VPNUnlimited"
color: "cfe8d4"
description: ""

37
.github/workflows/ci-skip.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: No trigger file paths
on:
push:
branches:
- master
paths-ignore:
- .github/workflows/ci.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
pull_request:
branches:
- master
paths-ignore:
- .github/workflows/ci.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
jobs:
verify:
runs-on: ubuntu-latest
permissions:
actions: read
steps:
- name: No trigger path triggered for required verify workflow.
run: exit 0

View File

@@ -32,13 +32,10 @@ on:
jobs:
verify:
# Only run if it's a push event or if it's a PR from this repository, and it is not dependabot.
if: |
github.actor != 'dependabot[bot]' &&
(github.event_name == 'push' ||
github.event_name == 'release' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository))
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
env:
DOCKER_BUILDKIT: "1"
steps:
@@ -52,8 +49,8 @@ jobs:
- name: Linting
run: docker build --target lint .
- name: Go mod tidy check
run: docker build --target tidy .
- name: Mocks check
run: docker build --target mocks .
- name: Build test image
run: docker build --target test -t test-container .
@@ -65,28 +62,35 @@ jobs:
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
test-container
- name: Code security analysis
uses: snyk/actions/golang@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: Build final image
run: docker build -t final-image .
# - name: Image security analysis
# uses: snyk/actions/docker@master
# env:
# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
# with:
# image: final-image
codeql:
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- uses: actions/checkout@v3
- uses: github/codeql-action/init@v2
with:
languages: go
- uses: github/codeql-action/autobuild@v2
- uses: github/codeql-action/analyze@v2
publish:
# Only run if it's a push event or if it's a PR from this repository
if: |
github.event_name == 'push' ||
github.event_name == 'release' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
needs: [verify]
github.repository == 'qdm12/gluetun' &&
(
github.event_name == 'push' ||
github.event_name == 'release' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]')
)
needs: [verify, codeql]
permissions:
actions: read
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
@@ -95,7 +99,7 @@ jobs:
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v3
uses: docker/metadata-action@v4
with:
flavor: |
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
@@ -103,17 +107,16 @@ jobs:
qmcgaw/gluetun
qmcgaw/private-internet-access
tags: |
type=ref,event=branch,enable=${{ github.ref != format('refs/heads/{0}', github.event.repository.default_branch) }}
type=ref,event=pr
type=semver,pattern=v{{major}}.{{minor}}.{{patch}}
type=semver,pattern=v{{major}}.{{minor}}
type=semver,pattern=v{{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
- uses: docker/setup-qemu-action@v1
- uses: docker/setup-buildx-action@v1
- uses: docker/setup-qemu-action@v2
- uses: docker/setup-buildx-action@v2
- uses: docker/login-action@v1
- uses: docker/login-action@v2
with:
username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }}
@@ -123,7 +126,7 @@ jobs:
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
- name: Build and push final image
uses: docker/build-push-action@v2.10.0
uses: docker/build-push-action@v3.1.1
with:
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -1,25 +0,0 @@
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '44 9 * * 0'
jobs:
analyze:
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- uses: actions/checkout@v3
- uses: github/codeql-action/init@v2
with:
languages: go
- uses: github/codeql-action/autobuild@v2
- uses: github/codeql-action/analyze@v2

View File

@@ -1,37 +0,0 @@
name: Dependabot
on:
pull_request:
branches:
- master
paths:
- .github/workflows/dependabot.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
jobs:
verify:
if: ${{ github.actor == 'dependabot[bot]' }}
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: "1"
steps:
- uses: actions/checkout@v3
- name: Build test image
run: docker build --target test -t test-container .
- name: Run tests in test container
run: |
touch coverage.txt
docker run --rm \
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
test-container
- name: Build final image
run: docker build -t final-image .

View File

@@ -1,18 +1,22 @@
name: Docker Hub description
on:
push:
branches: [master]
branches:
- master
paths:
- README.md
- .github/workflows/dockerhub-description.yml
jobs:
dockerHubDescription:
docker-hub-description:
if: github.repository == 'qdm12/gluetun'
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v3
- uses: actions/checkout@v3
- uses: peter-evans/dockerhub-description@v3
with:
username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }}

View File

@@ -1,40 +0,0 @@
name: Fork
on:
pull_request:
branches:
- master
paths:
- .github/workflows/fork.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
jobs:
verify:
if: github.event.pull_request.head.repo.full_name != github.repository && github.actor != 'dependabot[bot]'
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: "1"
steps:
- uses: actions/checkout@v3
- name: Linting
run: docker build --target lint .
- name: Build test image
run: docker build --target test -t test-container .
- name: Run tests in test container
run: |
touch coverage.txt
docker run --rm \
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
test-container
- name: Build final image
run: docker build -t final-image .

View File

@@ -7,9 +7,11 @@ on:
- .github/workflows/labels.yml
jobs:
labeler:
permissions:
issues: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: crazy-max/ghaction-github-labeler@v3
- uses: crazy-max/ghaction-github-labeler@v4
with:
yaml-file: .github/labels.yml

View File

@@ -7,46 +7,34 @@ issues:
- path: _test\.go
linters:
- dupl
- maligned
- goerr113
- containedctx
- path: internal/server/
- path: "internal\\/server\\/.+\\.go"
linters:
- dupl
- path: internal/configuration/
- path: "internal\\/configuration\\/settings\\/.+\\.go"
linters:
- dupl
- path: internal/constants/
linters:
- dupl
- text: "exported: exported var Err*"
linters:
- revive
- text: "mnd: Magic number: 0644*"
- text: "^mnd: Magic number: 0[0-9]{3}, in <argument> detected$"
source: "^.+= os\\.OpenFile\\(.+, .+, 0[0-9]{3}\\)"
linters:
- gomnd
- text: "mnd: Magic number: 0400*"
- text: "^mnd: Magic number: 0[0-9]{3}, in <argument> detected$"
source: "^.+= os\\.MkdirAll\\(.+, 0[0-9]{3}\\)"
linters:
- gomnd
- text: "variable 'mssFix' is only used in the if-statement*"
path: "openvpnconf.go"
linters:
- ifshort
- text: "variable 'auth' is only used in the if-statement*"
path: "openvpnconf.go"
linters:
- ifshort
- linters:
- lll
source: "^//go:generate "
source: "^//go:generate .+$"
- text: "returns interface \\(github\\.com\\/vishvananda\\/netlink\\.Link\\)"
linters:
- ireturn
linters:
enable:
# - cyclop
# - errorlint
# - ireturn
# - varnamelen
# - wrapcheck
- asasalint
- asciicheck
- bidichk
- bodyclose
@@ -57,6 +45,7 @@ linters:
- durationcheck
- errchkjson
- errname
- execinquery
- exhaustive
- exportloopref
- forcetypeassert
@@ -76,8 +65,9 @@ linters:
- goprintffuncname
- gosec
- grouper
- ifshort
- importas
- interfacebloat
- ireturn
- lll
- maintidx
- makezero
@@ -88,10 +78,11 @@ linters:
- nilnil
- noctx
- nolintlint
- nosprintfhostport
- prealloc
- predeclared
- predeclared
- promlinter
- reassign
- revive
- rowserrcheck
- sqlclosecheck
@@ -100,6 +91,7 @@ linters:
- tparallel
- unconvert
- unparam
- usestdlibvars
- wastedassign
- whitespace

25
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,25 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Update a VPN provider servers data",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "cmd/gluetun/main.go",
"args": [
"update",
"-enduser",
"-providers",
"${input:provider}"
],
}
],
"inputs": [
{
"id": "provider",
"type": "promptString",
"description": "Please enter a provider (or comma separated list of providers)",
}
]
}

View File

@@ -1,18 +1,22 @@
ARG ALPINE_VERSION=3.15
ARG GO_ALPINE_VERSION=3.15
ARG ALPINE_VERSION=3.16
ARG GO_ALPINE_VERSION=3.16
ARG GO_VERSION=1.17
ARG XCPUTRANSLATE_VERSION=v0.6.0
ARG GOLANGCI_LINT_VERSION=v1.44.2
ARG GOLANGCI_LINT_VERSION=v1.49.0
ARG MOCKGEN_VERSION=v1.6.0
ARG BUILDPLATFORM=linux/amd64
FROM --platform=${BUILDPLATFORM} qmcgaw/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:golangci-lint-${GOLANGCI_LINT_VERSION} AS golangci-lint
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:mockgen-${MOCKGEN_VERSION} AS mockgen
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${GO_ALPINE_VERSION} AS base
COPY --from=xcputranslate /xcputranslate /usr/local/bin/xcputranslate
RUN apk --update add git g++
# Note: findutils needed to have xargs support `-d` flag for mocks stage.
RUN apk --update add git g++ findutils
ENV CGO_ENABLED=0
COPY --from=golangci-lint /bin /go/bin/golangci-lint
COPY --from=mockgen /bin /go/bin/mockgen
WORKDIR /tmp/gobuild
COPY go.mod go.sum ./
RUN go mod download
@@ -30,14 +34,17 @@ FROM --platform=${BUILDPLATFORM} base AS lint
COPY .golangci.yml ./
RUN golangci-lint run --timeout=10m
FROM --platform=${BUILDPLATFORM} base AS tidy
FROM --platform=${BUILDPLATFORM} base AS mocks
RUN git init && \
git config user.email ci@localhost && \
git config user.name ci && \
git add -A && git commit -m ci && \
sed -i '/\/\/ indirect/d' go.mod && \
go mod tidy && \
git diff --exit-code -- go.mod
git config core.fileMode false && \
git add -A && \
git commit -m "snapshot" && \
grep -lr -E '^// Code generated by MockGen\. DO NOT EDIT\.$' . | xargs -r -d '\n' rm && \
go generate -run "mockgen" ./... && \
git diff --exit-code && \
rm -rf .git/
FROM --platform=${BUILDPLATFORM} base AS build
ARG TARGETPLATFORM
@@ -104,16 +111,25 @@ ENV VPN_SERVICE_PROVIDER=pia \
PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING=off \
PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
# # Cyberghost only:
OPENVPN_CERT= \
OPENVPN_KEY= \
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
# # VPNSecure only:
OPENVPN_ENCRYPTED_KEY= \
OPENVPN_ENCRYPTED_KEY_SECRETFILE=/run/secrets/openvpn_encrypted_key \
OPENVPN_KEY_PASSPHRASE= \
OPENVPN_KEY_PASSPHRASE_SECRETFILE=/run/secrets/openvpn_key_passphrase \
# # Nordvpn only:
SERVER_NUMBER= \
# # PIA and ProtonVPN only:
# # PIA only:
SERVER_NAMES= \
# # ProtonVPN only:
FREE_ONLY= \
# # Surfshark only:
MULTIHOP_ONLY= \
# # VPN Secure only:
PREMIUM_ONLY= \
# Firewall
FIREWALL=on \
FIREWALL_VPN_INPUT_PORTS= \
@@ -162,6 +178,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
HTTP_CONTROL_SERVER_ADDRESS=":8000" \
# Server data updater
UPDATER_PERIOD=0 \
UPDATER_MIN_RATIO=0.8 \
UPDATER_VPN_SERVICE_PROVIDERS= \
# Public IP
PUBLICIP_FILE="/tmp/gluetun/ip" \

View File

@@ -1,10 +1,6 @@
# Gluetun VPN client
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, ExpressVPN, FastestVPN,
HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Perfect Privacy, Privado, Private Internet Access, PrivateVPN,
ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN, WeVPN and Windscribe VPN servers
using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
Lightweight swiss-knife-like VPN client to multiple VPN service providers
![Title image](https://raw.githubusercontent.com/qdm12/gluetun/master/title.svg)
@@ -52,6 +48,7 @@ using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
- Drop me [an email](mailto:quentin.mcgaw@gmail.com)
- **Want to add a VPN provider?** check [Development](https://github.com/qdm12/gluetun/wiki/Development) and [Add a provider](https://github.com/qdm12/gluetun/wiki/Add-a-provider)
- Video:
[![Video Gif](https://i.imgur.com/CetWunc.gif)](https://youtu.be/0F6I03LQcI4)
@@ -60,8 +57,8 @@ using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP
## Features
- Based on Alpine 3.15 for a small Docker image of 29MB
- Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
- Based on Alpine 3.16 for a small Docker image of 29MB
- Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
- Supports OpenVPN for all providers listed
- Supports Wireguard both kernelspace and userspace
- For **Mullvad**, **Ivpn** and **Windscribe**

View File

@@ -29,15 +29,20 @@ import (
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/netlink"
"github.com/qdm12/gluetun/internal/openvpn"
"github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gluetun/internal/portforward"
"github.com/qdm12/gluetun/internal/pprof"
"github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/publicip"
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
"github.com/qdm12/gluetun/internal/routing"
"github.com/qdm12/gluetun/internal/server"
"github.com/qdm12/gluetun/internal/shadowsocks"
"github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/tun"
"github.com/qdm12/gluetun/internal/updater"
updater "github.com/qdm12/gluetun/internal/updater/loop"
"github.com/qdm12/gluetun/internal/updater/resolver"
"github.com/qdm12/gluetun/internal/updater/unzip"
"github.com/qdm12/gluetun/internal/vpn"
"github.com/qdm12/golibs/command"
"github.com/qdm12/goshutdown"
@@ -124,8 +129,8 @@ var (
//nolint:gocognit,gocyclo,maintidx
func _main(ctx context.Context, buildInfo models.BuildInformation,
args []string, logger log.LoggerInterface, source sources.Source,
tun tun.Interface, netLinker netlink.NetLinker, cmder command.RunStarter,
cli cli.CLIer) error {
tun Tun, netLinker netLinker, cmder command.RunStarter,
cli clier) error {
if len(args) > 1 { // cli operation
switch args[1] {
case "healthcheck":
@@ -215,9 +220,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return err
}
allServers := storage.GetServers()
err = allSettings.Validate(allServers)
err = allSettings.Validate(storage)
if err != nil {
return err
}
@@ -344,7 +347,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
portForwardLogger := logger.New(log.SetComponent("port forwarding"))
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
httpClient, firewallConf, portForwardLogger)
httpClient, firewallConf, portForwardLogger, puid, pgid)
portForwardHandler, portForwardCtx, portForwardDone := goshutdown.NewGoRoutineHandler(
"port forwarding", goroutine.OptionTimeout(time.Second))
go portForwardLooper.Run(portForwardCtx, portForwardDone)
@@ -363,7 +366,8 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
go unboundLooper.RunRestartTicker(dnsTickerCtx, dnsTickerDone)
controlGroupHandler.Add(dnsTickerHandler)
publicIPLooper := publicip.NewLoop(httpClient,
ipFetcher := ipinfo.New(httpClient)
publicIPLooper := publicip.NewLoop(ipFetcher,
logger.New(log.SetComponent("ip getter")),
allSettings.PublicIP, puid, pgid)
pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler(
@@ -376,18 +380,25 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
tickersGroupHandler.Add(pubIPTickerHandler)
updaterLogger := logger.New(log.SetComponent("updater"))
unzipper := unzip.New(httpClient)
parallelResolver := resolver.NewParallelResolver(allSettings.Updater.DNSAddress)
openvpnFileExtractor := extract.New()
providers := provider.NewProviders(storage, time.Now, updaterLogger,
httpClient, unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
vpnLogger := logger.New(log.SetComponent("vpn"))
vpnLooper := vpn.NewLoop(allSettings.VPN, allSettings.Firewall.VPNInputPorts,
allServers, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
providers, storage, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient,
buildInfo, *allSettings.Version.Enabled)
vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler(
"vpn", goroutine.OptionTimeout(time.Second))
go vpnLooper.Run(vpnCtx, vpnDone)
updaterLooper := updater.NewLooper(allSettings.Updater,
allServers, storage, vpnLooper.SetServers, httpClient,
logger.New(log.SetComponent("updater")))
updaterLooper := updater.NewLoop(allSettings.Updater,
providers, storage, httpClient, updaterLogger)
updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler(
"updater", goroutine.OptionTimeout(defaultShutdownTimeout))
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
@@ -407,7 +418,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
go httpProxyLooper.Run(httpProxyCtx, httpProxyDone)
otherGroupHandler.Add(httpProxyHandler)
shadowsocksLooper := shadowsocks.NewLooper(allSettings.Shadowsocks,
shadowsocksLooper := shadowsocks.NewLoop(allSettings.Shadowsocks,
logger.New(log.SetComponent("shadowsocks")))
shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler(
"shadowsocks proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
@@ -420,7 +431,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
"http server", goroutine.OptionTimeout(defaultShutdownTimeout))
httpServer, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
logger.New(log.SetComponent("http server")),
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper)
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper, storage)
if err != nil {
return fmt.Errorf("cannot setup control server: %w", err)
}
@@ -476,3 +487,54 @@ func printVersions(ctx context.Context, logger infoer,
return nil
}
type netLinker interface {
Addresser
Router
Ruler
Linker
IsWireguardSupported() (ok bool, err error)
}
type Addresser interface {
AddrList(link netlink.Link, family int) (
addresses []netlink.Addr, err error)
AddrAdd(link netlink.Link, addr *netlink.Addr) error
}
type Router interface {
RouteList(link netlink.Link, family int) (
routes []netlink.Route, err error)
RouteAdd(route *netlink.Route) error
RouteDel(route *netlink.Route) error
RouteReplace(route *netlink.Route) error
}
type Ruler interface {
RuleList(family int) (rules []netlink.Rule, err error)
RuleAdd(rule *netlink.Rule) error
RuleDel(rule *netlink.Rule) error
}
type Linker interface {
LinkList() (links []netlink.Link, err error)
LinkByName(name string) (link netlink.Link, err error)
LinkByIndex(index int) (link netlink.Link, err error)
LinkAdd(link netlink.Link) (err error)
LinkDel(link netlink.Link) (err error)
LinkSetUp(link netlink.Link) (err error)
LinkSetDown(link netlink.Link) (err error)
}
type clier interface {
ClientKey(args []string) error
FormatServers(args []string) error
OpenvpnConfig(logger cli.OpenvpnConfigLogger, source sources.Source) error
HealthCheck(ctx context.Context, source sources.Source, warner cli.Warner) error
Update(ctx context.Context, args []string, logger cli.UpdaterLogger) error
}
type Tun interface {
Check(tunDevice string) error
Create(tunDevice string) error
}

11
go.mod
View File

@@ -3,7 +3,7 @@ module github.com/qdm12/gluetun
go 1.17
require (
github.com/breml/rootcerts v0.2.3
github.com/breml/rootcerts v0.2.6
github.com/fatih/color v1.13.0
github.com/golang/mock v1.6.0
github.com/qdm12/dns v1.11.0
@@ -15,9 +15,11 @@ require (
github.com/qdm12/log v0.1.0
github.com/qdm12/ss-server v0.4.0
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
github.com/stretchr/testify v1.7.1
github.com/stretchr/testify v1.8.0
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
golang.org/x/text v0.3.7
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722
@@ -37,8 +39,7 @@ require (
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

19
go.sum
View File

@@ -4,8 +4,8 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/g
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/breml/rootcerts v0.2.3 h1:1vkYjKOiHVSyuz9Ue4AOrViEvUm8gk8phTg0vbcuU0A=
github.com/breml/rootcerts v0.2.3/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
github.com/breml/rootcerts v0.2.6 h1:CdPczjzItec+wopLoDsBAFcLEai2q7Yayfg/94/q/2E=
github.com/breml/rootcerts v0.2.6/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -127,12 +127,14 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5 h1:b/k/BVWzWRS5v6AB0gf2ckFSbFsHN5jR0HoNso1pN+w=
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
@@ -144,8 +146,9 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg=
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 h1:1tk03FUNpulq2cuWpXZWj649rwJpk0d20rxWiopKRmc=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -176,8 +179,9 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d h1:nTDGCTeAu2LhcsHTRzjyIUbZHCJ4QePArsm27Hka0UM=
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
@@ -222,6 +226,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -247,8 +253,9 @@ gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQb
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
inet.af/netaddr v0.0.0-20210511181906-37180328850c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722 h1:Qws2rZnQudC58cIagVucPQDLmMi3kAXgxscsgD0v6DU=
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=

View File

@@ -1,17 +1,9 @@
// Package alpine defines a configurator to interact with the Alpine operating system.
package alpine
import (
"os/user"
)
var _ Alpiner = (*Alpine)(nil)
type Alpiner interface {
UserCreater
VersionGetter
}
type Alpine struct {
alpineReleasePath string
passwdPath string

View File

@@ -12,10 +12,6 @@ var (
ErrUserAlreadyExists = errors.New("user already exists")
)
type UserCreater interface {
CreateUser(username string, uid int) (createdUsername string, err error)
}
// CreateUser creates a user in Alpine with the given UID.
func (a *Alpine) CreateUser(username string, uid int) (createdUsername string, err error) {
UIDStr := strconv.Itoa(uid)

View File

@@ -7,10 +7,6 @@ import (
"strings"
)
type VersionGetter interface {
Version(ctx context.Context) (version string, err error)
}
func (a *Alpine) Version(ctx context.Context) (version string, err error) {
file, err := os.OpenFile(a.alpineReleasePath, os.O_RDONLY, 0)
if err != nil {

View File

@@ -1,16 +1,5 @@
// Package cli defines an interface CLI to run command line operations.
package cli
var _ CLIer = (*CLI)(nil)
type CLIer interface {
ClientKeyFormatter
HealthChecker
OpenvpnConfigMaker
Updater
ServersFormatter
}
type CLI struct {
repoServersPath string
}

View File

@@ -10,10 +10,6 @@ import (
"github.com/qdm12/gluetun/internal/configuration/sources/files"
)
type ClientKeyFormatter interface {
ClientKey(args []string) error
}
func (c *CLI) ClientKey(args []string) error {
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
filepath := flagSet.String("path", files.OpenVPNClientKeyPath, "file path to the client.key file")

View File

@@ -6,49 +6,44 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/storage"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
type ServersFormatter interface {
FormatServers(args []string) error
}
var (
ErrFormatNotRecognized = errors.New("format is not recognized")
ErrProviderUnspecified = errors.New("VPN provider to format was not specified")
ErrFormatNotRecognized = errors.New("format is not recognized")
ErrProviderUnspecified = errors.New("VPN provider to format was not specified")
ErrMultipleProvidersToFormat = errors.New("more than one VPN provider to format were specified")
)
func addProviderFlag(flagSet *flag.FlagSet, providerToFormat map[string]*bool,
provider string, titleCaser cases.Caser) {
boolPtr, ok := providerToFormat[provider]
if !ok {
panic(fmt.Sprintf("unknown provider in format map: %s", provider))
}
flagSet.BoolVar(boolPtr, provider, false, "Format "+titleCaser.String(provider)+" servers")
}
func (c *CLI) FormatServers(args []string) error {
var format, output string
var cyberghost, expressvpn, fastestvpn, hideMyAss, ipvanish, ivpn, mullvad,
nordvpn, perfectPrivacy, pia, privado, privatevpn, protonvpn, purevpn, surfshark,
torguard, vpnUnlimited, vyprvpn, wevpn, windscribe bool
allProviders := providers.All()
providersToFormat := make(map[string]*bool, len(allProviders))
for _, provider := range allProviders {
providersToFormat[provider] = new(bool)
}
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'")
flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to")
flagSet.BoolVar(&cyberghost, providers.Cyberghost, false, "Format Cyberghost servers")
flagSet.BoolVar(&expressvpn, providers.Expressvpn, false, "Format ExpressVPN servers")
flagSet.BoolVar(&fastestvpn, providers.Fastestvpn, false, "Format FastestVPN servers")
flagSet.BoolVar(&hideMyAss, providers.HideMyAss, false, "Format HideMyAss servers")
flagSet.BoolVar(&ipvanish, providers.Ipvanish, false, "Format IpVanish servers")
flagSet.BoolVar(&ivpn, providers.Ivpn, false, "Format IVPN servers")
flagSet.BoolVar(&mullvad, providers.Mullvad, false, "Format Mullvad servers")
flagSet.BoolVar(&nordvpn, providers.Nordvpn, false, "Format Nordvpn servers")
flagSet.BoolVar(&perfectPrivacy, providers.Perfectprivacy, false, "Format Perfect Privacy servers")
flagSet.BoolVar(&pia, providers.PrivateInternetAccess, false, "Format Private Internet Access servers")
flagSet.BoolVar(&privado, providers.Privado, false, "Format Privado servers")
flagSet.BoolVar(&privatevpn, providers.Privatevpn, false, "Format Private VPN servers")
flagSet.BoolVar(&protonvpn, providers.Protonvpn, false, "Format Protonvpn servers")
flagSet.BoolVar(&purevpn, providers.Purevpn, false, "Format Purevpn servers")
flagSet.BoolVar(&surfshark, providers.Surfshark, false, "Format Surfshark servers")
flagSet.BoolVar(&torguard, providers.Torguard, false, "Format Torguard servers")
flagSet.BoolVar(&vpnUnlimited, providers.VPNUnlimited, false, "Format VPN Unlimited servers")
flagSet.BoolVar(&vyprvpn, providers.Vyprvpn, false, "Format Vyprvpn servers")
flagSet.BoolVar(&wevpn, providers.Wevpn, false, "Format WeVPN servers")
flagSet.BoolVar(&windscribe, providers.Windscribe, false, "Format Windscribe servers")
titleCaser := cases.Title(language.English)
for _, provider := range allProviders {
addProviderFlag(flagSet, providersToFormat, provider, titleCaser)
}
if err := flagSet.Parse(args); err != nil {
return err
}
@@ -57,58 +52,31 @@ func (c *CLI) FormatServers(args []string) error {
return fmt.Errorf("%w: %s", ErrFormatNotRecognized, format)
}
// Verify only one provider is set to be formatted.
var providers []string
for provider, formatPtr := range providersToFormat {
if *formatPtr {
providers = append(providers, provider)
}
}
switch len(providers) {
case 0:
return ErrProviderUnspecified
case 1:
default:
return fmt.Errorf("%w: %d specified: %s",
ErrMultipleProvidersToFormat, len(providers),
strings.Join(providers, ", "))
}
providerToFormat := providers[0]
logger := newNoopLogger()
storage, err := storage.New(logger, constants.ServersData)
if err != nil {
return fmt.Errorf("cannot create servers storage: %w", err)
}
currentServers := storage.GetServers()
var formatted string
switch {
case cyberghost:
formatted = currentServers.Cyberghost.ToMarkdown(providers.Cyberghost)
case expressvpn:
formatted = currentServers.Expressvpn.ToMarkdown(providers.Expressvpn)
case fastestvpn:
formatted = currentServers.Fastestvpn.ToMarkdown(providers.Fastestvpn)
case hideMyAss:
formatted = currentServers.HideMyAss.ToMarkdown(providers.HideMyAss)
case ipvanish:
formatted = currentServers.Ipvanish.ToMarkdown(providers.Ipvanish)
case ivpn:
formatted = currentServers.Ivpn.ToMarkdown(providers.Ivpn)
case mullvad:
formatted = currentServers.Mullvad.ToMarkdown(providers.Mullvad)
case nordvpn:
formatted = currentServers.Nordvpn.ToMarkdown(providers.Nordvpn)
case perfectPrivacy:
formatted = currentServers.Perfectprivacy.ToMarkdown(providers.Perfectprivacy)
case pia:
formatted = currentServers.Pia.ToMarkdown(providers.PrivateInternetAccess)
case privado:
formatted = currentServers.Privado.ToMarkdown(providers.Privado)
case privatevpn:
formatted = currentServers.Privatevpn.ToMarkdown(providers.Privatevpn)
case protonvpn:
formatted = currentServers.Protonvpn.ToMarkdown(providers.Protonvpn)
case purevpn:
formatted = currentServers.Purevpn.ToMarkdown(providers.Purevpn)
case surfshark:
formatted = currentServers.Surfshark.ToMarkdown(providers.Surfshark)
case torguard:
formatted = currentServers.Torguard.ToMarkdown(providers.Torguard)
case vpnUnlimited:
formatted = currentServers.VPNUnlimited.ToMarkdown(providers.VPNUnlimited)
case vyprvpn:
formatted = currentServers.Vyprvpn.ToMarkdown(providers.Vyprvpn)
case wevpn:
formatted = currentServers.Wevpn.ToMarkdown(providers.Wevpn)
case windscribe:
formatted = currentServers.Windscribe.ToMarkdown(providers.Windscribe)
default:
return ErrProviderUnspecified
}
formatted := storage.FormatToMarkdown(providerToFormat)
output = filepath.Clean(output)
file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)

View File

@@ -10,10 +10,6 @@ import (
"github.com/qdm12/gluetun/internal/healthcheck"
)
type HealthChecker interface {
HealthCheck(ctx context.Context, source sources.Source, warner Warner) error
}
func (c *CLI) HealthCheck(ctx context.Context, source sources.Source, warner Warner) error {
// Extract the health server port from the configuration.
config, err := source.ReadHealth()

View File

@@ -1,50 +1,73 @@
package cli
import (
"context"
"fmt"
"net"
"net/http"
"strings"
"time"
"github.com/qdm12/gluetun/internal/configuration/sources"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
"github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/updater/resolver"
)
type OpenvpnConfigMaker interface {
OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) error
}
type OpenvpnConfigLogger interface {
Info(s string)
Warn(s string)
}
type Unzipper interface {
FetchAndExtract(ctx context.Context, url string) (
contents map[string][]byte, err error)
}
type ParallelResolver interface {
Resolve(ctx context.Context, settings resolver.ParallelSettings) (
hostToIPs map[string][]net.IP, warnings []string, err error)
}
type IPFetcher interface {
FetchMultiInfo(ctx context.Context, ips []net.IP) (data []ipinfo.Response, err error)
}
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) error {
storage, err := storage.New(logger, constants.ServersData)
if err != nil {
return err
}
allServers := storage.GetServers()
allSettings, err := source.Read()
if err != nil {
return err
}
if err = allSettings.Validate(allServers); err != nil {
if err = allSettings.Validate(storage); err != nil {
return err
}
providerConf := provider.New(*allSettings.VPN.Provider.Name, allServers, time.Now)
// Unused by this CLI command
unzipper := (Unzipper)(nil)
client := (*http.Client)(nil)
warner := (Warner)(nil)
parallelResolver := (ParallelResolver)(nil)
ipFetcher := (IPFetcher)(nil)
openvpnFileExtractor := extract.New()
providers := provider.NewProviders(storage, time.Now, warner, client,
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
providerConf := providers.Get(*allSettings.VPN.Provider.Name)
connection, err := providerConf.GetConnection(allSettings.VPN.Provider.ServerSelection)
if err != nil {
return err
}
lines, err := providerConf.BuildConf(connection, allSettings.VPN.OpenVPN)
if err != nil {
return err
}
lines := providerConf.OpenVPNConfig(connection, allSettings.VPN.OpenVPN)
fmt.Println(strings.Join(lines, "\n"))
return nil

View File

@@ -2,51 +2,48 @@ package cli
import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"net"
"net/http"
"os"
"strings"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
"github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/updater"
"github.com/qdm12/gluetun/internal/updater/resolver"
"github.com/qdm12/gluetun/internal/updater/unzip"
)
var (
ErrModeUnspecified = errors.New("at least one of -enduser or -maintainer must be specified")
ErrDNSAddress = errors.New("DNS address is not valid")
ErrNoProviderSpecified = errors.New("no provider was specified")
)
type Updater interface {
Update(ctx context.Context, args []string, logger UpdaterLogger) error
}
type UpdaterLogger interface {
Info(s string)
Warn(s string)
Error(s string)
}
func boolPtr(b bool) *bool { return &b }
func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
options := settings.Updater{CLI: boolPtr(true)}
options := settings.Updater{}
var endUserMode, maintainerMode, updateAll bool
var dnsAddress, csvProviders string
var csvProviders string
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
flagSet.BoolVar(&endUserMode, "enduser", false, "Write results to /gluetun/servers.json (for end users)")
flagSet.BoolVar(&maintainerMode, "maintainer", false,
"Write results to ./internal/storage/servers.json to modify the program (for maintainers)")
flagSet.StringVar(&dnsAddress, "dns", "8.8.8.8", "DNS resolver address to use")
flagSet.StringVar(&options.DNSAddress, "dns", "8.8.8.8", "DNS resolver address to use")
const defaultMinRatio = 0.8
flagSet.Float64Var(&options.MinRatio, "minratio", defaultMinRatio,
"Minimum ratio of servers to find for the update to succeed")
flagSet.BoolVar(&updateAll, "all", false, "Update servers for all VPN providers")
flagSet.StringVar(&csvProviders, "providers", "", "CSV string of VPN providers to update server data for")
if err := flagSet.Parse(args); err != nil {
@@ -57,18 +54,8 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
return ErrModeUnspecified
}
options.DNSAddress = net.ParseIP(dnsAddress)
if options.DNSAddress == nil {
return fmt.Errorf("%w: %s", ErrDNSAddress, dnsAddress)
}
if updateAll {
for _, provider := range providers.All() {
if provider == providers.Custom {
continue
}
options.Providers = append(options.Providers, provider)
}
options.Providers = providers.All()
} else {
if csvProviders == "" {
return ErrNoProviderSpecified
@@ -83,48 +70,33 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
return fmt.Errorf("options validation failed: %w", err)
}
const clientTimeout = 10 * time.Second
httpClient := &http.Client{Timeout: clientTimeout}
storage, err := storage.New(logger, constants.ServersData)
if err != nil {
return fmt.Errorf("cannot create servers storage: %w", err)
}
currentServers := storage.GetServers()
updater := updater.New(options, httpClient, currentServers, logger)
allServers, err := updater.UpdateServers(ctx)
const clientTimeout = 10 * time.Second
httpClient := &http.Client{Timeout: clientTimeout}
unzipper := unzip.New(httpClient)
parallelResolver := resolver.NewParallelResolver(options.DNSAddress)
ipFetcher := ipinfo.New(httpClient)
openvpnFileExtractor := extract.New()
providers := provider.NewProviders(storage, time.Now, logger, httpClient,
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
updater := updater.New(httpClient, storage, providers, logger)
err = updater.UpdateServers(ctx, options.Providers, options.MinRatio)
if err != nil {
return fmt.Errorf("cannot update server information: %w", err)
}
if endUserMode {
if err := storage.FlushToFile(allServers); err != nil {
return fmt.Errorf("cannot write updated information to file: %w", err)
}
}
if maintainerMode {
if err := writeToEmbeddedJSON(c.repoServersPath, allServers); err != nil {
return fmt.Errorf("cannot write updated information to file: %w", err)
err := storage.FlushToFile(c.repoServersPath)
if err != nil {
return fmt.Errorf("cannot write servers data to embedded JSON file: %w", err)
}
}
return nil
}
func writeToEmbeddedJSON(repoServersPath string,
allServers models.AllServers) error {
const perms = 0600
f, err := os.OpenFile(repoServersPath,
os.O_TRUNC|os.O_WRONLY|os.O_CREATE, perms)
if err != nil {
return err
}
defer f.Close()
encoder := json.NewEncoder(f)
encoder.SetIndent("", " ")
return encoder.Encode(allServers)
}

View File

@@ -65,7 +65,7 @@ func (d *DoT) copy() (copied DoT) {
// unset field of the receiver settings object.
func (d *DoT) mergeWith(other DoT) {
d.Enabled = helpers.MergeWithBool(d.Enabled, other.Enabled)
d.UpdatePeriod = helpers.MergeWithDuration(d.UpdatePeriod, other.UpdatePeriod)
d.UpdatePeriod = helpers.MergeWithDurationPtr(d.UpdatePeriod, other.UpdatePeriod)
d.Unbound.mergeWith(other.Unbound)
d.Blacklist.mergeWith(other.Blacklist)
}
@@ -75,7 +75,7 @@ func (d *DoT) mergeWith(other DoT) {
// settings.
func (d *DoT) overrideWith(other DoT) {
d.Enabled = helpers.OverrideWithBool(d.Enabled, other.Enabled)
d.UpdatePeriod = helpers.OverrideWithDuration(d.UpdatePeriod, other.UpdatePeriod)
d.UpdatePeriod = helpers.OverrideWithDurationPtr(d.UpdatePeriod, other.UpdatePeriod)
d.Unbound.overrideWith(other.Unbound)
d.Blacklist.overrideWith(other.Blacklist)
}
@@ -83,7 +83,7 @@ func (d *DoT) overrideWith(other DoT) {
func (d *DoT) setDefaults() {
d.Enabled = helpers.DefaultBool(d.Enabled, true)
const defaultUpdatePeriod = 24 * time.Hour
d.UpdatePeriod = helpers.DefaultDuration(d.UpdatePeriod, defaultUpdatePeriod)
d.UpdatePeriod = helpers.DefaultDurationPtr(d.UpdatePeriod, defaultUpdatePeriod)
d.Unbound.setDefaults()
d.Blacklist.setDefaults()
}

View File

@@ -10,12 +10,14 @@ var (
ErrFirewallZeroPort = errors.New("cannot have a zero port to block")
ErrHostnameNotValid = errors.New("the hostname specified is not valid")
ErrISPNotValid = errors.New("the ISP specified is not valid")
ErrMinRatioNotValid = errors.New("minimum ratio is not valid")
ErrMissingValue = errors.New("missing value")
ErrNameNotValid = errors.New("the server name specified is not valid")
ErrOpenVPNClientKeyMissing = errors.New("client key is missing")
ErrOpenVPNCustomPortNotAllowed = errors.New("custom endpoint port is not allowed")
ErrOpenVPNEncryptionPresetNotValid = errors.New("PIA encryption preset is not valid")
ErrOpenVPNInterfaceNotValid = errors.New("interface name is not valid")
ErrOpenVPNKeyPassphraseIsEmpty = errors.New("key passphrase is empty")
ErrOpenVPNMSSFixIsTooHigh = errors.New("mssfix option value is too high")
ErrOpenVPNPasswordIsEmpty = errors.New("password is empty")
ErrOpenVPNTCPNotSupported = errors.New("TCP protocol is not supported")

View File

@@ -3,6 +3,7 @@ package settings
import (
"fmt"
"os"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
@@ -15,6 +16,12 @@ type Health struct {
// for the health check server.
// It cannot be the empty string in the internal state.
ServerAddress string
// ReadHeaderTimeout is the HTTP server header read timeout
// duration of the HTTP server. It defaults to 100 milliseconds.
ReadHeaderTimeout time.Duration
// ReadTimeout is the HTTP read timeout duration of the
// HTTP server. It defaults to 500 milliseconds.
ReadTimeout time.Duration
// TargetAddress is the address (host or host:port)
// to TCP dial to periodically for the health check.
// It cannot be the empty string in the internal state.
@@ -40,9 +47,11 @@ func (h Health) Validate() (err error) {
func (h *Health) copy() (copied Health) {
return Health{
ServerAddress: h.ServerAddress,
TargetAddress: h.TargetAddress,
VPN: h.VPN.copy(),
ServerAddress: h.ServerAddress,
ReadHeaderTimeout: h.ReadHeaderTimeout,
ReadTimeout: h.ReadTimeout,
TargetAddress: h.TargetAddress,
VPN: h.VPN.copy(),
}
}
@@ -50,6 +59,8 @@ func (h *Health) copy() (copied Health) {
// unset field of the receiver settings object.
func (h *Health) MergeWith(other Health) {
h.ServerAddress = helpers.MergeWithString(h.ServerAddress, other.ServerAddress)
h.ReadHeaderTimeout = helpers.MergeWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.MergeWithDuration(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = helpers.MergeWithString(h.TargetAddress, other.TargetAddress)
h.VPN.mergeWith(other.VPN)
}
@@ -59,12 +70,18 @@ func (h *Health) MergeWith(other Health) {
// settings.
func (h *Health) OverrideWith(other Health) {
h.ServerAddress = helpers.OverrideWithString(h.ServerAddress, other.ServerAddress)
h.ReadHeaderTimeout = helpers.OverrideWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.OverrideWithDuration(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = helpers.OverrideWithString(h.TargetAddress, other.TargetAddress)
h.VPN.overrideWith(other.VPN)
}
func (h *Health) SetDefaults() {
h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999")
const defaultReadHeaderTimeout = 100 * time.Millisecond
h.ReadHeaderTimeout = helpers.DefaultDuration(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
const defaultReadTimeout = 500 * time.Millisecond
h.ReadTimeout = helpers.DefaultDuration(h.ReadTimeout, defaultReadTimeout)
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "cloudflare.com:443")
h.VPN.setDefaults()
}
@@ -77,6 +94,8 @@ func (h Health) toLinesNode() (node *gotree.Node) {
node = gotree.New("Health settings:")
node.Appendf("Server listening address: %s", h.ServerAddress)
node.Appendf("Target address: %s", h.TargetAddress)
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
node.Appendf("Read timeout: %s", h.ReadTimeout)
node.AppendNode(h.VPN.toLinesNode("VPN"))
return node
}

View File

@@ -35,23 +35,23 @@ func (h *HealthyWait) copy() (copied HealthyWait) {
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (h *HealthyWait) mergeWith(other HealthyWait) {
h.Initial = helpers.MergeWithDuration(h.Initial, other.Initial)
h.Addition = helpers.MergeWithDuration(h.Addition, other.Addition)
h.Initial = helpers.MergeWithDurationPtr(h.Initial, other.Initial)
h.Addition = helpers.MergeWithDurationPtr(h.Addition, other.Addition)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (h *HealthyWait) overrideWith(other HealthyWait) {
h.Initial = helpers.OverrideWithDuration(h.Initial, other.Initial)
h.Addition = helpers.OverrideWithDuration(h.Addition, other.Addition)
h.Initial = helpers.OverrideWithDurationPtr(h.Initial, other.Initial)
h.Addition = helpers.OverrideWithDurationPtr(h.Addition, other.Addition)
}
func (h *HealthyWait) setDefaults() {
const initialDurationDefault = 6 * time.Second
const additionDurationDefault = 5 * time.Second
h.Initial = helpers.DefaultDuration(h.Initial, initialDurationDefault)
h.Addition = helpers.DefaultDuration(h.Addition, additionDurationDefault)
h.Initial = helpers.DefaultDurationPtr(h.Initial, initialDurationDefault)
h.Addition = helpers.DefaultDurationPtr(h.Addition, additionDurationDefault)
}
func (h HealthyWait) String() string {

View File

@@ -73,7 +73,15 @@ func DefaultStringPtr(existing *string, defaultValue string) (result *string) {
return result
}
func DefaultDuration(existing *time.Duration,
func DefaultDuration(existing time.Duration,
defaultValue time.Duration) (result time.Duration) {
if existing != 0 {
return existing
}
return defaultValue
}
func DefaultDurationPtr(existing *time.Duration,
defaultValue time.Duration) (result *time.Duration) {
if existing != nil {
return existing

View File

@@ -34,6 +34,13 @@ func MergeWithInt(existing, other int) (result int) {
return other
}
func MergeWithFloat64(existing, other float64) (result float64) {
if existing != 0 {
return existing
}
return other
}
func MergeWithStringPtr(existing, other *string) (result *string) {
if existing != nil {
return existing
@@ -100,7 +107,14 @@ func MergeWithIP(existing, other net.IP) (result net.IP) {
return result
}
func MergeWithDuration(existing, other *time.Duration) (result *time.Duration) {
func MergeWithDuration(existing, other time.Duration) (result time.Duration) {
if existing != 0 {
return existing
}
return other
}
func MergeWithDurationPtr(existing, other *time.Duration) (result *time.Duration) {
if existing != nil {
return existing
}

View File

@@ -32,6 +32,13 @@ func OverrideWithInt(existing, other int) (result int) {
return other
}
func OverrideWithFloat64(existing, other float64) (result float64) {
if other == 0 {
return existing
}
return other
}
func OverrideWithStringPtr(existing, other *string) (result *string) {
if other == nil {
return existing
@@ -86,7 +93,16 @@ func OverrideWithIP(existing, other net.IP) (result net.IP) {
return result
}
func OverrideWithDuration(existing, other *time.Duration) (result *time.Duration) {
func OverrideWithDuration(existing, other time.Duration) (
result time.Duration) {
if other == 0 {
return existing
}
return other
}
func OverrideWithDurationPtr(existing, other *time.Duration) (
result *time.Duration) {
if other == nil {
return existing
}

View File

@@ -3,6 +3,7 @@ package settings
import (
"fmt"
"os"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
@@ -33,6 +34,12 @@ type HTTPProxy struct {
// each request/response. It cannot be nil in the
// internal state.
Log *bool
// ReadHeaderTimeout is the HTTP header read timeout duration
// of the HTTP server. It defaults to 1 second if left unset.
ReadHeaderTimeout time.Duration
// ReadTimeout is the HTTP read timeout duration
// of the HTTP server. It defaults to 3 seconds if left unset.
ReadTimeout time.Duration
}
func (h HTTPProxy) validate() (err error) {
@@ -49,12 +56,14 @@ func (h HTTPProxy) validate() (err error) {
func (h *HTTPProxy) copy() (copied HTTPProxy) {
return HTTPProxy{
User: helpers.CopyStringPtr(h.User),
Password: helpers.CopyStringPtr(h.Password),
ListeningAddress: h.ListeningAddress,
Enabled: helpers.CopyBoolPtr(h.Enabled),
Stealth: helpers.CopyBoolPtr(h.Stealth),
Log: helpers.CopyBoolPtr(h.Log),
User: helpers.CopyStringPtr(h.User),
Password: helpers.CopyStringPtr(h.Password),
ListeningAddress: h.ListeningAddress,
Enabled: helpers.CopyBoolPtr(h.Enabled),
Stealth: helpers.CopyBoolPtr(h.Stealth),
Log: helpers.CopyBoolPtr(h.Log),
ReadHeaderTimeout: h.ReadHeaderTimeout,
ReadTimeout: h.ReadTimeout,
}
}
@@ -67,6 +76,8 @@ func (h *HTTPProxy) mergeWith(other HTTPProxy) {
h.Enabled = helpers.MergeWithBool(h.Enabled, other.Enabled)
h.Stealth = helpers.MergeWithBool(h.Stealth, other.Stealth)
h.Log = helpers.MergeWithBool(h.Log, other.Log)
h.ReadHeaderTimeout = helpers.MergeWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.MergeWithDuration(h.ReadTimeout, other.ReadTimeout)
}
// overrideWith overrides fields of the receiver
@@ -79,6 +90,8 @@ func (h *HTTPProxy) overrideWith(other HTTPProxy) {
h.Enabled = helpers.OverrideWithBool(h.Enabled, other.Enabled)
h.Stealth = helpers.OverrideWithBool(h.Stealth, other.Stealth)
h.Log = helpers.OverrideWithBool(h.Log, other.Log)
h.ReadHeaderTimeout = helpers.OverrideWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.OverrideWithDuration(h.ReadTimeout, other.ReadTimeout)
}
func (h *HTTPProxy) setDefaults() {
@@ -88,6 +101,10 @@ func (h *HTTPProxy) setDefaults() {
h.Enabled = helpers.DefaultBool(h.Enabled, false)
h.Stealth = helpers.DefaultBool(h.Stealth, false)
h.Log = helpers.DefaultBool(h.Log, false)
const defaultReadHeaderTimeout = time.Second
h.ReadHeaderTimeout = helpers.DefaultDuration(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
const defaultReadTimeout = 3 * time.Second
h.ReadTimeout = helpers.DefaultDuration(h.ReadTimeout, defaultReadTimeout)
}
func (h HTTPProxy) String() string {
@@ -106,6 +123,8 @@ func (h HTTPProxy) toLinesNode() (node *gotree.Node) {
node.Appendf("Password: %s", helpers.ObfuscatePassword(*h.Password))
node.Appendf("Stealth mode: %s", helpers.BoolPtrToYesNo(h.Stealth))
node.Appendf("Log: %s", helpers.BoolPtrToYesNo(h.Log))
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
node.Appendf("Read timeout: %s", h.ReadTimeout)
return node
}

View File

@@ -1,14 +1,16 @@
package settings
import (
"encoding/base64"
"fmt"
"regexp"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/openvpn"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/openvpn/parse"
"github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
"github.com/qdm12/gotree"
)
@@ -18,13 +20,15 @@ type OpenVPN struct {
// It can only be "2.4" or "2.5".
Version string
// User is the OpenVPN authentication username.
// It cannot be an empty string in the internal state
// if OpenVPN is used.
User string
// It cannot be nil in the internal state if OpenVPN is used.
// It is usually required but in some cases can be the empty string
// to indicate no user+password authentication is needed.
User *string
// Password is the OpenVPN authentication password.
// It cannot be an empty string in the internal state
// if OpenVPN is used.
Password string
// It cannot be nil in the internal state if OpenVPN is used.
// It is usually required but in some cases can be the empty string
// to indicate no user+password authentication is needed.
Password *string
// ConfFile is a custom OpenVPN configuration file path.
// It can be set to the empty string for it to be ignored.
// It cannot be nil in the internal state.
@@ -38,16 +42,25 @@ type OpenVPN struct {
// It cannot be nil in the internal state.
// It is ignored if it is set to the empty string.
Auth *string
// ClientCrt is the OpenVPN client certificate.
// This is notably used by Cyberghost.
// Cert is the OpenVPN certificate for the <cert> block.
// This is notably used by Cyberghost and VPN secure.
// It can be set to the empty string to be ignored.
// It cannot be nil in the internal state.
ClientCrt *string
// ClientKey is the OpenVPN client key.
Cert *string
// Key is the OpenVPN key.
// This is used by Cyberghost and VPN Unlimited.
// It can be set to the empty string to be ignored.
// It cannot be nil in the internal state.
ClientKey *string
Key *string
// EncryptedKey is the content of an encrypted
// key for OpenVPN. It is used by VPN secure.
// It defaults to the empty string meaning it is not
// to be used. KeyPassphrase must be set if this one is set.
EncryptedKey *string
// KeyPassphrase is the key passphrase to be used by OpenVPN
// to decrypt the EncryptedPrivateKey. It defaults to the
// empty string and must be set if EncryptedPrivateKey is set.
KeyPassphrase *string
// PIAEncPreset is the encryption preset for
// Private Internet Access. It can be set to an
// empty string for other providers.
@@ -79,22 +92,23 @@ var ivpnAccountID = regexp.MustCompile(`^(i|ivpn)\-[a-zA-Z0-9]{4}\-[a-zA-Z0-9]{4
func (o OpenVPN) validate(vpnProvider string) (err error) {
// Validate version
validVersions := []string{constants.Openvpn24, constants.Openvpn25}
validVersions := []string{openvpn.Openvpn24, openvpn.Openvpn25}
if !helpers.IsOneOf(o.Version, validVersions...) {
return fmt.Errorf("%w: %q can only be one of %s",
ErrOpenVPNVersionIsNotValid, o.Version, strings.Join(validVersions, ", "))
}
isCustom := vpnProvider == providers.Custom
isUserRequired := !isCustom && vpnProvider != providers.VPNSecure
if !isCustom && o.User == "" {
if isUserRequired && *o.User == "" {
return ErrOpenVPNUserIsEmpty
}
passwordRequired := !isCustom &&
(vpnProvider != providers.Ivpn || !ivpnAccountID.MatchString(o.User))
passwordRequired := isUserRequired &&
(vpnProvider != providers.Ivpn || !ivpnAccountID.MatchString(*o.User))
if passwordRequired && o.Password == "" {
if passwordRequired && *o.Password == "" {
return ErrOpenVPNPasswordIsEmpty
}
@@ -103,16 +117,25 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
return fmt.Errorf("custom configuration file: %w", err)
}
err = validateOpenVPNClientCertificate(vpnProvider, *o.ClientCrt)
err = validateOpenVPNClientCertificate(vpnProvider, *o.Cert)
if err != nil {
return fmt.Errorf("client certificate: %w", err)
}
err = validateOpenVPNClientKey(vpnProvider, *o.ClientKey)
err = validateOpenVPNClientKey(vpnProvider, *o.Key)
if err != nil {
return fmt.Errorf("client key: %w", err)
}
err = validateOpenVPNEncryptedKey(vpnProvider, *o.EncryptedKey)
if err != nil {
return fmt.Errorf("encrypted key: %w", err)
}
if *o.EncryptedKey != "" && *o.KeyPassphrase == "" {
return fmt.Errorf("%w", ErrOpenVPNKeyPassphraseIsEmpty)
}
const maxMSSFix = 10000
if *o.MSSFix > maxMSSFix {
return fmt.Errorf("%w: %d is over the maximum value of %d",
@@ -147,6 +170,12 @@ func validateOpenVPNConfigFilepath(isCustom bool,
return err
}
extractor := extract.New()
_, _, err = extractor.Data(confFile)
if err != nil {
return fmt.Errorf("failed extracting information from custom configuration file: %w", err)
}
return nil
}
@@ -155,6 +184,7 @@ func validateOpenVPNClientCertificate(vpnProvider,
switch vpnProvider {
case
providers.Cyberghost,
providers.VPNSecure,
providers.VPNUnlimited:
if clientCert == "" {
return ErrMissingValue
@@ -165,7 +195,7 @@ func validateOpenVPNClientCertificate(vpnProvider,
return nil
}
_, err = parse.ExtractCert([]byte(clientCert))
_, err = base64.StdEncoding.DecodeString(clientCert)
if err != nil {
return err
}
@@ -187,7 +217,24 @@ func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
return nil
}
_, err = parse.ExtractPrivateKey([]byte(clientKey))
_, err = base64.StdEncoding.DecodeString(clientKey)
if err != nil {
return err
}
return nil
}
func validateOpenVPNEncryptedKey(vpnProvider,
encryptedPrivateKey string) (err error) {
if vpnProvider == providers.VPNSecure && encryptedPrivateKey == "" {
return ErrMissingValue
}
if encryptedPrivateKey == "" {
return nil
}
_, err = base64.StdEncoding.DecodeString(encryptedPrivateKey)
if err != nil {
return err
}
@@ -196,21 +243,23 @@ func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
func (o *OpenVPN) copy() (copied OpenVPN) {
return OpenVPN{
Version: o.Version,
User: o.User,
Password: o.Password,
ConfFile: helpers.CopyStringPtr(o.ConfFile),
Ciphers: helpers.CopyStringSlice(o.Ciphers),
Auth: helpers.CopyStringPtr(o.Auth),
ClientCrt: helpers.CopyStringPtr(o.ClientCrt),
ClientKey: helpers.CopyStringPtr(o.ClientKey),
PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset),
IPv6: helpers.CopyBoolPtr(o.IPv6),
MSSFix: helpers.CopyUint16Ptr(o.MSSFix),
Interface: o.Interface,
ProcessUser: o.ProcessUser,
Verbosity: helpers.CopyIntPtr(o.Verbosity),
Flags: helpers.CopyStringSlice(o.Flags),
Version: o.Version,
User: helpers.CopyStringPtr(o.User),
Password: helpers.CopyStringPtr(o.Password),
ConfFile: helpers.CopyStringPtr(o.ConfFile),
Ciphers: helpers.CopyStringSlice(o.Ciphers),
Auth: helpers.CopyStringPtr(o.Auth),
Cert: helpers.CopyStringPtr(o.Cert),
Key: helpers.CopyStringPtr(o.Key),
EncryptedKey: helpers.CopyStringPtr(o.EncryptedKey),
KeyPassphrase: helpers.CopyStringPtr(o.KeyPassphrase),
PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset),
IPv6: helpers.CopyBoolPtr(o.IPv6),
MSSFix: helpers.CopyUint16Ptr(o.MSSFix),
Interface: o.Interface,
ProcessUser: o.ProcessUser,
Verbosity: helpers.CopyIntPtr(o.Verbosity),
Flags: helpers.CopyStringSlice(o.Flags),
}
}
@@ -218,13 +267,15 @@ func (o *OpenVPN) copy() (copied OpenVPN) {
// unset field of the receiver settings object.
func (o *OpenVPN) mergeWith(other OpenVPN) {
o.Version = helpers.MergeWithString(o.Version, other.Version)
o.User = helpers.MergeWithString(o.User, other.User)
o.Password = helpers.MergeWithString(o.Password, other.Password)
o.User = helpers.MergeWithStringPtr(o.User, other.User)
o.Password = helpers.MergeWithStringPtr(o.Password, other.Password)
o.ConfFile = helpers.MergeWithStringPtr(o.ConfFile, other.ConfFile)
o.Ciphers = helpers.MergeStringSlices(o.Ciphers, other.Ciphers)
o.Auth = helpers.MergeWithStringPtr(o.Auth, other.Auth)
o.ClientCrt = helpers.MergeWithStringPtr(o.ClientCrt, other.ClientCrt)
o.ClientKey = helpers.MergeWithStringPtr(o.ClientKey, other.ClientKey)
o.Cert = helpers.MergeWithStringPtr(o.Cert, other.Cert)
o.Key = helpers.MergeWithStringPtr(o.Key, other.Key)
o.EncryptedKey = helpers.MergeWithStringPtr(o.EncryptedKey, other.EncryptedKey)
o.KeyPassphrase = helpers.MergeWithStringPtr(o.KeyPassphrase, other.KeyPassphrase)
o.PIAEncPreset = helpers.MergeWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
o.IPv6 = helpers.MergeWithBool(o.IPv6, other.IPv6)
o.MSSFix = helpers.MergeWithUint16(o.MSSFix, other.MSSFix)
@@ -239,13 +290,15 @@ func (o *OpenVPN) mergeWith(other OpenVPN) {
// settings.
func (o *OpenVPN) overrideWith(other OpenVPN) {
o.Version = helpers.OverrideWithString(o.Version, other.Version)
o.User = helpers.OverrideWithString(o.User, other.User)
o.Password = helpers.OverrideWithString(o.Password, other.Password)
o.User = helpers.OverrideWithStringPtr(o.User, other.User)
o.Password = helpers.OverrideWithStringPtr(o.Password, other.Password)
o.ConfFile = helpers.OverrideWithStringPtr(o.ConfFile, other.ConfFile)
o.Ciphers = helpers.OverrideWithStringSlice(o.Ciphers, other.Ciphers)
o.Auth = helpers.OverrideWithStringPtr(o.Auth, other.Auth)
o.ClientCrt = helpers.OverrideWithStringPtr(o.ClientCrt, other.ClientCrt)
o.ClientKey = helpers.OverrideWithStringPtr(o.ClientKey, other.ClientKey)
o.Cert = helpers.OverrideWithStringPtr(o.Cert, other.Cert)
o.Key = helpers.OverrideWithStringPtr(o.Key, other.Key)
o.EncryptedKey = helpers.OverrideWithStringPtr(o.EncryptedKey, other.EncryptedKey)
o.KeyPassphrase = helpers.OverrideWithStringPtr(o.KeyPassphrase, other.KeyPassphrase)
o.PIAEncPreset = helpers.OverrideWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
o.IPv6 = helpers.OverrideWithBool(o.IPv6, other.IPv6)
o.MSSFix = helpers.OverrideWithUint16(o.MSSFix, other.MSSFix)
@@ -256,19 +309,24 @@ func (o *OpenVPN) overrideWith(other OpenVPN) {
}
func (o *OpenVPN) setDefaults(vpnProvider string) {
o.Version = helpers.DefaultString(o.Version, constants.Openvpn25)
o.Version = helpers.DefaultString(o.Version, openvpn.Openvpn25)
o.User = helpers.DefaultStringPtr(o.User, "")
if vpnProvider == providers.Mullvad {
o.Password = "m"
o.Password = helpers.DefaultStringPtr(o.Password, "m")
} else {
o.Password = helpers.DefaultStringPtr(o.Password, "")
}
o.ConfFile = helpers.DefaultStringPtr(o.ConfFile, "")
o.Auth = helpers.DefaultStringPtr(o.Auth, "")
o.ClientCrt = helpers.DefaultStringPtr(o.ClientCrt, "")
o.ClientKey = helpers.DefaultStringPtr(o.ClientKey, "")
o.Cert = helpers.DefaultStringPtr(o.Cert, "")
o.Key = helpers.DefaultStringPtr(o.Key, "")
o.EncryptedKey = helpers.DefaultStringPtr(o.EncryptedKey, "")
o.KeyPassphrase = helpers.DefaultStringPtr(o.KeyPassphrase, "")
var defaultEncPreset string
if vpnProvider == providers.PrivateInternetAccess {
defaultEncPreset = constants.PIAEncryptionPresetStrong
defaultEncPreset = presets.Strong
}
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
@@ -286,8 +344,8 @@ func (o OpenVPN) String() string {
func (o OpenVPN) toLinesNode() (node *gotree.Node) {
node = gotree.New("OpenVPN settings:")
node.Appendf("OpenVPN version: %s", o.Version)
node.Appendf("User: %s", helpers.ObfuscatePassword(o.User))
node.Appendf("Password: %s", helpers.ObfuscatePassword(o.Password))
node.Appendf("User: %s", helpers.ObfuscatePassword(*o.User))
node.Appendf("Password: %s", helpers.ObfuscatePassword(*o.Password))
if *o.ConfFile != "" {
node.Appendf("Custom configuration file: %s", *o.ConfFile)
@@ -301,12 +359,17 @@ func (o OpenVPN) toLinesNode() (node *gotree.Node) {
node.Appendf("Auth: %s", *o.Auth)
}
if *o.ClientCrt != "" {
node.Appendf("Client crt: %s", helpers.ObfuscateData(*o.ClientCrt))
if *o.Cert != "" {
node.Appendf("Client crt: %s", helpers.ObfuscateData(*o.Cert))
}
if *o.ClientKey != "" {
node.Appendf("Client key: %s", helpers.ObfuscateData(*o.ClientKey))
if *o.Key != "" {
node.Appendf("Client key: %s", helpers.ObfuscateData(*o.Key))
}
if *o.EncryptedKey != "" {
node.Appendf("Encrypted key: %s (key passhrapse %s)",
helpers.ObfuscateData(*o.EncryptedKey), helpers.ObfuscatePassword(*o.KeyPassphrase))
}
if *o.PIAEncPreset != "" {

View File

@@ -4,8 +4,8 @@ import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
"github.com/qdm12/gotree"
)
@@ -55,14 +55,13 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
switch vpnProvider {
// no restriction on port
case providers.Cyberghost, providers.HideMyAss,
providers.PrivateInternetAccess, providers.Privatevpn,
providers.Protonvpn, providers.Torguard:
providers.Privatevpn, providers.Torguard:
// no custom port allowed
case providers.Expressvpn, providers.Fastestvpn,
providers.Ipvanish, providers.Nordvpn,
providers.Privado, providers.Purevpn,
providers.Surfshark, providers.VPNUnlimited,
providers.Vyprvpn:
providers.Surfshark, providers.VPNSecure,
providers.VPNUnlimited, providers.Vyprvpn:
return fmt.Errorf("%w: for VPN service provider %s",
ErrOpenVPNCustomPortNotAllowed, vpnProvider)
default:
@@ -83,6 +82,9 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
case providers.Protonvpn:
allowedTCP = []uint16{443, 5995, 8443}
allowedUDP = []uint16{80, 443, 1194, 4569, 5060}
case providers.SlickVPN:
allowedTCP = []uint16{443, 8080, 8888}
allowedUDP = []uint16{443, 8080, 8888}
case providers.Wevpn:
allowedTCP = []uint16{53, 1195, 1199, 2018}
allowedUDP = []uint16{80, 1194, 1198}
@@ -106,9 +108,9 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
// Validate EncPreset
if vpnProvider == providers.PrivateInternetAccess {
validEncryptionPresets := []string{
constants.PIAEncryptionPresetNone,
constants.PIAEncryptionPresetNormal,
constants.PIAEncryptionPresetStrong,
presets.None,
presets.Normal,
presets.Strong,
}
if !helpers.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...) {
return fmt.Errorf("%w: %s; valid presets are %s",
@@ -150,7 +152,7 @@ func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
var defaultEncPreset string
if vpnProvider == providers.PrivateInternetAccess {
defaultEncPreset = constants.PIAEncryptionPresetStrong
defaultEncPreset = presets.Strong
}
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gotree"
)
@@ -23,11 +22,11 @@ type Provider struct {
}
// TODO v4 remove pointer for receiver (because of Surfshark).
func (p *Provider) validate(vpnType string, allServers models.AllServers) (err error) {
func (p *Provider) validate(vpnType string, storage Storage) (err error) {
// Validate Name
var validNames []string
if vpnType == vpn.OpenVPN {
validNames = providers.All()
validNames = providers.AllWithCustom()
validNames = append(validNames, "pia") // Retro-compatibility
} else { // Wireguard
validNames = []string{
@@ -42,7 +41,7 @@ func (p *Provider) validate(vpnType string, allServers models.AllServers) (err e
ErrVPNProviderNameNotValid, *p.Name, helpers.ChoicesOrString(validNames))
}
err = p.ServerSelection.validate(*p.Name, allServers)
err = p.ServerSelection.validate(*p.Name, storage)
if err != nil {
return fmt.Errorf("server selection: %w", err)
}

View File

@@ -48,18 +48,18 @@ func (p *PublicIP) copy() (copied PublicIP) {
}
func (p *PublicIP) mergeWith(other PublicIP) {
p.Period = helpers.MergeWithDuration(p.Period, other.Period)
p.Period = helpers.MergeWithDurationPtr(p.Period, other.Period)
p.IPFilepath = helpers.MergeWithStringPtr(p.IPFilepath, other.IPFilepath)
}
func (p *PublicIP) overrideWith(other PublicIP) {
p.Period = helpers.OverrideWithDuration(p.Period, other.Period)
p.Period = helpers.OverrideWithDurationPtr(p.Period, other.Period)
p.IPFilepath = helpers.OverrideWithStringPtr(p.IPFilepath, other.IPFilepath)
}
func (p *PublicIP) setDefaults() {
const defaultPeriod = 12 * time.Hour
p.Period = helpers.DefaultDuration(p.Period, defaultPeriod)
p.Period = helpers.DefaultDurationPtr(p.Period, defaultPeriod)
p.IPFilepath = helpers.DefaultStringPtr(p.IPFilepath, "/tmp/gluetun/ip")
}

View File

@@ -45,6 +45,10 @@ type ServerSelection struct { //nolint:maligned
// FreeOnly is true if VPN servers that are not free should
// be filtered. This is used with ProtonVPN and VPN Unlimited.
FreeOnly *bool
// PremiumOnly is true if VPN servers that are not premium should
// be filtered. This is used with VPN Secure.
// TODO extend to providers using FreeOnly.
PremiumOnly *bool
// StreamOnly is true if VPN servers not for streaming should
// be filtered. This is used with VPNUnlimited.
StreamOnly *bool
@@ -63,26 +67,26 @@ type ServerSelection struct { //nolint:maligned
var (
ErrOwnedOnlyNotSupported = errors.New("owned only filter is not supported")
ErrFreeOnlyNotSupported = errors.New("free only filter is not supported")
ErrPremiumOnlyNotSupported = errors.New("premium only filter is not supported")
ErrStreamOnlyNotSupported = errors.New("stream only filter is not supported")
ErrMultiHopOnlyNotSupported = errors.New("multi hop only filter is not supported")
ErrFreePremiumBothSet = errors.New("free only and premium only filters are both set")
)
func (ss *ServerSelection) validate(vpnServiceProvider string,
allServers models.AllServers) (err error) {
storage Storage) (err error) {
switch ss.VPN {
case vpn.OpenVPN, vpn.Wireguard:
default:
return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN)
}
countryChoices, regionChoices, cityChoices,
ispChoices, nameChoices, hostnameChoices, err := getLocationFilterChoices(vpnServiceProvider, ss, allServers)
filterChoices, err := getLocationFilterChoices(vpnServiceProvider, ss, storage)
if err != nil {
return err // already wrapped error
}
err = validateServerFilters(*ss, countryChoices, regionChoices, cityChoices,
ispChoices, nameChoices, hostnameChoices)
err = validateServerFilters(*ss, filterChoices)
if err != nil {
if errors.Is(err, helpers.ErrNoChoice) {
return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err)
@@ -105,6 +109,18 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
ErrFreeOnlyNotSupported, vpnServiceProvider)
}
if *ss.PremiumOnly &&
!helpers.IsOneOf(vpnServiceProvider,
providers.VPNSecure,
) {
return fmt.Errorf("%w: for VPN service provider %s",
ErrPremiumOnlyNotSupported, vpnServiceProvider)
}
if *ss.FreeOnly && *ss.PremiumOnly {
return ErrFreePremiumBothSet
}
if *ss.StreamOnly &&
!helpers.IsOneOf(vpnServiceProvider,
providers.Protonvpn,
@@ -135,155 +151,48 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
return nil
}
func getLocationFilterChoices(vpnServiceProvider string, ss *ServerSelection,
allServers models.AllServers) (
countryChoices, regionChoices, cityChoices,
ispChoices, nameChoices, hostnameChoices []string,
func getLocationFilterChoices(vpnServiceProvider string,
ss *ServerSelection, storage Storage) (filterChoices models.FilterChoices,
err error) {
switch vpnServiceProvider {
case providers.Custom:
case providers.Cyberghost:
servers := allServers.GetCyberghost()
countryChoices = validation.ExtractCountries(servers)
hostnameChoices = validation.ExtractHostnames(servers)
case providers.Expressvpn:
servers := allServers.GetExpressvpn()
countryChoices = validation.ExtractCountries(servers)
cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.ExtractHostnames(servers)
case providers.Fastestvpn:
servers := allServers.GetFastestvpn()
countryChoices = validation.ExtractCountries(servers)
hostnameChoices = validation.ExtractHostnames(servers)
case providers.HideMyAss:
servers := allServers.GetHideMyAss()
countryChoices = validation.ExtractCountries(servers)
regionChoices = validation.ExtractRegions(servers)
cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.ExtractHostnames(servers)
case providers.Ipvanish:
servers := allServers.GetIpvanish()
countryChoices = validation.ExtractCountries(servers)
cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.ExtractHostnames(servers)
case providers.Ivpn:
servers := allServers.GetIvpn()
countryChoices = validation.ExtractCountries(servers)
cityChoices = validation.ExtractCities(servers)
ispChoices = validation.ExtractISPs(servers)
hostnameChoices = validation.ExtractHostnames(servers)
case providers.Mullvad:
servers := allServers.GetMullvad()
countryChoices = validation.ExtractCountries(servers)
cityChoices = validation.ExtractCities(servers)
ispChoices = validation.ExtractISPs(servers)
hostnameChoices = validation.ExtractHostnames(servers)
case providers.Nordvpn:
servers := allServers.GetNordvpn()
regionChoices = validation.ExtractRegions(servers)
hostnameChoices = validation.ExtractHostnames(servers)
case providers.Perfectprivacy:
servers := allServers.GetPerfectprivacy()
cityChoices = validation.ExtractCities(servers)
case providers.Privado:
servers := allServers.GetPrivado()
countryChoices = validation.ExtractCountries(servers)
regionChoices = validation.ExtractRegions(servers)
cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.ExtractHostnames(servers)
case providers.PrivateInternetAccess:
servers := allServers.GetPia()
regionChoices = validation.ExtractRegions(servers)
hostnameChoices = validation.ExtractHostnames(servers)
nameChoices = validation.ExtractServerNames(servers)
case providers.Privatevpn:
servers := allServers.GetPrivatevpn()
countryChoices = validation.ExtractCountries(servers)
cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.ExtractHostnames(servers)
case providers.Protonvpn:
servers := allServers.GetProtonvpn()
countryChoices = validation.ExtractCountries(servers)
regionChoices = validation.ExtractRegions(servers)
cityChoices = validation.ExtractCities(servers)
nameChoices = validation.ExtractServerNames(servers)
hostnameChoices = validation.ExtractHostnames(servers)
case providers.Purevpn:
servers := allServers.GetPurevpn()
countryChoices = validation.ExtractCountries(servers)
regionChoices = validation.ExtractRegions(servers)
cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.ExtractHostnames(servers)
case providers.Surfshark:
servers := allServers.GetSurfshark()
countryChoices = validation.ExtractCountries(servers)
cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.ExtractHostnames(servers)
regionChoices = validation.ExtractRegions(servers)
filterChoices = storage.GetFilterChoices(vpnServiceProvider)
if vpnServiceProvider == providers.Surfshark {
// // Retro compatibility
// TODO v4 remove
regionChoices = append(regionChoices, validation.SurfsharkRetroLocChoices()...)
if err := helpers.AreAllOneOf(ss.Regions, regionChoices); err != nil {
return nil, nil, nil, nil, nil, nil, fmt.Errorf("%w: %s", ErrRegionNotValid, err)
filterChoices.Regions = append(filterChoices.Regions, validation.SurfsharkRetroLocChoices()...)
if err := helpers.AreAllOneOf(ss.Regions, filterChoices.Regions); err != nil {
return models.FilterChoices{}, fmt.Errorf("%w: %s", ErrRegionNotValid, err)
}
// Retro compatibility
// TODO remove in v4
*ss = surfsharkRetroRegion(*ss)
case providers.Torguard:
servers := allServers.GetTorguard()
countryChoices = validation.ExtractCountries(servers)
cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.ExtractHostnames(servers)
case providers.VPNUnlimited:
servers := allServers.GetVPNUnlimited()
countryChoices = validation.ExtractCountries(servers)
cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.ExtractHostnames(servers)
case providers.Vyprvpn:
servers := allServers.GetVyprvpn()
regionChoices = validation.ExtractRegions(servers)
case providers.Wevpn:
servers := allServers.GetWevpn()
cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.ExtractHostnames(servers)
case providers.Windscribe:
servers := allServers.GetWindscribe()
regionChoices = validation.ExtractRegions(servers)
cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.ExtractHostnames(servers)
default:
return nil, nil, nil, nil, nil, nil, fmt.Errorf("%w: %s", ErrVPNProviderNameNotValid, vpnServiceProvider)
}
return countryChoices, regionChoices, cityChoices,
ispChoices, nameChoices, hostnameChoices, nil
return filterChoices, nil
}
// validateServerFilters validates filters against the choices given as arguments.
// Set an argument to nil to pass the check for a particular filter.
func validateServerFilters(settings ServerSelection,
countryChoices, regionChoices, cityChoices, ispChoices,
nameChoices, hostnameChoices []string) (err error) {
if err := helpers.AreAllOneOf(settings.Countries, countryChoices); err != nil {
func validateServerFilters(settings ServerSelection, filterChoices models.FilterChoices) (err error) {
if err := helpers.AreAllOneOf(settings.Countries, filterChoices.Countries); err != nil {
return fmt.Errorf("%w: %s", ErrCountryNotValid, err)
}
if err := helpers.AreAllOneOf(settings.Regions, regionChoices); err != nil {
if err := helpers.AreAllOneOf(settings.Regions, filterChoices.Regions); err != nil {
return fmt.Errorf("%w: %s", ErrRegionNotValid, err)
}
if err := helpers.AreAllOneOf(settings.Cities, cityChoices); err != nil {
if err := helpers.AreAllOneOf(settings.Cities, filterChoices.Cities); err != nil {
return fmt.Errorf("%w: %s", ErrCityNotValid, err)
}
if err := helpers.AreAllOneOf(settings.ISPs, ispChoices); err != nil {
if err := helpers.AreAllOneOf(settings.ISPs, filterChoices.ISPs); err != nil {
return fmt.Errorf("%w: %s", ErrISPNotValid, err)
}
if err := helpers.AreAllOneOf(settings.Hostnames, hostnameChoices); err != nil {
if err := helpers.AreAllOneOf(settings.Hostnames, filterChoices.Hostnames); err != nil {
return fmt.Errorf("%w: %s", ErrHostnameNotValid, err)
}
if err := helpers.AreAllOneOf(settings.Names, nameChoices); err != nil {
if err := helpers.AreAllOneOf(settings.Names, filterChoices.Names); err != nil {
return fmt.Errorf("%w: %s", ErrNameNotValid, err)
}
@@ -303,6 +212,7 @@ func (ss *ServerSelection) copy() (copied ServerSelection) {
Numbers: helpers.CopyUint16Slice(ss.Numbers),
OwnedOnly: helpers.CopyBoolPtr(ss.OwnedOnly),
FreeOnly: helpers.CopyBoolPtr(ss.FreeOnly),
PremiumOnly: helpers.CopyBoolPtr(ss.PremiumOnly),
StreamOnly: helpers.CopyBoolPtr(ss.StreamOnly),
MultiHopOnly: helpers.CopyBoolPtr(ss.MultiHopOnly),
OpenVPN: ss.OpenVPN.copy(),
@@ -322,6 +232,7 @@ func (ss *ServerSelection) mergeWith(other ServerSelection) {
ss.Numbers = helpers.MergeUint16Slices(ss.Numbers, other.Numbers)
ss.OwnedOnly = helpers.MergeWithBool(ss.OwnedOnly, other.OwnedOnly)
ss.FreeOnly = helpers.MergeWithBool(ss.FreeOnly, other.FreeOnly)
ss.PremiumOnly = helpers.MergeWithBool(ss.PremiumOnly, other.PremiumOnly)
ss.StreamOnly = helpers.MergeWithBool(ss.StreamOnly, other.StreamOnly)
ss.MultiHopOnly = helpers.MergeWithBool(ss.MultiHopOnly, other.MultiHopOnly)
@@ -341,6 +252,7 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) {
ss.Numbers = helpers.OverrideWithUint16Slice(ss.Numbers, other.Numbers)
ss.OwnedOnly = helpers.OverrideWithBool(ss.OwnedOnly, other.OwnedOnly)
ss.FreeOnly = helpers.OverrideWithBool(ss.FreeOnly, other.FreeOnly)
ss.PremiumOnly = helpers.OverrideWithBool(ss.PremiumOnly, other.PremiumOnly)
ss.StreamOnly = helpers.OverrideWithBool(ss.StreamOnly, other.StreamOnly)
ss.MultiHopOnly = helpers.OverrideWithBool(ss.MultiHopOnly, other.MultiHopOnly)
ss.OpenVPN.overrideWith(other.OpenVPN)
@@ -352,6 +264,7 @@ func (ss *ServerSelection) setDefaults(vpnProvider string) {
ss.TargetIP = helpers.DefaultIP(ss.TargetIP, net.IP{})
ss.OwnedOnly = helpers.DefaultBool(ss.OwnedOnly, false)
ss.FreeOnly = helpers.DefaultBool(ss.FreeOnly, false)
ss.PremiumOnly = helpers.DefaultBool(ss.PremiumOnly, false)
ss.StreamOnly = helpers.DefaultBool(ss.StreamOnly, false)
ss.MultiHopOnly = helpers.DefaultBool(ss.MultiHopOnly, false)
ss.OpenVPN.setDefaults(vpnProvider)
@@ -408,6 +321,10 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
node.Appendf("Free only servers: yes")
}
if *ss.PremiumOnly {
node.Appendf("Premium only servers: yes")
}
if *ss.StreamOnly {
node.Appendf("Stream only servers: yes")
}

View File

@@ -24,10 +24,14 @@ type Settings struct {
Pprof pprof.Settings
}
type Storage interface {
GetFilterChoices(provider string) models.FilterChoices
}
// Validate validates all the settings and returns an error
// if one of them is not valid.
// TODO v4 remove pointer for receiver (because of Surfshark).
func (s *Settings) Validate(allServers models.AllServers) (err error) {
func (s *Settings) Validate(storage Storage) (err error) {
nameToValidation := map[string]func() error{
"control server": s.ControlServer.validate,
"dns": s.DNS.validate,
@@ -42,7 +46,7 @@ func (s *Settings) Validate(allServers models.AllServers) (err error) {
"version": s.Version.validate,
// Pprof validation done in pprof constructor
"VPN": func() error {
return s.VPN.validate(allServers)
return s.VPN.Validate(storage)
},
}
@@ -69,7 +73,7 @@ func (s *Settings) copy() (copied Settings) {
System: s.System.copy(),
Updater: s.Updater.copy(),
Version: s.Version.copy(),
VPN: s.VPN.copy(),
VPN: s.VPN.Copy(),
Pprof: s.Pprof.Copy(),
}
}
@@ -91,7 +95,7 @@ func (s *Settings) MergeWith(other Settings) {
}
func (s *Settings) OverrideWith(other Settings,
allServers models.AllServers) (err error) {
storage Storage) (err error) {
patchedSettings := s.copy()
patchedSettings.ControlServer.overrideWith(other.ControlServer)
patchedSettings.DNS.overrideWith(other.DNS)
@@ -104,9 +108,9 @@ func (s *Settings) OverrideWith(other Settings,
patchedSettings.System.overrideWith(other.System)
patchedSettings.Updater.overrideWith(other.Updater)
patchedSettings.Version.overrideWith(other.Version)
patchedSettings.VPN.overrideWith(other.VPN)
patchedSettings.Pprof.MergeWith(other.Pprof)
err = patchedSettings.Validate(allServers)
patchedSettings.VPN.OverrideWith(other.VPN)
patchedSettings.Pprof.OverrideWith(other.Pprof)
err = patchedSettings.Validate(storage)
if err != nil {
return err
}

View File

@@ -67,6 +67,8 @@ func Test_Settings_String(t *testing.T) {
├── Health settings:
| ├── Server listening address: 127.0.0.1:9999
| ├── Target address: cloudflare.com:443
| ├── Read header timeout: 100ms
| ├── Read timeout: 500ms
| └── VPN wait durations:
| ├── Initial duration: 6s
| └── Additional duration: 5s

View File

@@ -3,15 +3,14 @@ package settings
import (
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/surfshark/servers"
)
func surfsharkRetroRegion(selection ServerSelection) (
updatedSelection ServerSelection) {
locationData := constants.SurfsharkLocationData()
locationData := servers.LocationData()
retroToLocation := make(map[string]models.SurfsharkLocationData, len(locationData))
retroToLocation := make(map[string]servers.ServerLocation, len(locationData))
for _, data := range locationData {
if data.RetroLoc == "" {
continue

View File

@@ -2,7 +2,6 @@ package settings
import (
"fmt"
"net"
"strings"
"time"
@@ -21,16 +20,15 @@ type Updater struct {
Period *time.Duration
// DNSAddress is the DNS server address to use
// to resolve VPN server hostnames to IP addresses.
// It cannot be nil in the internal state.
DNSAddress net.IP
// It cannot be the empty string in the internal state.
DNSAddress string
// MinRatio is the minimum ratio of servers to
// find per provider, compared to the total current
// number of servers. It defaults to 0.8.
MinRatio float64
// Providers is the list of VPN service providers
// to update server information for.
Providers []string
// CLI is to precise the updater is running in CLI
// mode. This is set automatically and cannot be set
// by settings sources. It cannot be nil in the
// internal state.
CLI *bool
}
func (u Updater) Validate() (err error) {
@@ -40,21 +38,23 @@ func (u Updater) Validate() (err error) {
ErrUpdaterPeriodTooSmall, *u.Period, minPeriod)
}
for i, provider := range u.Providers {
valid := false
for _, validProvider := range providers.All() {
if validProvider == providers.Custom {
continue
}
if u.MinRatio <= 0 || u.MinRatio > 1 {
return fmt.Errorf("%w: %.2f must be between 0+ and 1",
ErrMinRatioNotValid, u.MinRatio)
}
validProviders := providers.All()
for _, provider := range u.Providers {
valid := false
for _, validProvider := range validProviders {
if provider == validProvider {
valid = true
break
}
}
if !valid {
return fmt.Errorf("%w: %s at index %d",
ErrVPNProviderNameNotValid, provider, i)
return fmt.Errorf("%w: %q can only be one of %s",
ErrVPNProviderNameNotValid, provider, helpers.ChoicesOrString(validProviders))
}
}
@@ -64,35 +64,40 @@ func (u Updater) Validate() (err error) {
func (u *Updater) copy() (copied Updater) {
return Updater{
Period: helpers.CopyDurationPtr(u.Period),
DNSAddress: helpers.CopyIP(u.DNSAddress),
DNSAddress: u.DNSAddress,
MinRatio: u.MinRatio,
Providers: helpers.CopyStringSlice(u.Providers),
CLI: u.CLI,
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (u *Updater) mergeWith(other Updater) {
u.Period = helpers.MergeWithDuration(u.Period, other.Period)
u.DNSAddress = helpers.MergeWithIP(u.DNSAddress, other.DNSAddress)
u.Period = helpers.MergeWithDurationPtr(u.Period, other.Period)
u.DNSAddress = helpers.MergeWithString(u.DNSAddress, other.DNSAddress)
u.MinRatio = helpers.MergeWithFloat64(u.MinRatio, other.MinRatio)
u.Providers = helpers.MergeStringSlices(u.Providers, other.Providers)
u.CLI = helpers.MergeWithBool(u.CLI, other.CLI)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (u *Updater) overrideWith(other Updater) {
u.Period = helpers.OverrideWithDuration(u.Period, other.Period)
u.DNSAddress = helpers.OverrideWithIP(u.DNSAddress, other.DNSAddress)
u.Period = helpers.OverrideWithDurationPtr(u.Period, other.Period)
u.DNSAddress = helpers.OverrideWithString(u.DNSAddress, other.DNSAddress)
u.MinRatio = helpers.OverrideWithFloat64(u.MinRatio, other.MinRatio)
u.Providers = helpers.OverrideWithStringSlice(u.Providers, other.Providers)
u.CLI = helpers.MergeWithBool(u.CLI, other.CLI)
}
func (u *Updater) SetDefaults(vpnProvider string) {
u.Period = helpers.DefaultDuration(u.Period, 0)
u.DNSAddress = helpers.DefaultIP(u.DNSAddress, net.IPv4(1, 1, 1, 1))
u.CLI = helpers.DefaultBool(u.CLI, false)
u.Period = helpers.DefaultDurationPtr(u.Period, 0)
u.DNSAddress = helpers.DefaultString(u.DNSAddress, "1.1.1.1:53")
if u.MinRatio == 0 {
const defaultMinRatio = 0.8
u.MinRatio = defaultMinRatio
}
if len(u.Providers) == 0 && vpnProvider != providers.Custom {
u.Providers = []string{vpnProvider}
}
@@ -110,11 +115,8 @@ func (u Updater) toLinesNode() (node *gotree.Node) {
node = gotree.New("Server data updater settings:")
node.Appendf("Update period: %s", *u.Period)
node.Appendf("DNS address: %s", u.DNSAddress)
node.Appendf("Minimum ratio: %.1f", u.MinRatio)
node.Appendf("Providers to update: %s", strings.Join(u.Providers, ", "))
if *u.CLI {
node.Appendf("CLI mode: enabled")
}
return node
}

View File

@@ -19,6 +19,9 @@ func ExtractCountries(servers []models.Server) (values []string) {
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.Country
if value == "" {
continue
}
_, alreadySeen := seen[value]
if alreadySeen {
continue
@@ -35,6 +38,9 @@ func ExtractRegions(servers []models.Server) (values []string) {
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.Region
if value == "" {
continue
}
_, alreadySeen := seen[value]
if alreadySeen {
continue
@@ -51,6 +57,9 @@ func ExtractCities(servers []models.Server) (values []string) {
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.City
if value == "" {
continue
}
_, alreadySeen := seen[value]
if alreadySeen {
continue
@@ -67,6 +76,9 @@ func ExtractISPs(servers []models.Server) (values []string) {
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.ISP
if value == "" {
continue
}
_, alreadySeen := seen[value]
if alreadySeen {
continue
@@ -83,6 +95,9 @@ func ExtractServerNames(servers []models.Server) (values []string) {
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.ServerName
if value == "" {
continue
}
_, alreadySeen := seen[value]
if alreadySeen {
continue
@@ -99,6 +114,9 @@ func ExtractHostnames(servers []models.Server) (values []string) {
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.Hostname
if value == "" {
continue
}
_, alreadySeen := seen[value]
if alreadySeen {
continue

View File

@@ -1,12 +1,12 @@
package validation
import (
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/provider/surfshark/servers"
)
// TODO remove in v4.
func SurfsharkRetroLocChoices() (choices []string) {
locationData := constants.SurfsharkLocationData()
locationData := servers.LocationData()
choices = make([]string, 0, len(locationData))
seen := make(map[string]struct{}, len(locationData))
for _, data := range locationData {

View File

@@ -6,7 +6,6 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gotree"
)
@@ -21,7 +20,7 @@ type VPN struct {
}
// TODO v4 remove pointer for receiver (because of Surfshark).
func (v *VPN) validate(allServers models.AllServers) (err error) {
func (v *VPN) Validate(storage Storage) (err error) {
// Validate Type
validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard}
if !helpers.IsOneOf(v.Type, validVPNTypes...) {
@@ -29,7 +28,7 @@ func (v *VPN) validate(allServers models.AllServers) (err error) {
ErrVPNTypeNotValid, v.Type, strings.Join(validVPNTypes, ", "))
}
err = v.Provider.validate(v.Type, allServers)
err = v.Provider.validate(v.Type, storage)
if err != nil {
return fmt.Errorf("provider settings: %w", err)
}
@@ -49,7 +48,7 @@ func (v *VPN) validate(allServers models.AllServers) (err error) {
return nil
}
func (v *VPN) copy() (copied VPN) {
func (v *VPN) Copy() (copied VPN) {
return VPN{
Type: v.Type,
Provider: v.Provider.copy(),
@@ -65,7 +64,7 @@ func (v *VPN) mergeWith(other VPN) {
v.Wireguard.mergeWith(other.Wireguard)
}
func (v *VPN) overrideWith(other VPN) {
func (v *VPN) OverrideWith(other VPN) {
v.Type = helpers.OverrideWithString(v.Type, other.Type)
v.Provider.overrideWith(other.Provider)
v.OpenVPN.overrideWith(other.OpenVPN)

View File

@@ -2,14 +2,13 @@ package env
import (
"fmt"
"os"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (r *Reader) ReadHealth() (health settings.Health, err error) {
health.ServerAddress = os.Getenv("HEALTH_SERVER_ADDRESS")
health.ServerAddress = getCleanedEnv("HEALTH_SERVER_ADDRESS")
_, health.TargetAddress = r.getEnvWithRetro("HEALTH_TARGET_ADDRESS", "HEALTH_ADDRESS_TO_PING")
health.VPN.Initial, err = r.readDurationWithRetro(

View File

@@ -1,7 +1,6 @@
package env
import (
"encoding/base64"
"fmt"
"os"
"strconv"
@@ -12,8 +11,18 @@ import (
"github.com/qdm12/govalid/integer"
)
// getCleanedEnv returns an environment variable value with
// surrounding spaces and trailing new line characters removed.
func getCleanedEnv(envKey string) (value string) {
value = os.Getenv(envKey)
value = strings.TrimSpace(value)
value = strings.TrimSuffix(value, "\r\n")
value = strings.TrimSuffix(value, "\n")
return value
}
func envToCSV(envKey string) (values []string) {
csv := os.Getenv(envKey)
csv := getCleanedEnv(envKey)
if csv == "" {
return nil
}
@@ -21,15 +30,24 @@ func envToCSV(envKey string) (values []string) {
}
func envToInt(envKey string) (n int, err error) {
s := os.Getenv(envKey)
s := getCleanedEnv(envKey)
if s == "" {
return 0, nil
}
return strconv.Atoi(s)
}
func envToFloat64(envKey string) (f float64, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return 0, nil
}
const bits = 64
return strconv.ParseFloat(s, bits)
}
func envToStringPtr(envKey string) (stringPtr *string) {
s := os.Getenv(envKey)
s := getCleanedEnv(envKey)
if s == "" {
return nil
}
@@ -37,7 +55,7 @@ func envToStringPtr(envKey string) (stringPtr *string) {
}
func envToBoolPtr(envKey string) (boolPtr *bool, err error) {
s := os.Getenv(envKey)
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
@@ -49,7 +67,7 @@ func envToBoolPtr(envKey string) (boolPtr *bool, err error) {
}
func envToIntPtr(envKey string) (intPtr *int, err error) {
s := os.Getenv(envKey)
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
@@ -61,7 +79,7 @@ func envToIntPtr(envKey string) (intPtr *int, err error) {
}
func envToUint8Ptr(envKey string) (uint8Ptr *uint8, err error) {
s := os.Getenv(envKey)
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
@@ -78,7 +96,7 @@ func envToUint8Ptr(envKey string) (uint8Ptr *uint8, err error) {
}
func envToUint16Ptr(envKey string) (uint16Ptr *uint16, err error) {
s := os.Getenv(envKey)
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
@@ -95,7 +113,7 @@ func envToUint16Ptr(envKey string) (uint16Ptr *uint16, err error) {
}
func envToDurationPtr(envKey string) (durationPtr *time.Duration, err error) {
s := os.Getenv(envKey)
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
@@ -114,15 +132,6 @@ func lowerAndSplit(csv string) (values []string) {
return strings.Split(csv, ",")
}
func decodeBase64(b64String string) (decoded string, err error) {
b, err := base64.StdEncoding.DecodeString(b64String)
if err != nil {
return "", fmt.Errorf("cannot decode base64 string %q: %w",
b64String, err)
}
return string(b), nil
}
func unsetEnvKeys(envKeys []string, err error) (newErr error) {
newErr = err
for _, envKey := range envKeys {

View File

@@ -20,7 +20,3 @@ func setTestEnv(t *testing.T, key, value string) {
})
require.NoError(t, err)
}
func TestXxx(t *testing.T) {
t.Log(int(^uint32(0)))
}

View File

@@ -3,7 +3,6 @@ package env
import (
"errors"
"fmt"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
@@ -20,7 +19,7 @@ func readLog() (log settings.Log, err error) {
}
func readLogLevel() (level *log.Level, err error) {
s := os.Getenv("LOG_LEVEL")
s := getCleanedEnv("LOG_LEVEL")
if s == "" {
return nil, nil //nolint:nilnil
}

View File

@@ -2,7 +2,6 @@ package env
import (
"fmt"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
@@ -12,13 +11,14 @@ import (
func (r *Reader) readOpenVPN() (
openVPN settings.OpenVPN, err error) {
defer func() {
err = unsetEnvKeys([]string{"OPENVPN_CLIENTKEY", "OPENVPN_CLIENTCRT"}, err)
err = unsetEnvKeys([]string{"OPENVPN_KEY", "OPENVPN_CERT",
"OPENVPN_KEY_PASSPHRASE", "OPENVPN_ENCRYPTED_KEY"}, err)
}()
openVPN.Version = os.Getenv("OPENVPN_VERSION")
openVPN.Version = getCleanedEnv("OPENVPN_VERSION")
openVPN.User = r.readOpenVPNUser()
openVPN.Password = r.readOpenVPNPassword()
confFile := os.Getenv("OPENVPN_CUSTOM_CONFIG")
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG")
if confFile != "" {
openVPN.ConfFile = &confFile
}
@@ -26,20 +26,16 @@ func (r *Reader) readOpenVPN() (
ciphersKey, _ := r.getEnvWithRetro("OPENVPN_CIPHERS", "OPENVPN_CIPHER")
openVPN.Ciphers = envToCSV(ciphersKey)
auth := os.Getenv("OPENVPN_AUTH")
auth := getCleanedEnv("OPENVPN_AUTH")
if auth != "" {
openVPN.Auth = &auth
}
openVPN.ClientCrt, err = readBase64OrNil("OPENVPN_CLIENTCRT")
if err != nil {
return openVPN, fmt.Errorf("environment variable OPENVPN_CLIENTCRT: %w", err)
}
openVPN.Cert = envToStringPtr("OPENVPN_CERT")
openVPN.Key = envToStringPtr("OPENVPN_KEY")
openVPN.EncryptedKey = envToStringPtr("OPENVPN_ENCRYPTED_KEY")
openVPN.ClientKey, err = readBase64OrNil("OPENVPN_CLIENTKEY")
if err != nil {
return openVPN, fmt.Errorf("environment variable OPENVPN_CLIENTKEY: %w", err)
}
openVPN.KeyPassphrase = r.readOpenVPNKeyPassphrase()
openVPN.PIAEncPreset = r.readPIAEncryptionPreset()
@@ -65,7 +61,7 @@ func (r *Reader) readOpenVPN() (
return openVPN, fmt.Errorf("environment variable OPENVPN_VERBOSITY: %w", err)
}
flagsStr := os.Getenv("OPENVPN_FLAGS")
flagsStr := getCleanedEnv("OPENVPN_FLAGS")
if flagsStr != "" {
openVPN.Flags = strings.Fields(flagsStr)
}
@@ -73,29 +69,35 @@ func (r *Reader) readOpenVPN() (
return openVPN, nil
}
func (r *Reader) readOpenVPNUser() (user string) {
_, user = r.getEnvWithRetro("OPENVPN_USER", "USER")
func (r *Reader) readOpenVPNUser() (user *string) {
user = new(string)
_, *user = r.getEnvWithRetro("OPENVPN_USER", "USER")
if *user == "" {
return nil
}
// Remove spaces in user ID to simplify user's life, thanks @JeordyR
return strings.ReplaceAll(user, " ", "")
*user = strings.ReplaceAll(*user, " ", "")
return user
}
func (r *Reader) readOpenVPNPassword() (password string) {
_, password = r.getEnvWithRetro("OPENVPN_PASSWORD", "PASSWORD")
func (r *Reader) readOpenVPNPassword() (password *string) {
password = new(string)
_, *password = r.getEnvWithRetro("OPENVPN_PASSWORD", "PASSWORD")
if *password == "" {
return nil
}
return password
}
func readBase64OrNil(envKey string) (valueOrNil *string, err error) {
value := os.Getenv(envKey)
if value == "" {
return nil, nil //nolint:nilnil
func (r *Reader) readOpenVPNKeyPassphrase() (passphrase *string) {
passphrase = new(string)
*passphrase = getCleanedEnv("OPENVPN_KEY_PASSPHRASE")
if *passphrase == "" {
return nil
}
decoded, err := decodeBase64(value)
if err != nil {
return nil, err
}
return &decoded, nil
return passphrase
}
func (r *Reader) readPIAEncryptionPreset() (presetPtr *string) {

View File

@@ -3,7 +3,6 @@ package env
import (
"errors"
"fmt"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
@@ -13,7 +12,7 @@ import (
func (r *Reader) readOpenVPNSelection() (
selection settings.OpenVPNSelection, err error) {
confFile := os.Getenv("OPENVPN_CUSTOM_CONFIG")
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG")
if confFile != "" {
selection.ConfFile = &confFile
}

View File

@@ -2,7 +2,6 @@ package env
import (
"fmt"
"os"
"github.com/qdm12/gluetun/internal/pprof"
)
@@ -23,7 +22,7 @@ func readPprof() (settings pprof.Settings, err error) {
return settings, fmt.Errorf("environment variable PPROF_MUTEX_PROFILE_RATE: %w", err)
}
settings.HTTPServer.Address = os.Getenv("PPROF_HTTP_SERVER_ADDRESS")
settings.HTTPServer.Address = getCleanedEnv("PPROF_HTTP_SERVER_ADDRESS")
return settings, nil
}

View File

@@ -2,7 +2,6 @@ package env
import (
"fmt"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
@@ -35,7 +34,7 @@ func (r *Reader) readVPNServiceProvider(vpnType string) (vpnProviderPtr *string)
s = strings.ToLower(s)
switch {
case vpnType != vpn.Wireguard &&
os.Getenv("OPENVPN_CUSTOM_CONFIG") != "": // retro compatibility
getCleanedEnv("OPENVPN_CUSTOM_CONFIG") != "": // retro compatibility
return stringPtr(providers.Custom)
case s == "":
return nil

View File

@@ -2,7 +2,6 @@ package env
import (
"fmt"
"os"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings"
@@ -20,7 +19,7 @@ func (r *Reader) readPublicIP() (publicIP settings.PublicIP, err error) {
}
func readPublicIPPeriod() (period *time.Duration, err error) {
s := os.Getenv("PUBLICIP_PERIOD")
s := getCleanedEnv("PUBLICIP_PERIOD")
if s == "" {
return nil, nil //nolint:nilnil
}

View File

@@ -1,8 +1,6 @@
package env
import (
"os"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/configuration/sources"
)
@@ -111,12 +109,12 @@ func (r *Reader) getEnvWithRetro(currentKey string,
// We check retro-compatibility keys first since
// the current key might be set in the Dockerfile.
for _, key = range retroKeys {
value = os.Getenv(key)
value = getCleanedEnv(key)
if value != "" {
r.onRetroActive(key, currentKey)
return key, value
}
}
return currentKey, os.Getenv(currentKey)
return currentKey, getCleanedEnv(currentKey)
}

View File

@@ -2,7 +2,6 @@ package env
import (
"fmt"
"os"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary"
@@ -20,7 +19,7 @@ func (r *Reader) readControlServer() (controlServer settings.ControlServer, err
}
func readControlServerLog() (enabled *bool, err error) {
s := os.Getenv("HTTP_CONTROL_SERVER_LOG")
s := getCleanedEnv("HTTP_CONTROL_SERVER_LOG")
if s == "" {
return nil, nil //nolint:nilnil
}

View File

@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"net"
"os"
"strconv"
"strings"
@@ -49,7 +48,7 @@ func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
serverNamesKey, _ := r.getEnvWithRetro("SERVER_NAMES", "SERVER_NAME")
ss.Names = envToCSV(serverNamesKey)
if csv := os.Getenv("SERVER_NUMBER"); csv != "" {
if csv := getCleanedEnv("SERVER_NUMBER"); csv != "" {
numbersStrings := strings.Split(csv, ",")
numbers := make([]uint16, len(numbersStrings))
for i, numberString := range numbersStrings {
@@ -78,6 +77,12 @@ func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
return ss, fmt.Errorf("environment variable FREE_ONLY: %w", err)
}
// VPNSecure only
ss.PremiumOnly, err = envToBoolPtr("PREMIUM_ONLY")
if err != nil {
return ss, fmt.Errorf("environment variable PREMIUM_ONLY: %w", err)
}
// VPNUnlimited only
ss.MultiHopOnly, err = envToBoolPtr("MULTIHOP_ONLY")
if err != nil {

View File

@@ -3,7 +3,6 @@ package env
import (
"errors"
"fmt"
"os"
"strconv"
"github.com/qdm12/gluetun/internal/configuration/settings"
@@ -26,7 +25,7 @@ func (r *Reader) readSystem() (system settings.System, err error) {
return system, err
}
system.Timezone = os.Getenv("TZ")
system.Timezone = getCleanedEnv("TZ")
return system, nil
}

View File

@@ -2,8 +2,6 @@ package env
import (
"fmt"
"net"
"os"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings"
@@ -20,13 +18,18 @@ func readUpdater() (updater settings.Updater, err error) {
return updater, err
}
updater.MinRatio, err = envToFloat64("UPDATER_MIN_RATIO")
if err != nil {
return updater, fmt.Errorf("environment variable UPDATER_MIN_RATIO: %w", err)
}
updater.Providers = envToCSV("UPDATER_VPN_SERVICE_PROVIDERS")
return updater, nil
}
func readUpdaterPeriod() (period *time.Duration, err error) {
s := os.Getenv("UPDATER_PERIOD")
s := getCleanedEnv("UPDATER_PERIOD")
if s == "" {
return nil, nil //nolint:nilnil
}
@@ -38,11 +41,11 @@ func readUpdaterPeriod() (period *time.Duration, err error) {
return period, nil
}
func readUpdaterDNSAddress() (ip net.IP, err error) {
func readUpdaterDNSAddress() (address string, err error) {
// TODO this is currently using Cloudflare in
// plaintext to not be blocked by DNS over TLS by default.
// If a plaintext address is set in the DNS settings, this one will be used.
// use custom future encrypted DNS written in Go without blocking
// as it's too much trouble to start another parallel unbound instance for now.
return nil, nil
return "", nil
}

View File

@@ -2,7 +2,6 @@ package env
import (
"fmt"
"os"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary"
@@ -18,7 +17,7 @@ func readVersion() (version settings.Version, err error) {
}
func readVersionEnabled() (enabled *bool, err error) {
s := os.Getenv("VERSION_INFORMATION")
s := getCleanedEnv("VERSION_INFORMATION")
if s == "" {
return nil, nil //nolint:nilnil
}

View File

@@ -2,14 +2,13 @@ package env
import (
"fmt"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (r *Reader) readVPN() (vpn settings.VPN, err error) {
vpn.Type = strings.ToLower(os.Getenv("VPN_TYPE"))
vpn.Type = strings.ToLower(getCleanedEnv("VPN_TYPE"))
vpn.Provider, err = r.readProvider(vpn.Type)
if err != nil {

View File

@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"net"
"os"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/port"
@@ -22,7 +21,7 @@ func (r *Reader) readWireguardSelection() (
return selection, err
}
selection.PublicKey = os.Getenv("WIREGUARD_PUBLIC_KEY")
selection.PublicKey = getCleanedEnv("WIREGUARD_PUBLIC_KEY")
return selection, nil
}

View File

@@ -1,9 +1,12 @@
package files
import (
"fmt"
"io"
"os"
"strings"
"github.com/qdm12/gluetun/internal/openvpn/extract"
)
// ReadFromFile reads the content of the file as a string.
@@ -32,3 +35,21 @@ func ReadFromFile(filepath string) (s *string, err error) {
content = strings.TrimSuffix(content, "\n")
return &content, nil
}
func readPEMFile(filepath string) (base64Ptr *string, err error) {
pemData, err := ReadFromFile(filepath)
if err != nil {
return nil, fmt.Errorf("reading file: %w", err)
}
if pemData == nil {
return nil, nil //nolint:nilnil
}
base64Data, err := extract.PEM([]byte(*pemData))
if err != nil {
return nil, fmt.Errorf("extracting base64 encoded data from PEM content: %w", err)
}
return &base64Data, nil
}

View File

@@ -11,18 +11,23 @@ const (
OpenVPNClientKeyPath = "/gluetun/client.key"
// OpenVPNClientCertificatePath is the OpenVPN client certificate filepath.
OpenVPNClientCertificatePath = "/gluetun/client.crt"
openVPNEncryptedKey = "/gluetun/openvpn_encrypted_key"
)
func (r *Reader) readOpenVPN() (settings settings.OpenVPN, err error) {
settings.ClientKey, err = ReadFromFile(OpenVPNClientKeyPath)
settings.Key, err = readPEMFile(OpenVPNClientKeyPath)
if err != nil {
return settings, fmt.Errorf("client key: %w", err)
}
settings.ClientCrt, err = ReadFromFile(OpenVPNClientCertificatePath)
settings.Cert, err = readPEMFile(OpenVPNClientCertificatePath)
if err != nil {
return settings, fmt.Errorf("client certificate: %w", err)
}
settings.EncryptedKey, err = readPEMFile(openVPNEncryptedKey)
if err != nil {
return settings, fmt.Errorf("reading encrypted key file: %w", err)
}
return settings, nil
}

View File

@@ -1,31 +1,48 @@
package secrets
import (
"fmt"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/sources/files"
"github.com/qdm12/gluetun/internal/openvpn/extract"
)
// getCleanedEnv returns an environment variable value with
// surrounding spaces and trailing new line characters removed.
func getCleanedEnv(envKey string) (value string) {
value = os.Getenv(envKey)
value = strings.TrimSpace(value)
value = strings.TrimSuffix(value, "\r\n")
value = strings.TrimSuffix(value, "\n")
return value
}
func readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath string) (
stringPtr *string, err error) {
path := os.Getenv(secretPathEnvKey)
path := getCleanedEnv(secretPathEnvKey)
if path == "" {
path = defaultSecretPath
}
return files.ReadFromFile(path)
}
func readSecretFileAsString(secretPathEnvKey, defaultSecretPath string) (
s string, err error) {
path := os.Getenv(secretPathEnvKey)
if path == "" {
path = defaultSecretPath
}
stringPtr, err := files.ReadFromFile(path)
func readPEMSecretFile(secretPathEnvKey, defaultSecretPath string) (
base64Ptr *string, err error) {
pemData, err := readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath)
if err != nil {
return "", err
} else if stringPtr == nil {
return "", nil
return nil, fmt.Errorf("reading secret file: %w", err)
}
return *stringPtr, nil
if pemData == nil {
return nil, nil //nolint:nilnil
}
base64Data, err := extract.PEM([]byte(*pemData))
if err != nil {
return nil, fmt.Errorf("extracting base64 encoded data from PEM content: %w", err)
}
return &base64Data, nil
}

View File

@@ -8,7 +8,7 @@ import (
func readOpenVPN() (
settings settings.OpenVPN, err error) {
settings.User, err = readSecretFileAsString(
settings.User, err = readSecretFileAsStringPtr(
"OPENVPN_USER_SECRETFILE",
"/run/secrets/openvpn_user",
)
@@ -16,7 +16,7 @@ func readOpenVPN() (
return settings, fmt.Errorf("cannot read user file: %w", err)
}
settings.Password, err = readSecretFileAsString(
settings.Password, err = readSecretFileAsStringPtr(
"OPENVPN_PASSWORD_SECRETFILE",
"/run/secrets/openvpn_password",
)
@@ -24,7 +24,7 @@ func readOpenVPN() (
return settings, fmt.Errorf("cannot read password file: %w", err)
}
settings.ClientKey, err = readSecretFileAsStringPtr(
settings.Key, err = readPEMSecretFile(
"OPENVPN_CLIENTKEY_SECRETFILE",
"/run/secrets/openvpn_clientkey",
)
@@ -32,7 +32,23 @@ func readOpenVPN() (
return settings, fmt.Errorf("cannot read client key file: %w", err)
}
settings.ClientCrt, err = readSecretFileAsStringPtr(
settings.EncryptedKey, err = readPEMSecretFile(
"OPENVPN_ENCRYPTED_KEY_SECRETFILE",
"/run/secrets/openvpn_encrypted_key",
)
if err != nil {
return settings, fmt.Errorf("reading encrypted key file: %w", err)
}
settings.KeyPassphrase, err = readSecretFileAsStringPtr(
"OPENVPN_KEY_PASSPHRASE_SECRETFILE",
"/run/secrets/openvpn_key_passphrase",
)
if err != nil {
return settings, fmt.Errorf("reading key passphrase file: %w", err)
}
settings.Cert, err = readPEMSecretFile(
"OPENVPN_CLIENTCRT_SECRETFILE",
"/run/secrets/openvpn_clientcrt",
)

View File

@@ -1,64 +0,0 @@
package constants
const (
AES128cbc = "aes-128-cbc"
AES256cbc = "aes-256-cbc"
AES128gcm = "aes-128-gcm"
AES256gcm = "aes-256-gcm"
SHA1 = "sha1"
SHA256 = "sha256"
SHA512 = "sha512"
)
const (
Openvpn24 = "2.4"
Openvpn25 = "2.5"
)
//nolint:lll
const (
CyberghostCA = "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"
ExpressvpnCA = "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"
ExpressvpnCert = "MIIDTjCCAregAwIBAgIDKzZvMA0GCSqGSIb3DQEBCwUAMIGFMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UEChMMRm9ydC1GdW5zdG9uMRgwFgYDVQQDEw9Gb3J0LUZ1bnN0b24gQ0ExITAfBgkqhkiG9w0BCQEWEm1lQG15aG9zdC5teWRvbWFpbjAgFw0xNjExMDMwMzA2MThaGA8yMDY2MTEwMzAzMDYxOFowgYoxCzAJBgNVBAYTAlZHMQwwCgYDVQQIDANCVkkxEzARBgNVBAoMCkV4cHJlc3NWUE4xEzARBgNVBAsMCkV4cHJlc3NWUE4xHDAaBgNVBAMME2V4cHJlc3N2cG5fY3VzdG9tZXIxJTAjBgkqhkiG9w0BCQEWFnN1cHBvcnRAZXhwcmVzc3Zwbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrOYt/KOi2uMDGev3pXg8j1SO4J/4EVWDF7vJcKr2jrZlqD/zuAFx2W1YWvwumPO6PKH4PU9621aNdiumaUkv/RplCfznnnxqobhJuTE2oA+rS1bOq+9OhHwF9jgNXNVk+XX4d0toST5uGE6Z3OdmPBur8o5AlCf78PDSAwpFOw5HrgLqOEU4hTweC1/czX2VsvsHv22HRI6JMZgP8gGQii/p9iukqfaJvGdPciL5p1QRBUQIi8P8pNvEp1pVIpxYj7/LOUqb2DxFvgmp2v1IQ0Yu88SWsFk84+xAYHzfkLyS31Sqj5uLRBnJqx3fIlOihQ50GI72fwPMwo+OippvVAgMBAAGjPzA9MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgSwMB0GA1UdDgQWBBSkBM1TCX9kBgFsv2RmOzudMXa9njANBgkqhkiG9w0BAQsFAAOBgQA+2e4b+33zFmA+1ZQ46kWkfiB+fEeDyMwMLeYYyDS2d8mZhNZKdOw7dy4Ifz9Vqzp4aKuQ6j61c6k1UaQQL0tskqWVzslSFvs9NZyUAJLLdGUc5TT2MiLwiXQwd4UvH6bGeePdhvB4+ZbW7VMD7TE8hZhjhAL4F6yAP1EQvg3LDA=="
ExpressvpnRSAKey = "MIIEpAIBAAKCAQEAqzmLfyjotrjAxnr96V4PI9UjuCf+BFVgxe7yXCq9o62Zag/87gBcdltWFr8Lpjzujyh+D1PettWjXYrpmlJL/0aZQn85558aqG4SbkxNqAPq0tWzqvvToR8BfY4DVzVZPl1+HdLaEk+bhhOmdznZjwbq/KOQJQn+/Dw0gMKRTsOR64C6jhFOIU8Hgtf3M19lbL7B79th0SOiTGYD/IBkIov6fYrpKn2ibxnT3Ii+adUEQVECIvD/KTbxKdaVSKcWI+/yzlKm9g8Rb4Jqdr9SENGLvPElrBZPOPsQGB835C8kt9Uqo+bi0QZyasd3yJTooUOdBiO9n8DzMKPjoqab1QIDAQABAoIBAHgsekC0SKi+AOcNOZqJ3pxqophE0V7fQX2KWGXhxZnUZMFxGTc936deMYzjZ1y0lUa6x8cgOUcfqHol3hDmw9oWBckLHGv5Wi9umdb6DOLoZO62+FQATSdfaJ9jheq2Ub2YxsRN0apaXzB6KDKz0oM0+sZ4Udn9Kw6DfuIELRIWwEx4w0v3gKW7YLC4Jkc4AwLkPK03xEA/qImfkCmaMPLhrgVQt+IFfP8bXzL7CCC04rNU/IS8pyjex+iUolnQZlbXntF7Bm4V2mz0827ZVqrgAb/hEQRlsTW3rRkVh+rrdoUE7BCZRTFmRCbLoShjN6XuSf4sAus8ch4UEN12gN0CgYEA4o/tSvij1iPaXLmt4KOEuxqmSGB8MLKhFde8lBbNdrDgxiIH9bH7khKx15XRTX0qLDbs8b2/UJygZG0Aa1kIBqZTXTgeMAuxPRTesALJPdqQ/ROnbJcdFkI7gllrAG8VB0fH4wTRsRd0vWEB6YlCdE107u6LEsLAHxOj9Q5819cCgYEAwXjx9RkQ2qITBx5Ewib8YsltA0n3cmRomPicLlsnKV5DfvyCLpFIsZ1h3f9dUpfxRLwzp8wcoLiq9cCoOGdu1udw/yBTqmhaXWhUK/g77f9Ze2ZB1OEhuyKLYJ1vW/h/Z/a1aPCMxZqsDTPCePsuO8Cez5gqs8LjM3W7EyzRxDMCgYEAvhHrDFt975fSiLoJgo0MPIAGAnBXn+8sLwv3m/FpW+rWF8LTFK/Fku12H5wDpNOdvswxijkauIE+GiJMGMLvdcyx4WHECaC1h73reJRNykOEIZ0Md5BrCZJ1JEzp9Mo8RQhWTEFtvfkkqgApP4g0pSeaMx0StaGG1kt+4IbP+68CgYBrZdQKlquAck/Vt7u7eyDHRcE5/ilaWtqlb/xizz7h++3D5C/v4b5UumTFcyg+3RGVclPKZcfOgDSGzzeSd/hTW46iUTOgeOUQzQVMkzPRXdoyYgVRQtgSpY5xR3O1vjAbahwx8LZ0SvQPMBhYSDbV/Isr+fBacWjl/AipEEwxeQKBgQDdrAEnVlOFoCLw4sUjsPoxkLjhTAgI7CYk5NNxX67Rnj0tp+Y49+sGUhl5sCGfMKkLShiON5P2oxZa+B0aPtQjsdnsFPa1uaZkK4c++SS6AetzYRpVDLmLp7/1CulE0z3O0sBekpwiuaqLJ9ZccC81g4+2j8j6c50rIAct3hxIxw=="
ExpressvpnTLSAuth = "48d9999bd71095b10649c7cb471c1051b1afdece597cea06909b99303a18c67401597b12c04a787e98cdb619ee960d90a0165529dc650f3a5c6fbe77c91c137dcf55d863fcbe314df5f0b45dbe974d9bde33ef5b4803c3985531c6c23ca6906d6cd028efc8585d1b9e71003566bd7891b9cc9212bcba510109922eed87f5c8e66d8e59cbd82575261f02777372b2cd4ca5214c4a6513ff26dd568f574fd40d6cd450fc788160ff68434ce2bf6afb00e710a3198538f14c4d45d84ab42637872e778a6b35a124e700920879f1d003ba93dccdb953cdf32bea03f365760b0ed8002098d4ce20d045b45a83a8432cc737677aed27125592a7148d25c87fdbe0a3f6"
FastestvpnCA = "MIIFQjCCAyqgAwIBAgIIUfxepT+rr8owDQYJKoZIhvcNAQEMBQAwPzELMAkGA1UEBhMCS1kxEzARBgNVBAoTCkZhc3Rlc3RWUE4xGzAZBgNVBAMTEkZhc3Rlc3RWUE4gUm9vdCBDQTAeFw0xNzA5MTYwMDAxNDZaFw0yNzA5MTQwMDAxNDZaMD8xCzAJBgNVBAYTAktZMRMwEQYDVQQKEwpGYXN0ZXN0VlBOMRswGQYDVQQDExJGYXN0ZXN0VlBOIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1Xj+WfPTozFynFqc+c3CVrggIllaXEl5bY5VgFynXkqCTM6lSrfC4pNjGXUbqWe6RnGJbM4/6kUn+lQDjFSQV1rzP2eDS8+r5+X2WXh4AoeNRUWhvSG+HiHD/B2EFK+Nd5BRSdUjpKWAtsCmT2bBt7nT0jN1OdeNrLJeyF8siAqv/oQzKznF9aIe/N01b2M8ZOFTzoXi2fZAckgGWui8NB/lzkVIJqSkAPRL8qiJLuRCPVOX1PFD8vV//R8/QumtfbcYBMo6vCk2HmWdrh5OQHPxb3KJtbtG+Z1j8x6HGEAe17djYepBiRMyCEQvYgfD6tvFylc4IquhqE9yaP60PJod5TxpWnRQ6HIGSeBm+S+rYSMalTZ8+pUqOOA+IQCYpfpx6EKIJL/VsW2C7cXdvudxDhXPI5lR/QidCb9Ohq3WkfxXaYwzrngdg2avmNqId9R4KESuM9GoHW0dszfyBCh5wYfeaffMElfDam3B92NUwyhZwtIiv623WVXY9PPz+EDjSJsIAu2Vi1vdJyA4nD4k9Lwmx/1zTc/UaYVLsiBqL2WdfvFTeoWoV+dNxQXSEPhB8gwi8x4O4lZW0cwVy/6fa8KMY8gZbcbSTr7U5bRERfW8l+jY+mYKQ/M/ccgpxaHiw1/+4LWfbJQ7VhJJrTyN0C36FQzY1URkSXg+53wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmVEL4x6xdCqiqu2OBLs27EA8xGYwDQYJKoZIhvcNAQEMBQADggIBABCpITvO1+R4T9v2+onHiFxU5JjtCZ0zkXqRCMp/Z0UIYbeo1p07pZCPAUjBfGPCkAaR++OiG9sysALdJf8Y6HQKcyuAcWUqQnaIhoZ2JcAP7EKq7uCqsMhcYZD/j3O/3RPtSW5UOx6ItDU+Ua0t9Edho9whNw0VQXmo1JjYoP3FzPjuKoDWTSO1q5eYlZfwcTcs55O2shNkFafPg/6cCm5j6v9nyHrM3sk4LjkrBPUXVx2m/aoz219t8O9Ha9/CdMKXsPO/8gTUzpgnzSgPnGnBmi5xr1nspVN8X4E2f3D+DKqBim3YgslD68NcuFQvJ0/BxZzWVbrr+QXoyzaiCgXuogpIDc2bB6oRXqFnHNz36d4QJmJdWdSaijiS/peQ6EOPgOZ1GuObLWlDCBZLNeQ+N6QaiJxVO4XUj/s22i1IRtwdz84TRHrbWiIpEymsqmb/Ep5r4xV5d6+791axclfOTH7tQrY/SbPtTJI4OEgNekI8YfadQifpelF82MsFFEZuaQn0lj+fvLGtE/zKh3OdLTxRc5TAgBB+0T81+JQosygNr2aFFG0hxar1eyw/gLeG8H+7Ie50pyPvXO4OgB6Key8rSExpilQXlvAT1qX0qS3/K1i/9QkSE9ftIPT6vtwLV2sVQzfyanI4IZgWC6ryhvNLsRn0NFnQclor0+aq"
FastestvpnTLSAuth = "697fe793b32cb5091d30f2326d5d124a9412e93d0a44ef7361395d76528fcbfc82c3859dccea70a93cfa8fae409709bff75f844cf5ff0c237f426d0c20969233db0e706edb6bdf195ec3dc11b3f76bc807a77e74662d9a800c8cd1144ebb67b7f0d3f1281d1baf522bfe03b7c3f963b1364fc0769400e413b61ca7b43ab19fac9e0f77e41efd4bda7fd77b1de2d7d7855cbbe3e620cecceac72c21a825b243e651f44d90e290e09c3ad650de8fca99c858bc7caad584bc69b11e5c9fd9381c69c505ec487a65912c672d83ed0113b5a74ddfbd3ab33b3683cec593557520a72c4d6cce46111f56f3396cc3ce7183edce553c68ea0796cf6c4375fad00aaa2a42"
HideMyAssCA = "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"
HideMyAssCert = "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"
HideMyAssRSAKey = "MIIEpAIBAAKCAQEA5XY3ERJYWs/YIeBoybivNlu+M32rJs+CAZsh7BnnetTxytI4ngsMRoqXETuis8udp2hsqEHsglLR9tlk9C8yCuKhxbkpdrXFWdISmUq5sa7/wqg/zJF1AZm5Jy0oHNyTHfG6XW61I/h9IN5dmcR9YLir8DVDBNllbtt0z+DnvOhYJOqC30ENahWkTmNKl1cT7EBrR5slddiBJleAb08z77pwsD310e6jWTBySsBcPy+xu/Jj2QgVil/3mstZZDI+noFzs3SkTFBkha/lNTP7NODBQ6m39iaJxz6ZR1xE3v7XU0H5WnpZIcQ2+kmu5Krk2y1GYMKL+9oaotXFPz9v+QIDAQABAoIBAQCcMcssOMOiFWc3MC3EWo4SP4MKQ9n0Uj5Z34LI151FdJyehlj54+VYQ1Cv71tCbjED2sZUBoP69mtsT/EzcsjqtfiOwgrifrs2+BOm+0HKHKiGlcbP9peiHkT10PxEITWXpYtJvGlbcfOjIxqt6B28cBjCK09ShrVQL9ylAKBearRRUacszppntMNTMtN/uG48ZR9Wm+xAczImdG6CrG5sLI/++JwM5PDChLvn5JgMGyOfQZdjNe1oSOVLmqFeG5uu/FS4oMon9+HtfjHJr4ZgA1yQ2wQh3GvEjlP8zwHxEpRJYbxpj6ZbjHZJ2HLX/Gcd9/cXiN8+fQ2zPIYQyG9dAoGBAPUUmt2nJNvl7gj0GbZZ3XR9o+hvj7bJ74W2NhMrw6kjrrzHTAUQd1sBQS8szAQCLqf2ou1aw9AMMBdsLAHydXxvbH7IBAla7rKr23iethtSfjhTNSgQLJHVZlNHfp3hzNtCQZ7j0qVjrteNotrdVF7kKPHDXAK00ICy6SPNjvrXAoGBAO+vdnO15jLeZbbi3lQNS4r8oCadyqyX7ouKE6MtKNhiPsNPGqHKiGcKs/+QylVgYvSmm7TgpsCAiEYeLSPT+Yq3y7HtwVpULlpfAhEJXmvn/6hGpOizx1WNGWhw7nHPWPDzf+jqCGzHdhK0aEZR3MZZQ+U+uKfGiJ8vrvgB7eGvAoGAWxxp5nU48rcsIw/8bxpBhgkfYk33M5EnBqKSv9XJS5wEXhIJZOiWNrLktNEGl4boKXE7aNoRacreJhcE1UR6AOS7hPZ+6atwiePyF4mJUeb9HZtxa493wk9/Vv6BR9il++1Jz/QKX4oLef8hyBP4Rb60qgxirG7kBLR+j9zfhskCgYEAzA5y5xIeuIIU0H4XUDG9dcebxSSjbwsuYIgeLdb9pjMGQhsvjjyyoh8/nT20tLkJpkXN3FFCRjNnUWLRhWYrVkkh1wqWiYOPrwqh5MU4KN/sDWSPcznTY+drkTpMFoKzsvdrl2zf3VR3FneXKv742bkXj601Ykko+XWMHcLutisCgYBSq8IrsjzfaTQiTGI9a7WWsvzK92bq7Abnfq7swAXWcJd/bnjTQKLrrvt2bmwNvlWKAb3c69BFMn0X4t4PuN0iJQ39D6aQAEaM7HwWAmjf5TbodbmgbGxdsUB4xcCIQQ1mvTkigXWrCg0YAD2GZSoaslXAAVv6nR5qWEIa0Hx9GA=="
IpvanishCA = "MIIErTCCA5WgAwIBAgIJAMYKzSS8uPKDMA0GCSqGSIb3DQEBDQUAMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCRkwxFDASBgNVBAcTC1dpbnRlciBQYXJrMREwDwYDVQQKEwhJUFZhbmlzaDEVMBMGA1UECxMMSVBWYW5pc2ggVlBOMRQwEgYDVQQDEwtJUFZhbmlzaCBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBpcHZhbmlzaC5jb20wHhcNMTIwMTExMTkzMjIwWhcNMjgxMTAyMTkzMjIwWjCBlTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkZMMRQwEgYDVQQHEwtXaW50ZXIgUGFyazERMA8GA1UEChMISVBWYW5pc2gxFTATBgNVBAsTDElQVmFuaXNoIFZQTjEUMBIGA1UEAxMLSVBWYW5pc2ggQ0ExIzAhBgkqhkiG9w0BCQEWFHN1cHBvcnRAaXB2YW5pc2guY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt9DBWNr/IKOuY3TmDP5x7vYZR0DGxLbXU8TyAzBbjUtFFMbhxlHiXVQrZHmgzih94x7BgXM7tWpmMKYVb+gNaqMdWE680Qm3nOwmhy/dulXDkEHAwD05i/iTx4ZaUdtV2vsKBxRg1vdC4AEiwD7bqV4HOi13xcG971aQ55Mj1KeCdA0aNvpat1LWx2jjWxsfI8s2Lv5Fkoi1HO1+vTnnaEsJZrBgAkLXpItqP29Lik3/OBIvkBIxlKrhiVPixE5qNiD+eSPirsmROvsyIonoJtuY4Dw5K6pcNlKyYiwo1IOFYU3YxffwFJk+bSW4WVBhsdf5dGxq/uOHmuz5gdwxCwIDAQABo4H9MIH6MAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFEv9FCWJHefBcIPX9p8RHCVOGe6uMIHKBgNVHSMEgcIwgb+AFEv9FCWJHefBcIPX9p8RHCVOGe6uoYGbpIGYMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCRkwxFDASBgNVBAcTC1dpbnRlciBQYXJrMREwDwYDVQQKEwhJUFZhbmlzaDEVMBMGA1UECxMMSVBWYW5pc2ggVlBOMRQwEgYDVQQDEwtJUFZhbmlzaCBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBpcHZhbmlzaC5jb22CCQDGCs0kvLjygzANBgkqhkiG9w0BAQ0FAAOCAQEAI2dkh/43ksV2fdYpVGhYaFZPVqCJoToCez0IvOmLeLGzow+EOSrY508oyjYeNP4VJEjApqo0NrMbKl8g/8bpLBcotOCF1c1HZ+y9v7648uumh01SMjsbBeHOuQcLb+7gX6c0pEmxWv8qj5JiW3/1L1bktnjW5Yp5oFkFSMXjOnIoYKHyKLjN2jtwH6XowUNYpg4qVtKU0CXPdOznWcd9/zSfa393HwJPeeVLbKYaFMC4IEbIUmKYtWyoJ9pJ58smU3pWsHZUg9Zc0LZZNjkNlBdQSLmUHAJ33Bd7pJS0JQeiWviC+4UTmzEWRKa7pDGnYRYNu2cUo0/voStphv8EVA=="
IvpnCA = "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"
IvpnTLSAuth = "ac470c93ff9f5602a8aab37dee84a52814d10f20490ad23c47d5d82120c1bf859e93d0696b455d4a1b8d55d40c2685c41ca1d0aef29a3efd27274c4ef09020a3978fe45784b335da6df2d12db97bbb838416515f2a96f04715fd28949c6fe296a925cfada3f8b8928ed7fc963c1563272f5cf46e5e1d9c845d7703ca881497b7e6564a9d1dea9358adffd435295479f47d5298fabf5359613ff5992cb57ff081a04dfb81a26513a6b44a9b5490ad265f8a02384832a59cc3e075ad545461060b7bcab49bac815163cb80983dd51d5b1fd76170ffd904d8291071e96efc3fb777856c717b148d08a510f5687b8a8285dcffe737b98916dd15ef6235dee4266d3b"
MullvadCA = "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"
NordvpnCA = "MIIFCjCCAvKgAwIBAgIBATANBgkqhkiG9w0BAQ0FADA5MQswCQYDVQQGEwJQQTEQMA4GA1UEChMHTm9yZFZQTjEYMBYGA1UEAxMPTm9yZFZQTiBSb290IENBMB4XDTE2MDEwMTAwMDAwMFoXDTM1MTIzMTIzNTk1OVowOTELMAkGA1UEBhMCUEExEDAOBgNVBAoTB05vcmRWUE4xGDAWBgNVBAMTD05vcmRWUE4gUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMkr/BYhyo0F2upsIMXwC6QvkZps3NN2/eQFkfQIS1gql0aejsKsEnmY0Kaon8uZCTXPsRH1gQNgg5D2gixdd1mJUvV3dE3y9FJrXMoDkXdCGBodvKJyU6lcfEVF6/UxHcbBguZK9UtRHS9eJYm3rpL/5huQMCppX7kUeQ8dpCwd3iKITqwd1ZudDqsWaU0vqzC2H55IyaZ/5/TnCk31Q1UP6BksbbuRcwOVskEDsm6YoWDnn/IIzGOYnFJRzQH5jTz3j1QBvRIuQuBuvUkfhx1FEwhwZigrcxXuMP+QgM54kezgziJUaZcOM2zF3lvrwMvXDMfNeIoJABv9ljw969xQ8czQCU5lMVmA37ltv5Ec9U5hZuwk/9QO1Z+d/r6Jx0mlurS8gnCAKJgwa3kyZw6e4FZ8mYL4vpRRhPdvRTWCMJkeB4yBHyhxUmTRgJHm6YR3D6hcFAc9cQcTEl/I60tMdz33G6m0O42sQt/+AR3YCY/RusWVBJB/qNS94EtNtj8iaebCQW1jHAhvGmFILVR9lzD0EzWKHkvyWEjmUVRgCDd6Ne3eFRNS73gdv/C3l5boYySeu4exkEYVxVRn8DhCxs0MnkMHWFK6MyzXCCn+JnWFDYPfDKHvpff/kLDobtPBf+Lbch5wQy9quY27xaj0XwLyjOltpiSTLWae/Q4vAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBDQUAA4ICAQC9fUL2sZPxIN2mD32VeNySTgZlCEdVmlq471o/bDMP4B8gnQesFRtXY2ZCjs50Jm73B2LViL9qlREmI6vE5IC8IsRBJSV4ce1WYxyXro5rmVg/k6a10rlsbK/eg//GHoJxDdXDOokLUSnxt7gk3QKpX6eCdh67p0PuWm/7WUJQxH2SDxsT9vB/iZriTIEe/ILoOQF0Aqp7AgNCcLcLAmbxXQkXYCCSB35Vp06u+eTWjG0/pyS5V14stGtw+fA0DJp5ZJV4eqJ5LqxMlYvEZ/qKTEdoCeaXv2QEmN6dVqjDoTAok0t5u4YRXzEVCfXAC3ocplNdtCA72wjFJcSbfif4BSC8bDACTXtnPC7nD0VndZLp+RiNLeiENhk0oTC+UVdSc+n2nJOzkCK0vYu0Ads4JGIB7g8IB3z2t9ICmsWrgnhdNdcOe15BincrGA8avQ1cWXsfIKEjbrnEuEk9b5jel6NfHtPKoHc9mDpRdNPISeVawDBM1mJChneHt59Nh8Gah74+TM1jBsw4fhJPvoc7Atcg740JErb904mZfkIEmojCVPhBHVQ9LHBAdM8qFI2kRK0IynOmAZhexlP/aT/kpEsEPyaZQlnBn3An1CRz8h0SPApL8PytggYKeQmRhl499+6jLxcZ2IegLfqq41dzIjwHwTMplg+1pKIOVojpWA=="
NordvpnTLSAuth = "e685bdaf659a25a200e2b9e39e51ff030fc72cf1ce07232bd8b2be5e6c670143f51e937e670eee09d4f2ea5a6e4e69965db852c275351b86fc4ca892d78ae002d6f70d029bd79c4d1c26cf14e9588033cf639f8a74809f29f72b9d58f9b8f5fefc7938eade40e9fed6cb92184abb2cc10eb1a296df243b251df0643d53724cdb5a92a1d6cb817804c4a9319b57d53be580815bcfcb2df55018cc83fc43bc7ff82d51f9b88364776ee9d12fc85cc7ea5b9741c4f598c485316db066d52db4540e212e1518a9bd4828219e24b20d88f598a196c9de96012090e333519ae18d35099427e7b372d348d352dc4c85e18cd4b93f8a56ddb2e64eb67adfc9b337157ff4"
PerfectprivacyCA = "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"
PerfectprivacyCert = "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"
PerfectprivacyKey = "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"
PerfectprivacyTLSCrypt = "d10a8e2641f5834f6c5e04a6ee9a798553d338fa2836ef2a91057c1f6174a3a12b36f16d1110b20e42ae94d3bd579213e9c3770be6c74804348dddba876945a5a3ab7660f9436f85f331641f6efc81315f0d12b2766a9f15c10a53cf9ba32dc80f03b5f15a6cc6987bda795dbe83443ec81f3d5e161cd47fab6b1f125b3adeee1eae33370d018594e0ff6b25b815228d27371b32c82a95f4929d3abb5fa36e57bf1f42353542568fbb8233f4645f05820275f79570cb8bbcf8010fc5d20f07d031a8227d45daf7349e34158c91a3d4e5add19cfa02f683f87609f6525fa0594016d11abf2de649f83ad54edd3e74e032e34b1bca685b8499916826d9aee11c13"
PiaCANormal = "MIIFqzCCBJOgAwIBAgIJAKZ7D5Yv87qDMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzM1MThaFw0zNDA0MTIxNzM1MThaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPXDL1L9tX6DGf36liA7UBTy5I869z0UVo3lImfOs/GSiFKPtInlesP65577nd7UNzzXlH/P/CnFPdBWlLp5ze3HRBCc/Avgr5CdMRkEsySL5GHBZsx6w2cayQ2EcRhVTwWpcdldeNO+pPr9rIgPrtXqT4SWViTQRBeGM8CDxAyTopTsobjSiYZCF9Ta1gunl0G/8Vfp+SXfYCC+ZzWvP+L1pFhPRqzQQ8k+wMZIovObK1s+nlwPaLyayzw9a8sUnvWB/5rGPdIYnQWPgoNlLN9HpSmsAcw2z8DXI9pIxbr74cb3/HSfuYGOLkRqrOk6h4RCOfuWoTrZup1uEOn+fw8CAwEAAaOCAVQwggFQMB0GA1UdDgQWBBQv63nQ/pJAt5tLy8VJcbHe22ZOsjCCAR8GA1UdIwSCARYwggESgBQv63nQ/pJAt5tLy8VJcbHe22ZOsqGB7qSB6zCB6DELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRMwEQYDVQQHEwpMb3NBbmdlbGVzMSAwHgYDVQQKExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UECxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAMTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQpExdQcml2YXRlIEludGVybmV0IEFjY2VzczEvMC0GCSqGSIb3DQEJARYgc2VjdXJlQHByaXZhdGVpbnRlcm5ldGFjY2Vzcy5jb22CCQCmew+WL/O6gzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQAna5PgrtxfwTumD4+3/SYvwoD66cB8IcK//h1mCzAduU8KgUXocLx7QgJWo9lnZ8xUryXvWab2usg4fqk7FPi00bED4f4qVQFVfGfPZIH9QQ7/48bPM9RyfzImZWUCenK37pdw4Bvgoys2rHLHbGen7f28knT2j/cbMxd78tQc20TIObGjo8+ISTRclSTRBtyCGohseKYpTS9himFERpUgNtefvYHbn70mIOzfOJFTVqfrptf9jXa9N8Mpy3ayfodz1wiqdteqFXkTYoSDctgKMiZ6GdocK9nMroQipIQtpnwd4yBDWIyC6Bvlkrq5TQUtYDQ8z9v+DMO6iwyIDRiU"
PiaCAStrong = "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"
PiaX509CRLNormal = "MIICWDCCAUAwDQYJKoZIhvcNAQENBQAwgegxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRlaW50ZXJuZXRhY2Nlc3MuY29tFw0xNjA3MDgxOTAwNDZaFw0zNjA3MDMxOTAwNDZaMCYwEQIBARcMMTYwNzA4MTkwMDQ2MBECAQYXDDE2MDcwODE5MDA0NjANBgkqhkiG9w0BAQ0FAAOCAQEAQZo9X97ci8EcPYu/uK2HB152OZbeZCINmYyluLDOdcSvg6B5jI+ffKN3laDvczsG6CxmY3jNyc79XVpEYUnq4rT3FfveW1+Ralf+Vf38HdpwB8EWB4hZlQ205+21CALLvZvR8HcPxC9KEnev1mU46wkTiov0EKc+EdRxkj5yMgv0V2Reze7AP+NQ9ykvDScH4eYCsmufNpIjBLhpLE2cuZZXBLcPhuRzVoU3l7A9lvzG9mjA5YijHJGHNjlWFqyrn1CfYS6koa4TGEPngBoAziWRbDGdhEgJABHrpoaFYaL61zqyMR6jC0K2ps9qyZAN74LEBedEfK7tBOzWMwr58A=="
PiaX509CRLStrong = "MIIDWDCCAUAwDQYJKoZIhvcNAQENBQAwgegxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRlaW50ZXJuZXRhY2Nlc3MuY29tFw0xNjA3MDgxOTAwNDZaFw0zNjA3MDMxOTAwNDZaMCYwEQIBARcMMTYwNzA4MTkwMDQ2MBECAQYXDDE2MDcwODE5MDA0NjANBgkqhkiG9w0BAQ0FAAOCAgEAppFfEpGsasjB1QgJcosGpzbf2kfRhM84o2TlqY1ua+Gi5TMdKydA3LJcNTjlI9a0TYAJfeRX5IkpoglSUuHuJgXhP3nEvX10mjXDpcu/YvM8TdE5JV2+EGqZ80kFtBeOq94WcpiVKFTR4fO+VkOK9zwspFfb1cNs9rHvgJ1QMkRUF8PpLN6AkntHY0+6DnigtSaKqldqjKTDTv2OeH3nPoh80SGrt0oCOmYKfWTJGpggMGKvIdvU3vH9+EuILZKKIskt+1dwdfA5Bkz1GLmiQG7+9ZZBQUjBG9Dos4hfX/rwJ3eU8oUIm4WoTz9rb71SOEuUUjP5NPy9HNx2vx+cVvLsTF4ZDZaUztW9o9JmIURDtbeyqxuHN3prlPWB6aj73IIm2dsDQvs3XXwRIxs8NwLbJ6CyEuvEOVCskdM8rdADWx1J0lRNlOJ0Z8ieLLEmYAA834VN1SboB6wJIAPxQU3rcBhXqO9y8aa2oRMg8NxZ5gr+PnKVMqag1x0IxbIgLxtkXQvxXxQHEMSODzvcOfK/nBRBsqTj30P+R87sU8titOoxNeRnBDRNhdEy/QGAqGh62ShPpQUCJdnKRiRTjnil9hMQHevoSuFKeEMO30FQL7BZyo37GFU+q1WPCplVZgCP9hC8Rn5K2+f6KLFo5bhtowSmu+GY1yZtg+RTtsA="
PrivadoCA = "MIIFKDCCAxCgAwIBAgIJAMtrmqZxIV/OMA0GCSqGSIb3DQEBDQUAMBIxEDAOBgNVBAMMB1ByaXZhZG8wHhcNMjAwMTA4MjEyODQ1WhcNMzUwMTA5MjEyODQ1WjASMRAwDgYDVQQDDAdQcml2YWRvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxPwOgiwNJzZTnKIXwAB0TSu/Lu2qt2U2I8obtQjwhi/7OrfmbmYykSdro70al2XPhnwAGGdCxW6LDnp0UN/IOhD11mgBPo14f5CLkBQjSJ6VN5miPbvK746LsNZl9H8rQGvDuPo4CG9BfPZMiDRGlsMxij/jztzgT1gmuxQ7WHfFRcNzBas1dHa9hV/d3TU6/t47x4SE/ljdcCtJiu7Zn6ODKQoys3mB7Luz2ngqUJWvkqsg+E4+3eJ0M8Hlbn5TPaRJBID7DAdYo6Vs6xGCYr981ThFcmoIQ10js10yANrrfGAzd03b3TnLAgko0uQMHjliMZL6L8sWOPHxyxJI0us88SFh4UgcFyRHKHPKux7w24SxAlZUYoUcTHp9VjG5XvDKYxzgV2RdM4ulBGbQRQ3y3/CyddsyQYMvA55Ets0LfPaBvDIcct70iXijGsdvlX1du3ArGpG7Vaje/RU4nbbGT6HYRdt5YyZfof288ukMOSj20nVcmS+c/4tqsxSerRb1aq5LOi1IemSkTMeC5gCbexk+L1vl7NT/58sxjGmu5bXwnvev/lIItfi2AlITrfUSEv19iDMKkeshwn/+sFJBMWYyluP+yJ56yR+MWoXvLlSWphLDTqq19yx3BZn0P1tgbXoR0g8PTdJFcz8z3RIb7myVLYulV1oGG/3rka0CAwEAAaOBgDB+MB0GA1UdDgQWBBTFtJkZCVDuDAD6k5bJzefjJdO3DTBCBgNVHSMEOzA5gBTFtJkZCVDuDAD6k5bJzefjJdO3DaEWpBQwEjEQMA4GA1UEAwwHUHJpdmFkb4IJAMtrmqZxIV/OMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBDQUAA4ICAQB7MUSXMeBb9wlSv4sUaT1JHEwE26nlBw+TKmezfuPU5pBlY0LYr6qQZY95DHqsRJ7ByUzGUrGo17dNGXlcuNc6TAaQQEDRPo6y+LVh2TWMk15TUMI+MkqryJtCret7xGvDigKYMJgBy58HN3RAVr1B7cL9youwzLgc2Y/NcFKvnQJKeiIYAJ7g0CcnJiQvgZTS7xdwkEBXfsngmUCIG320DLPEL+Ze0HiUrxwWljMRya6i40AeH3Zu2i532xX1wV5+cjA4RJWIKg6ri/Q54iFGtZrA9/nc6y9uoQHkmz8cGyVUmJxFzMrrIICVqUtVRxLhkTMe4UzwRWTBeGgtW4tS0yq1QonAKfOyjgRw/CeY55D2UGvnAFZdTadtYXS4Alu2P9zdwoEk3fzHiVmDjqfJVr5wz9383aABUFrPI3nz6ed/Z6LZflKh1k+DUDEp8NxU4klUULWsSOKoa5zGX51G8cdHxwQLImXvtGuN5eSR8jCTgxFZhdps/xes4KkyfIz9FMYG748M+uOTgKITf4zdJ9BAyiQaOufVQZ8WjhWzWk9YHec9VqPkzpWNGkVjiRI5ewuXwZzZ164tMv2hikBXSuUCnFz37/ZNwGlDi0oBdDszCk2GxccdFHHaCSmpjU5MrdJ+5IhtTKGeTx+US2hTIVHQFIO99DmacxSYvLNcSQ=="
PrivatevpnCA = "MIIErTCCA5WgAwIBAgIJAPp3HmtYGCIOMA0GCSqGSIb3DQEBCwUAMIGVMQswCQYDVQQGEwJTRTELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN0b2NraG9sbTETMBEGA1UEChMKUHJpdmF0ZVZQTjEWMBQGA1UEAxMNUHJpdmF0ZVZQTiBDQTETMBEGA1UEKRMKUHJpdmF0ZVZQTjEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBwcml2YXR2cG4uc2UwHhcNMTcwNTI0MjAxNTM3WhcNMjcwNTIyMjAxNTM3WjCBlTELMAkGA1UEBhMCU0UxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdG9ja2hvbG0xEzARBgNVBAoTClByaXZhdGVWUE4xFjAUBgNVBAMTDVByaXZhdGVWUE4gQ0ExEzARBgNVBCkTClByaXZhdGVWUE4xIzAhBgkqhkiG9w0BCQEWFHN1cHBvcnRAcHJpdmF0dnBuLnNlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwjqTWbKk85WN8nd1TaBgBnBHceQWosp8mMHr4xWMTLagWRcq2Modfy7RPnBo9kyn5j/ZZwL/21gLWJbxidurGyZZdEV9Wb5KQl3DUNxa19kwAbkkEchdES61e99MjmQlWq4vGPXAHjEuDxOZ906AXglCyAvQoXcYW0mNm9yybWllVp1aBrCaZQrNYr7eoFvolqJXdQQ3FFsTBCYa5bHJcKQLBfsiqdJ/BAxhNkQtcmWNSgLy16qoxQpCsxNCxAcYnasuL4rwOP+RazBkJTPXA/2neCJC5rt+sXR9CSfiXdJGwMpYso5m31ZEd7JL2+is0FeAZ6ETrKMnEZMsTpTkdwIDAQABo4H9MIH6MB0GA1UdDgQWBBRCkBlC94zCY6VNncMnK36JxT7bazCBygYDVR0jBIHCMIG/gBRCkBlC94zCY6VNncMnK36JxT7ba6GBm6SBmDCBlTELMAkGA1UEBhMCU0UxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdG9ja2hvbG0xEzARBgNVBAoTClByaXZhdGVWUE4xFjAUBgNVBAMTDVByaXZhdGVWUE4gQ0ExEzARBgNVBCkTClByaXZhdGVWUE4xIzAhBgkqhkiG9w0BCQEWFHN1cHBvcnRAcHJpdmF0dnBuLnNlggkA+ncea1gYIg4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAayugvExKDHar7t1zyYn99Vt1NMf46J8x4Dt9TNjBml5mR9nKvWmreMUuuOhLaO8Da466KGdXeDFNLcBYZd/J2iTawE6/3fmrML9H2sa+k/+E4uU5nQ84ZGOwCinCkMalVjM8EZ0/H2RZvLAVUnvPuUz2JfJhmiRkbeE75fVuqpAm9qdE+/7lg3oICYzxa6BJPxT+Imdjy3Q/FWdsXqX6aallhohPAZlMZgZL4eXECnV8rAfzyjOJggkMDZQt3Flc0Y4iDMfzrEhSOWMkNFBFwjK0F/dnhsX+fPX6GGRpUZgZcCt/hWvypqc05/SnrdKM/vV/jV/yZe0NVzY7S8Ur5g=="
PrivatevpnTLSAuth = "f035a3acaeffb5aedb5bc920bca26ca7ac701da88249008e03563eba6af6d2625ac8ba1e5e0921f76be004c24ae4fd43e42caf0f84269ad44d8d4c14ba45b1386f251c7330d8cc56afd16d516835645651ef7e87a723ac78ae0d49da5b2f2d78ceafcff7a6367d0712628a6547e5fc8fef93c87f7bcd6107c7b1ae68396e944aadae50111d01a5d0c67223d667bdbf1bf434bdef03644ecc5386e102724eef3872f66547eb66dc0fea8286069cb082a41c89083b28fe9f4cec25d48017f26c4fd85b25ddf2ae5448dd2bccf3eef2aacf42ef1e88c3248c689423d0b05a641e9e79dd6b9b5c40f0cc21ffdc891b9eee951477b537261cb56a958a4f490d961ecb"
ProtonvpnCA = "MIIFozCCA4ugAwIBAgIBATANBgkqhkiG9w0BAQ0FADBAMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMUHJvdG9uVlBOIEFHMRowGAYDVQQDExFQcm90b25WUE4gUm9vdCBDQTAeFw0xNzAyMTUxNDM4MDBaFw0yNzAyMTUxNDM4MDBaMEAxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxQcm90b25WUE4gQUcxGjAYBgNVBAMTEVByb3RvblZQTiBSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAt+BsSsZg7+AuqTq7vDbPzfygtl9f8fLJqO4amsyOXlI7pquL5IsEZhpWyJIIvYybqS4s1/T7BbvHPLVEwlrq8A5DBIXcfuXrBbKoYkmpICGc2u1KYVGOZ9A+PH9z4Tr6OXFfXRnsbZToie8t2Xjv/dZDdUDAqeW89I/mXg3k5x08m2nfGCQDm4gCanN1r5MT7ge56z0MkY3FFGCOqRwspIEUzu1ZqGSTkG1eQiOYIrdOF5cc7n2APyvBIcfvp/W3cpTOEmEBJ7/14RnXnHo0fcx61Inx/6ZxzKkW8BMdGGQF3tF6u2M0FjVN0lLH9S0ul1TgoOS56yEJ34hrJSRTqHuar3t/xdCbKFZjyXFZFNsXVvgJu34CNLrHHTGJj9jiUfFnxWQYMo9UNUd4a3PPG1HnbG7LAjlvj5JlJ5aqO5gshdnqb9uIQeR2CdzcCJgklwRGCyDT1pm7eoivWV19YBd81vKulLzgPavu3kRRe83yl29It2hwQ9FMs5w6ZV/X6ciTKo3etkX9nBD9ZzJPsGQsBUy7CzO1jK4W01+u3ItmQS+1s4xtcFxdFY8o/q1zoqBlxpe5MQIWN6QalryiET74gMHE/S5WrPlsq/gehxsdgc6GDUXG4dk8vn6OUMa6wb5wRO3VXGEc67IYm4mDFTYiPvLaFOxtndlUWuCruKcCAwEAAaOBpzCBpDAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBSDkIaYhLVZTwyLNTetNB2qV0gkVDBoBgNVHSMEYTBfgBSDkIaYhLVZTwyLNTetNB2qV0gkVKFEpEIwQDELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFByb3RvblZQTiBBRzEaMBgGA1UEAxMRUHJvdG9uVlBOIFJvb3QgQ0GCAQEwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBDQUAA4ICAQCYr7LpvnfZXBCxVIVc2ea1fjxQ6vkTj0zMhtFs3qfeXpMRf+g1NAh4vv1UIwLsczilMt87SjpJ25pZPyS3O+/VlI9ceZMvtGXdMGfXhTDp//zRoL1cbzSHee9tQlmEm1tKFxB0wfWd/inGRjZxpJCTQh8oc7CTziHZufS+Jkfpc4Rasr31fl7mHhJahF1j/ka/OOWmFbiHBNjzmNWPQInJm+0ygFqij5qs51OEvubR8yh5Mdq4TNuWhFuTxpqoJ87VKaSOx/Aefca44Etwcj4gHb7LThidw/kyzysZiWjyrbfX/31RX7QanKiMk2RDtgZaWi/lMfsl5O+6E2lJ1vo4xv9pW8225B5XeAeXHCfjV/vrrCFqeCprNF6a3Tn/LX6VNy3jbeC+167QagBOaoDA01XPOx7OdhsbGd7cJ5VkgyycZgLnT9zrChgwjx59JQosFEG1DsaAgHfpEl/N3YPJh68N7fwN41Cjzsk39v6iZdfuet/sP7oiP5/gLmA/CIPNhdIYxaojbLjFPkftVjVPn49RqwqzJJPRN8BOyb94yhQ7KO4F3IcLT/y/dsWitY0ZH4lCnAVV/v2YjWAWS3OWyC8BFx/Jmc3WDK/yPwECUcPgHIeXiRjHnJt0Zcm23O2Q3RphpU+1SO3XixsXpOVOYP6rJIXW9bMZA1gTTlpi7A=="
ProtonvpnTLSAuth = "6acef03f62675b4b1bbd03e53b187727423cea742242106cb2916a8a4c8297563d22c7e5cef430b1103c6f66eb1fc5b375a672f158e2e2e936c3faa48b035a6de17beaac23b5f03b10b868d53d03521d8ba115059da777a60cbfd7b2c9c5747278a15b8f6e68a3ef7fd583ec9f398c8bd4735dab40cbd1e3c62a822e97489186c30a0b48c7c38ea32ceb056d3fa5a710e10ccc7a0ddb363b08c3d2777a3395e10c0b6080f56309192ab5aacd4b45f55da61fc77af39bd81a19218a79762c33862df55785075f37d8c71dc8a42097ee43344739a0dd48d03025b0450cf1fb5e8caeb893d9a96d1f15519bb3c4dcb40ee316672ea16c012664f8a9f11255518deb"
PurevpnCA = "MIIE6DCCA9CgAwIBAgIJAMjXFoeo5uSlMA0GCSqGSIb3DQEBCwUAMIGoMQswCQYDVQQGEwJISzEQMA4GA1UECBMHQ2VudHJhbDELMAkGA1UEBxMCSEsxGDAWBgNVBAoTD1NlY3VyZS1TZXJ2ZXJDQTELMAkGA1UECxMCSVQxGDAWBgNVBAMTD1NlY3VyZS1TZXJ2ZXJDQTEYMBYGA1UEKRMPU2VjdXJlLVNlcnZlckNBMR8wHQYJKoZIhvcNAQkBFhBtYWlsQGhvc3QuZG9tYWluMB4XDTE2MDExNTE1MzQwOVoXDTI2MDExMjE1MzQwOVowgagxCzAJBgNVBAYTAkhLMRAwDgYDVQQIEwdDZW50cmFsMQswCQYDVQQHEwJISzEYMBYGA1UEChMPU2VjdXJlLVNlcnZlckNBMQswCQYDVQQLEwJJVDEYMBYGA1UEAxMPU2VjdXJlLVNlcnZlckNBMRgwFgYDVQQpEw9TZWN1cmUtU2VydmVyQ0ExHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDluufhyLlyvXzPUL16kAWAdivl1roQv3QHbuRshyKacf/1Er1JqEbtW3Mx9Fvr/u27qU2W8lQI6DaJhU2BfijPe/KHkib55mvHzIVvoexxya26nk79F2c+d9PnuuMdThWQO3El5a/i2AASnM7T7piIBT2WRZW2i8RbfJaTT7G7LP7OpMKIV1qyBg/cWoO7cIWQW4jmzqrNryIkF0AzStLN1DxvnQZwgXBGv0CwuAkfQuNSLu0PQgPp0PhdukNZFllv5D29IhPr0Z+kwPtrAgPQo+lHlOBHBMUpDT4XChTPeAvMaUSBsqmonAE8UUHEabWrqYN/kWNHCNkYXMkiVmK1AgMBAAGjggERMIIBDTAdBgNVHQ4EFgQU456ijsFrYnzHBShLAPpOUqQ+Z2cwgd0GA1UdIwSB1TCB0oAU456ijsFrYnzHBShLAPpOUqQ+Z2ehga6kgaswgagxCzAJBgNVBAYTAkhLMRAwDgYDVQQIEwdDZW50cmFsMQswCQYDVQQHEwJISzEYMBYGA1UEChMPU2VjdXJlLVNlcnZlckNBMQswCQYDVQQLEwJJVDEYMBYGA1UEAxMPU2VjdXJlLVNlcnZlckNBMRgwFgYDVQQpEw9TZWN1cmUtU2VydmVyQ0ExHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQDI1xaHqObkpTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCvga2HMwOtUxWH/inL2qk24KX2pxLg939JNhqoyNrUpbDHag5xPQYXUmUpKrNJZ0z+o/ZnNUPHydTSXE7Z7E45J0GDN5E7g4pakndKnDLSjp03NgGsCGW+cXnz6UBPM5FStFvGdDeModeSUyoS9fjk+mYROvmiy5EiVDP91sKGcPLR7Ym0M7zl2aaqV7bb98HmMoBOxpeZQinof67nKrCsgz/xjktWFgcmPl4/PQSsmqQD0fTtWxGuRX+FzwvF2OCMCAJgp1RqJNlk2g50/kBIoJVPPCfjDFeDU5zGaWGSQ9+z1L6/z7VXdjUiHL0ouOcHwbiS4ZjTr9nMn6WdAHU2"
PurevpnCert = "MIIEnzCCA4egAwIBAgIBAzANBgkqhkiG9w0BAQsFADCBqDELMAkGA1UEBhMCSEsxEDAOBgNVBAgTB0NlbnRyYWwxCzAJBgNVBAcTAkhLMRgwFgYDVQQKEw9TZWN1cmUtU2VydmVyQ0ExCzAJBgNVBAsTAklUMRgwFgYDVQQDEw9TZWN1cmUtU2VydmVyQ0ExGDAWBgNVBCkTD1NlY3VyZS1TZXJ2ZXJDQTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRvbWFpbjAeFw0xNjAxMTUxNjE1MzhaFw0yNjAxMTIxNjE1MzhaMIGdMQswCQYDVQQGEwJISzEQMA4GA1UECBMHQ2VudHJhbDELMAkGA1UEBxMCSEsxFjAUBgNVBAoTDVNlY3VyZS1DbGllbnQxCzAJBgNVBAsTAklUMRYwFAYDVQQDEw1TZWN1cmUtQ2xpZW50MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRvbWFpbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAxsnyn4v6xxDPnuDaYS0b9M1N8nxgg7OBPBlK+FWRxdTQ8yxt5U5CZGm7riVp7fya2J2iPZIgmHQEv/KbxztsHAVlYSfYYlalrnhEL3bDP2tY+N43AwB1k5BrPq2s1pPLT2XG951drDKG4PUuFHUP1sHzW5oQlfVCmxgIMAP8OYkCAwEAAaOCAV8wggFbMAkGA1UdEwQCMAAwLQYJYIZIAYb4QgENBCAWHkVhc3ktUlNBIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU9MwUnUDbQKKZKjoeieD2OD5NlAEwgd0GA1UdIwSB1TCB0oAU456ijsFrYnzHBShLAPpOUqQ+Z2ehga6kgaswgagxCzAJBgNVBAYTAkhLMRAwDgYDVQQIEwdDZW50cmFsMQswCQYDVQQHEwJISzEYMBYGA1UEChMPU2VjdXJlLVNlcnZlckNBMQswCQYDVQQLEwJJVDEYMBYGA1UEAxMPU2VjdXJlLVNlcnZlckNBMRgwFgYDVQQpEw9TZWN1cmUtU2VydmVyQ0ExHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQDI1xaHqObkpTATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNVHQ8EBAMCB4AwDQYJKoZIhvcNAQELBQADggEBAFyFo2VUX/UFixsdPdK9/Yt6mkCWc+XS1xbapGXXb9U1d+h1iBCIV9odUHgNCXWpz1hR5Uu/OCzaZ0asLE4IFMZlQmJs8sMT0c1tfPPGW45vxbL0lhqnQ8PNcBH7huNK7VFjUh4szXRKmaQPaM4S91R3L4CaNfVeHfAg7mN2m9Zn5Gto1Q1/CFMGKu2hxwGEw5p+X1czBWEvg/O09ckx/ggkkI1NcZsNiYQ+6Pz8DdGGX3+05YwLZu94+O6iIMrzxl/il0eK83g3YPbsOrASARvw6w/8sOnJCK5eOacl21oww875KisnYdWjHB1FiI+VzQ1/gyoDsL5kPTJVuu2CoG8="
PurevpnKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMbJ8p+L+scQz57g2mEtG/TNTfJ8YIOzgTwZSvhVkcXU0PMsbeVOQmRpu64lae38mtidoj2SIJh0BL/ym8c7bBwFZWEn2GJWpa54RC92wz9rWPjeNwMAdZOQaz6trNaTy09lxvedXawyhuD1LhR1D9bB81uaEJX1QpsYCDAD/DmJAgMBAAECgYEAvTHbDupE5U0krUvHzBEIuHblptGlcfNYHoDcD3oxYR3pOGeiuElBexv+mgHVzcFLBrsQfJUlHLPfCWi3xmjRvDQcr7N7U1u7NIzazy/PpRBaKolMRiM1KMYi2DG0i4ZONwFT8bvNHOIrZzCLY54KDrqOn55OzC70WYjWh4t5evkCQQDkkzZUAeskBC9+JP/zLps8jhwfoLBWGw/zbC9ePDmX0N8MTZdcUpg6KUTf1wbkLUyVtIRjS2ao6qu1jWG6K0x3AkEA3qPWyaWQWCynhNDqu2U1cPb2kh5AJip+gqxO3emikAdajsSxeoyEC2AfyBITbeB1tvCUZH17J4i/0+OFTEQp/wJAb/zEOGJ8PzghwK8GC7JA8mk51DEZVAaMSRovFv9wxDXcoh191AjPdmdzzCuAv9iF1i8MUc3GbWoUWK39PIYsPwJAWh63sqfx5b8tj/WBDpnJKBDPfhYAoXJSA1L8GZeY1fQkE+ZKcPCwAmrGcpXeh3t0Krj3WDXyw+32uC5Apr5wwQJAPZwOOReaC4YNfBPZN9BdHvVjOYGGUffpI+X+hWpLRnQFJteAi+eqwyk0Oi0SkJB+a7jcerK2d7q7xhec5WHlng=="
PurevpnTLSAuth = "e30af995f56d07426d9ba1f824730521d4283db4b4d0cdda9c6e8759a3799dcb7939b6a5989160c9660de0f6125cbb1f585b41c074b2fe88ecfcf17eab9a33be1352379cdf74952b588fb161a93e13df9135b2b29038231e02d657a6225705e6868ccb0c384ed11614690a1894bfbeb274cebf1fe9c2329bdd5c8a40fe8820624d2ea7540cd79ab76892db51fc371a3ac5fc9573afecb3fffe3281e61d72e91579d9b03d8cbf7909b3aebf4d90850321ee6b7d0a7846d15c27d8290e031e951e19438a4654663cad975e138f5bc5af89c737ad822f27e19057731f41e1e254cc9c95b7175c622422cde9f1f2cfd3510add94498b4d7133d3729dd214a16b27fb"
SurfsharkCA = "MIIFTTCCAzWgAwIBAgIJAMs9S3fqwv+mMA0GCSqGSIb3DQEBCwUAMD0xCzAJBgNVBAYTAlZHMRIwEAYDVQQKDAlTdXJmc2hhcmsxGjAYBgNVBAMMEVN1cmZzaGFyayBSb290IENBMB4XDTE4MDMxNDA4NTkyM1oXDTI4MDMxMTA4NTkyM1owPTELMAkGA1UEBhMCVkcxEjAQBgNVBAoMCVN1cmZzaGFyazEaMBgGA1UEAwwRU3VyZnNoYXJrIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDEGMNj0aisM63oSkmVJyZPaYX7aPsZtzsxo6m6p5Wta3MGASoryRsBuRaH6VVa0fwbI1nw5ubyxkuaNa4v3zHVwuSq6F1p8S811+1YP1av+jqDcMyojH0ujZSHIcb/i5LtaHNXBQ3qN48Cc7sqBnTIIFpmb5HthQ/4pW+a82b1guM5dZHsh7q+LKQDIGmvtMtO1+NEnmj81BApFayiaD1ggvwDI4x7o/Y3ksfWSCHnqXGyqzSFLh8QuQrTmWUm84YHGFxoI1/8AKdIyVoB6BjcaMKtKs/pbctk6vkzmYf0XmGovDKPQF6MwUekchLjB5gSBNnptSQ9kNgnTLqi0OpSwI6ixX52Ksva6UM8P01ZIhWZ6ua/T/tArgODy5JZMW+pQ1A6L0b7egIeghpwKnPRG+5CzgO0J5UE6gv000mqbmC3CbiS8xi2xuNgruAyY2hUOoV9/BuBev8ttE5ZCsJH3YlG6NtbZ9hPc61GiBSx8NJnX5QHyCnfic/X87eST/amZsZCAOJ5v4EPSaKrItt+HrEFWZQIq4fJmHJNNbYvWzCE08AL+5/6Z+lxb/Bm3dapx2zdit3x2e+miGHekuiE8lQWD0rXD4+T+nDRi3X+kyt8Ex/8qRiUfrisrSHFzVMRungIMGdO9O/zCINFrb7wahm4PqU2f12Z9TRCOTXciQIDAQABo1AwTjAdBgNVHQ4EFgQUYRpbQwyDahLMN3F2ony3+UqOYOgwHwYDVR0jBBgwFoAUYRpbQwyDahLMN3F2ony3+UqOYOgwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAn9zV7F/XVnFNZhHFrt0ZS1Yqz+qM9CojLmiyblMFh0p7t+Hh+VKVgMwrz0LwDH4UsOosXA28eJPmech6/bjfymkoXISy/NUSTFpUChGO9RabGGxJsT4dugOw9MPaIVZffny4qYOc/rXDXDSfF2b+303lLPI43y9qoe0oyZ1vtk/UKG75FkWfFUogGNbpOkuz+et5Y0aIEiyg0yh6/l5Q5h8+yom0HZnREHhqieGbkaGKLkyu7zQ4D4tRK/mBhd8nv+09GtPEG+D5LPbabFVxKjBMP4Vp24WuSUOqcGSsURHevawPVBfgmsxf1UCjelaIwngdh6WfNCRXa5QQPQTKubQvkvXONCDdhmdXQccnRX1nJWhPYi0onffvjsWUfztRypsKzX4dvM9k7xnIcGSGEnCC4RCgt1UiZIj7frcCMssbA6vJ9naM0s7JF7N3VKeHJtqe1OCRHMYnWUZt9vrqX6IoIHlZCoLlv39wFW9QNxelcAOCVbD+19MZ0ZXt7LitjIqe7yF5WxDQN4xru087FzQ4Hfj7eH1SNLLyKZkA1eecjmRoi/OoqAt7afSnwtQLtMUc2bQDg6rHt5C0e4dCLqP/9PGZTSJiwmtRHJ/N5qYWIh9ju83APvLm/AGBTR2pXmj9G3KdVOkpIC7L35dI623cSEC3Q3UZutsEm/UplsM="
SurfsharkTLSAuth = "b02cb1d7c6fee5d4f89b8de72b51a8d0c7b282631d6fc19be1df6ebae9e2779e6d9f097058a31c97f57f0c35526a44ae09a01d1284b50b954d9246725a1ead1ff224a102ed9ab3da0152a15525643b2eee226c37041dc55539d475183b889a10e18bb94f079a4a49888da566b99783460ece01daaf93548beea6c827d9674897e7279ff1a19cb092659e8c1860fbad0db4ad0ad5732f1af4655dbd66214e552f04ed8fd0104e1d4bf99c249ac229ce169d9ba22068c6c0ab742424760911d4636aafb4b85f0c952a9ce4275bc821391aa65fcd0d2394f006e3fba0fd34c4bc4ab260f4b45dec3285875589c97d3087c9134d3a3aa2f904512e85aa2dc2202498"
TorguardCA = "MIIDMTCCAhmgAwIBAgIJAKnGGJK6qLqSMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMMCVRHLVZQTi1DQTAgFw0xOTA1MjExNDIzMTFaGA8yMDU5MDUxMTE0MjMxMVowFDESMBAGA1UEAwwJVEctVlBOLUNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlv0UgPD3xVAvhhP6q1HCmeAWbH+9HPkyQ2P6qM5oHY5dntjmq8YT48FZGHWv7+s9O47v6Bv7rEc4UwQx15cc2LByivX2JwmE8JACvNfwEnZXYAPq9WU3ZgRrAGvA09ItuLqK2fQ4A7h8bFhmyxCbSzP1sSIT/zJY6ebuh5rDQSMJRMaoI0t1zorEZ7PlEmh+o0w5GPs0D0vY50UcnEzB4GOdWC9pJREwEqppWYLN7RRdG8JyIqmA59mhARCnQFUo38HWic4trxFe71jtD7YInNV7ShQtg0S0sXo36Rqfz72Jo08qqI70dNs5DN1aGNkQ/tRK9DhL5DLmTkaCw7mEFQIDAQABo4GDMIGAMB0GA1UdDgQWBBR7DcymXBp6u/jAaZOPUjUhEyhXfjBEBgNVHSMEPTA7gBR7DcymXBp6u/jAaZOPUjUhEyhXfqEYpBYwFDESMBAGA1UEAwwJVEctVlBOLUNBggkAqcYYkrqoupIwDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAE79ngbdSlP7IBbfnJ+2Ju7vqt9/GyhcsYtjibp6gsMUxKlD8HuvlSGj5kNO5wiwN7XXqsjYtJfdhmzzVbXksi8Fnbnfa8GhFl4IAjLJ5cxaWOxjr6wx2AhIs+BVVARjaU7iTK91RXJnl6u7UDHTkQylBTl7wgpMeG6GjhaHfcOL1t7D2w8x23cTO+p+n53P3cBq+9TiAUORdzXJvbCxlPMDSDArsgBjC57W7dtdnZo7gTfQG77JTDFBeSwPwLF7PjBB4S6rzU/4fcYwy83XKP6zDn9tgUJDnpFb/7jJ/PbNkK4BWYJp3XytOtt66v9SEKw+v/fJ+VkjU16vE/9Q3h4="
TorguardTLSAuth = "770e8de5fc56e0248cc7b5aab56be80d0e19cbf003c1b3ed68efbaf08613c3a1a019dac6a4b84f13a6198f73229ffc21fa512394e288f82aa2cf0180f01fb3eb1a71e00a077a20f6d7a83633f5b4f47f27e30617eaf8485dd8c722a8606d56b3c183f65da5d3c9001a8cbdb96c793d936251098b24fe52a6dd2472e98cfccbc466e63520d63ade7a0eacc36208c3142a1068236a52142fbb7b3ed83d785e12a28261bccfb3bcb62a8d2f6d18f5df5f3652e59c5627d8d9c8f7877c4d7b08e19a5c363556ba68d392be78b75152dd55ba0f74d45089e84f77f4492d886524ea6c82b9f4dd83d46528d4f5c3b51cfeaf2838d938bd0597c426b0e440434f2c451f"
VPNUnlimitedCA = "MIIEjjCCA/egAwIBAgIJAKsVbHBdakXuMA0GCSqGSIb3DQEBBQUAMIHgMQswCQYDVQQGEwJVUzELMAkGA1UECBMCTlkxETAPBgNVBAcTCE5ldyBZb3JrMR8wHQYDVQQKExZTaW1wbGV4IFNvbHV0aW9ucyBJbmMuMRYwFAYDVQQLEw1WcG4gVW5saW1pdGVkMSMwIQYDVQQDExpzZXJ2ZXIudnBudW5saW1pdGVkYXBwLmNvbTEjMCEGA1UEKRMac2VydmVyLnZwbnVubGltaXRlZGFwcC5jb20xLjAsBgkqhkiG9w0BCQEWH3N1cHBvcnRAc2ltcGxleHNvbHV0aW9uc2luYy5jb20wHhcNMTMxMjE2MTM1OTQ0WhcNMjMxMjE0MTM1OTQ0WjCB4DELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk5ZMREwDwYDVQQHEwhOZXcgWW9yazEfMB0GA1UEChMWU2ltcGxleCBTb2x1dGlvbnMgSW5jLjEWMBQGA1UECxMNVnBuIFVubGltaXRlZDEjMCEGA1UEAxMac2VydmVyLnZwbnVubGltaXRlZGFwcC5jb20xIzAhBgNVBCkTGnNlcnZlci52cG51bmxpbWl0ZWRhcHAuY29tMS4wLAYJKoZIhvcNAQkBFh9zdXBwb3J0QHNpbXBsZXhzb2x1dGlvbnNpbmMuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDADUzS8QWGvdhLFKsEzAiq5+b0ukKjBza0k6/dmCeYTvCVqGKg/h1IAtQdVVLAkmEp0zvGH7PuOhXm7zZrCouBr/RiG4tEcoRHwc6AJmowkYERlY7+xGx3OuNrD00QceNTsan0bn78jwt0zhFNmHdoTtFjgK3oqmQYSAtbEVWYgwIDAQABo4IBTDCCAUgwHQYDVR0OBBYEFKClmYP+tMNgWagUJCCHjtaui2k+MIIBFwYDVR0jBIIBDjCCAQqAFKClmYP+tMNgWagUJCCHjtaui2k+oYHmpIHjMIHgMQswCQYDVQQGEwJVUzELMAkGA1UECBMCTlkxETAPBgNVBAcTCE5ldyBZb3JrMR8wHQYDVQQKExZTaW1wbGV4IFNvbHV0aW9ucyBJbmMuMRYwFAYDVQQLEw1WcG4gVW5saW1pdGVkMSMwIQYDVQQDExpzZXJ2ZXIudnBudW5saW1pdGVkYXBwLmNvbTEjMCEGA1UEKRMac2VydmVyLnZwbnVubGltaXRlZGFwcC5jb20xLjAsBgkqhkiG9w0BCQEWH3N1cHBvcnRAc2ltcGxleHNvbHV0aW9uc2luYy5jb22CCQCrFWxwXWpF7jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBALkWhfw7SSV7it+ZYZmT+cQbExjlYgQ40zae2J2CqIYACRcfsDHvh7Q+fiwSocevv2NE0dWi6WB2H6SiudYjvDvubAX/QbzfBxtbxCCoAIlfPCm8xOnWFN7TUJAzWwHJkKgEnu29GZHu2x8J+7VeDbKH5RTMHHe8FkSxh6Zz/BMN"
VyprvpnCA = "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"
WevpnCA = "MIIDQjCCAiqgAwIBAgIUPppqnRZfvGGrT4GjXFE4Q29QzgowDQYJKoZIhvcNAQELBQAwEzERMA8GA1UEAwwIQ2hhbmdlTWUwHhcNMTkxMTA1MjMzMzIzWhcNMjkxMTAyMjMzMzIzWjATMREwDwYDVQQDDAhDaGFuZ2VNZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL5DFBJlTqhXukJFWlI8TNW9+HEQCZXhyVFvQhJFF2xIGVNx51XzqxiRANjVJZJrA68kV8az0v2Dxj0SFnRWDR6pOjjdp2CyHFcgHyfv+4MrsreAtkue86bB/1ECPWaoIwtaLnwI6SEmFZl98RlI9v4M/8IE4chOnMrM/F22+2OXI//TduvTcbyOMUiiouIP8UG1FB3J5FyuaW6qPZz2G0efDoaOI+E9LSxE87OoFrII7UqdHlWxRb3nUuPU1Ee4rN/d4tFyP4AvPKfsGhVOwyGG21IdRnbXIuDi0xytkCGOZ4j2bq5zqudnp4Izt6yJgdzZpQQWK3kSHB3qTT/Yzl8CAwEAAaOBjTCBijAdBgNVHQ4EFgQUXYkoo4WbkkvbgLVdGob9RScRf3AwTgYDVR0jBEcwRYAUXYkoo4WbkkvbgLVdGob9RScRf3ChF6QVMBMxETAPBgNVBAMMCENoYW5nZU1lghQ+mmqdFl+8YatPgaNcUThDb1DOCjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAOr1XmyWBRYfTQPNvZZ+DjCfiRYzLOi2AGefZt/jETqPDF8deVbyL1fLhXZzuX+5Etlsil3PflJjpzc/FSeZRuYRaShtwF3j6I08Eww9rBkaCnsukMUcLtMOvhdAU8dUakcRA2wkQ7Z+TWdMBv5+/6MnX10An1fIz7bAy3btMEOPTEFLo8Bst1SxJtUMaqhUteSOJ1VorpK3CWfOFaXxbJAb4E0+3zt6Vsc8mY5tt6wAi8IqiN4WD79ZdvKxENK4FMkR1kNpBY97mvdf82rzpwiBuJgN5ywmH78Ghj+9T8nI6/UIqJ1y22IRYGv6dMif8fHo5WWhCv3qmCqqY8vwuxw=="
WevpnCert = "MIIDTDCCAjSgAwIBAgIRAKxt8SMIXezjmHm2KDCAQdIwDQYJKoZIhvcNAQELBQAwEzERMA8GA1UEAwwIQ2hhbmdlTWUwHhcNMTkxMTA1MjMzMzI0WhcNMjkxMTAyMjMzMzI0WjAOMQwwCgYDVQQDDAN0Y3AwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvEwY2erLhMm3Mpsnybm3G6zvGyeblUAaehQVEUs+KM2/5np0Ovx0y8Iz9pIC9ITaWM0B3dM6uBsNEtylZIe4Dd9aFujunSeCFsLRf8i9AbrUombpQ6P4jzYFBxwcEw//UShwa4HZI6JuSYikdpx/dyXdBH2skahwDVc8VUFdBLLSglfKGbuzP9GsdSwQCeBRWgA3dvIzIkQkBwfnt9WQKUfRAe8e5NybaAn8Yuu9sjLkQe6eyV7toxkZTcEXdABG2vtdTEzlAsQilZzIxg3jcdeEgMgRKngng+YNP0rR5nofZ1iDlp+vBj0nuqTTJLHMrRWPIc7bdYFD/f2J49WORAgMBAAGjgZ8wgZwwCQYDVR0TBAIwADAdBgNVHQ4EFgQUmSAFmCo1FAKVq8RQF7jMxMxcMtUwTgYDVR0jBEcwRYAUXYkoo4WbkkvbgLVdGob9RScRf3ChF6QVMBMxETAPBgNVBAMMCENoYW5nZU1lghQ+mmqdFl+8YatPgaNcUThDb1DOCjATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNVHQ8EBAMCB4AwDQYJKoZIhvcNAQELBQADggEBADPqdEgL+0kou8P974QEaNg1XOAXpwP0NNqbkZ/Oj9+Lp96YAhAHOAJig+RWbBktK8zu8oUUGR1qLXAWCmirlXErVuBRnadTEh3A7SOuY02BcsYAtpQ2EU9j5K/LV7nTfagkVdWy7x/av361UD4t9fv1j4YYTh4XLRp7KVXs6AGZ7T1hqPYFMUIoPpFhPzFxH4euJjfazr4SkTR6k6Vhw3pyFd6HP65vcqpzHGxFytSa8HtltBk2DpzIf8yV9TEy+gOXFaaGss0YKQ5OU1ieqZRuLVEGiu17lByYiQGyemIETJbdkyiSg93dDJRxjaTk7c8CEdpipt07ndSIPldMtXA="
WevpnTLSCrypt = "7be66c0df0b8855e076d9e37b19f9ff3c1735ed537dee6dc786e51bdb8502f878077eeba0420a25e2b04814d22bbdcc0191a4fc396fdba1af6eb090a9d8664f18e70012ee98a2e32c28620a771d13cf3a619c417480c2c312562fffaebfd7ba73f57a28edde6c287365e6ce28291a29728da211cb53e01aa46b92f5f276c61fb46bd810b41219022c8f3d9e699fe9ade6bfcbb937fbbf6f49d741740e71c7c008a9a13c2432608038c6310b4f33588d8d234b3dffcf0823395267d73140d0e9a40e323ca92866c37073bfb072ab9de518bb9f2c65df7e219c2f114afbcf7c6e3c401cb08c3ed2901725b0601d2b5de89245719dd32506d52f149d14156215c1e"
WindscribeCA = "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"
WindscribeTLSAuth = "5801926a57ac2ce27e3dfd1dd6ef82042d82bd4f3f0021296f57734f6f1ea714a6623845541c4b0c3dea0a050fe6746cb66dfab14cda27e5ae09d7c155aa554f399fa4a863f0e8c1af787e5c602a801d3a2ec41e395a978d56729457fe6102d7d9e9119aa83643210b33c678f9d4109e3154ac9c759e490cb309b319cf708cae83ddadc3060a7a26564d1a24411cd552fe6620ea16b755697a4fc5e6e9d0cfc0c5c4a1874685429046a424c026db672e4c2c492898052ba59128d46200b40f880027a8b6610a4d559bdc9346d33a0a6b08e75c7fd43192b162bfd0aef0c716b31584827693f676f9a5047123466f0654eade34972586b31c6ce7e395f4b478cb"
)

View File

@@ -0,0 +1,7 @@
package openvpn
const (
SHA1 = "sha1"
SHA256 = "sha256"
SHA512 = "sha512"
)

View File

@@ -0,0 +1,8 @@
package openvpn
const (
AES128cbc = "aes-128-cbc"
AES256cbc = "aes-256-cbc"
AES128gcm = "aes-128-gcm"
AES256gcm = "aes-256-gcm"
)

View File

@@ -0,0 +1,9 @@
package openvpn
const (
// AuthConf is the file path to the OpenVPN auth file.
AuthConf = "/etc/openvpn/auth.conf"
// AskPassPath is the file path to the decryption passphrase for
// and encrypted private key, which is pointed by `askpass`.
AskPassPath = "/etc/openvpn/askpass" //nolint:gosec
)

View File

@@ -0,0 +1,6 @@
package openvpn
const (
Openvpn24 = "2.4"
Openvpn25 = "2.5"
)

View File

@@ -1,8 +1,6 @@
package constants
const (
// OpenVPNAuthConf is the file path to the OpenVPN auth file.
OpenVPNAuthConf = "/etc/openvpn/auth.conf"
// ServersData is the server information filepath.
ServersData = "/gluetun/servers.json"
)

View File

@@ -1,7 +0,0 @@
package constants
const (
PIAEncryptionPresetNone = "none"
PIAEncryptionPresetNormal = "normal"
PIAEncryptionPresetStrong = "strong"
)

View File

@@ -5,6 +5,7 @@ const (
// VPN configurations.
Custom = "custom"
Cyberghost = "cyberghost"
Example = "example"
Expressvpn = "expressvpn"
Fastestvpn = "fastestvpn"
HideMyAss = "hidemyass"
@@ -18,17 +19,19 @@ const (
Privatevpn = "privatevpn"
Protonvpn = "protonvpn"
Purevpn = "purevpn"
SlickVPN = "slickvpn"
Surfshark = "surfshark"
Torguard = "torguard"
VPNSecure = "vpnsecure"
VPNUnlimited = "vpn unlimited"
Vyprvpn = "vyprvpn"
Wevpn = "wevpn"
Windscribe = "windscribe"
)
// All returns all the providers except the custom provider.
func All() []string {
return []string{
Custom,
Cyberghost,
Expressvpn,
Fastestvpn,
@@ -43,11 +46,21 @@ func All() []string {
Privatevpn,
Protonvpn,
Purevpn,
SlickVPN,
Surfshark,
Torguard,
VPNSecure,
VPNUnlimited,
Vyprvpn,
Wevpn,
Windscribe,
}
}
func AllWithCustom() []string {
allProviders := All()
allProvidersWithCustom := make([]string, len(allProviders)+1)
copy(allProvidersWithCustom, allProviders)
allProvidersWithCustom[len(allProvidersWithCustom)-1] = Custom
return allProvidersWithCustom
}

View File

@@ -0,0 +1,23 @@
package providers
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_All(t *testing.T) {
t.Parallel()
all := All()
assert.NotContains(t, all, Custom)
assert.NotEmpty(t, all)
}
func Test_AllWithCustom(t *testing.T) {
t.Parallel()
all := AllWithCustom()
assert.Contains(t, all, Custom)
assert.Len(t, all, len(All())+1)
}

View File

@@ -0,0 +1,15 @@
package dns
import (
"context"
"github.com/qdm12/dns/pkg/unbound"
)
type Configurator interface {
SetupFiles(ctx context.Context) error
MakeUnboundConf(settings unbound.Settings) (err error)
Start(ctx context.Context, verbosityDetailsLevel uint8) (
stdoutLines, stderrLines chan string, waitError chan error, err error)
Version(ctx context.Context) (version string, err error)
}

View File

@@ -1,4 +1,3 @@
// Package dns defines interfaces to interact with DNS and DNS over TLS.
package dns
import (
@@ -7,7 +6,6 @@ import (
"time"
"github.com/qdm12/dns/pkg/blacklist"
"github.com/qdm12/dns/pkg/unbound"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/dns/state"
@@ -15,20 +13,10 @@ import (
"github.com/qdm12/gluetun/internal/models"
)
var _ Looper = (*Loop)(nil)
type Looper interface {
Runner
RestartTickerRunner
loopstate.Applier
loopstate.Getter
SettingsGetSetter
}
type Loop struct {
statusManager loopstate.Manager
state state.Manager
conf unbound.Configurator
statusManager *loopstate.State
state *state.State
conf Configurator
resolvConf string
blockBuilder blacklist.Builder
client *http.Client
@@ -46,7 +34,7 @@ type Loop struct {
const defaultBackoffTime = 10 * time.Second
func NewLoop(conf unbound.Configurator, settings settings.DNS,
func NewLoop(conf Configurator, settings settings.DNS,
client *http.Client, logger Logger) *Loop {
start := make(chan struct{})
running := make(chan models.LoopStatus)

View File

@@ -7,10 +7,6 @@ import (
"github.com/qdm12/gluetun/internal/constants"
)
type Runner interface {
Run(ctx context.Context, done chan<- struct{})
}
func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
defer close(done)
@@ -86,7 +82,6 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
l.logger.Info("starting")
stayHere = false
case err := <-waitError: // unexpected error
close(waitError)
closeStreams()
unboundCancel()

View File

@@ -6,22 +6,8 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings"
)
type SettingsGetSetter interface {
SettingsGetter
SettingsSetter
}
type SettingsGetter interface {
GetSettings() (settings settings.DNS)
}
func (l *Loop) GetSettings() (settings settings.DNS) { return l.state.GetSettings() }
type SettingsSetter interface {
SetSettings(ctx context.Context, settings settings.DNS) (
outcome string)
}
func (l *Loop) SetSettings(ctx context.Context, settings settings.DNS) (
outcome string) {
return l.state.SetSettings(ctx, settings)

View File

@@ -8,12 +8,6 @@ import (
"github.com/qdm12/gluetun/internal/constants"
)
type SettingsGetSetter interface {
GetSettings() (settings settings.DNS)
SetSettings(ctx context.Context,
settings settings.DNS) (outcome string)
}
func (s *State) GetSettings() (settings settings.DNS) {
s.settingsMu.RLock()
defer s.settingsMu.RUnlock()

View File

@@ -1,19 +1,14 @@
package state
import (
"context"
"sync"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/loopstate"
"github.com/qdm12/gluetun/internal/models"
)
var _ Manager = (*State)(nil)
type Manager interface {
SettingsGetSetter
}
func New(statusApplier loopstate.Applier,
func New(statusApplier StatusApplier,
settings settings.DNS,
updateTicker chan<- struct{}) *State {
return &State{
@@ -24,10 +19,15 @@ func New(statusApplier loopstate.Applier,
}
type State struct {
statusApplier loopstate.Applier
statusApplier StatusApplier
settings settings.DNS
settingsMu sync.RWMutex
updateTicker chan<- struct{}
}
type StatusApplier interface {
ApplyStatus(ctx context.Context, status models.LoopStatus) (
outcome string, err error)
}

View File

@@ -7,10 +7,6 @@ import (
"github.com/qdm12/gluetun/internal/constants"
)
type RestartTickerRunner interface {
RunRestartTicker(ctx context.Context, done chan<- struct{})
}
func (l *Loop) RunRestartTicker(ctx context.Context, done chan<- struct{}) {
defer close(done)
// Timer that acts as a ticker

View File

@@ -5,10 +5,6 @@ import (
"fmt"
)
type Enabler interface {
SetEnabled(ctx context.Context, enabled bool) (err error)
}
func (c *Config) SetEnabled(ctx context.Context, enabled bool) (err error) {
c.stateMutex.Lock()
defer c.stateMutex.Unlock()

View File

@@ -1,5 +1,3 @@
// Package firewall defines a configurator used to change the state
// of the firewall as well as do some light routing changes.
package firewall
import (
@@ -12,16 +10,6 @@ import (
"github.com/qdm12/golibs/command"
)
var _ Configurator = (*Config)(nil)
// Configurator allows to change firewall rules and modify network routes.
type Configurator interface {
Enabler
VPNConnectionSetter
PortAllower
OutboundSubnetsSetter
}
type Config struct { //nolint:maligned
runner command.Runner
logger Logger

View File

@@ -8,10 +8,6 @@ import (
"github.com/qdm12/gluetun/internal/subnet"
)
type OutboundSubnetsSetter interface {
SetOutboundSubnets(ctx context.Context, subnets []net.IPNet) (err error)
}
func (c *Config) SetOutboundSubnets(ctx context.Context, subnets []net.IPNet) (err error) {
c.stateMutex.Lock()
defer c.stateMutex.Unlock()

View File

@@ -6,11 +6,6 @@ import (
"strconv"
)
type PortAllower interface {
SetAllowedPort(ctx context.Context, port uint16, intf string) (err error)
RemoveAllowedPort(ctx context.Context, port uint16) (err error)
}
func (c *Config) SetAllowedPort(ctx context.Context, port uint16, intf string) (err error) {
c.stateMutex.Lock()
defer c.stateMutex.Unlock()

View File

@@ -7,11 +7,6 @@ import (
"github.com/qdm12/gluetun/internal/models"
)
type VPNConnectionSetter interface {
SetVPNConnection(ctx context.Context,
connection models.Connection, vpnIntf string) error
}
func (c *Config) SetVPNConnection(ctx context.Context,
connection models.Connection, vpnIntf string) (err error) {
c.stateMutex.Lock()

View File

@@ -12,12 +12,6 @@ var (
ErrHTTPStatusNotOK = errors.New("HTTP response status is not OK")
)
var _ Checker = (*Client)(nil)
type Checker interface {
Check(ctx context.Context, url string) error
}
type Client struct {
httpClient *http.Client
}

View File

@@ -1,4 +1,3 @@
// Package healthcheck defines the client and server side of the built in healthcheck.
package healthcheck
import (

View File

@@ -5,11 +5,10 @@ import (
"time"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/vpn"
)
type vpnHealth struct {
looper vpn.Looper
loop StatusApplier
healthyWait time.Duration
healthyTimer *time.Timer
}
@@ -17,8 +16,8 @@ type vpnHealth struct {
func (s *Server) onUnhealthyVPN(ctx context.Context) {
s.logger.Info("program has been unhealthy for " +
s.vpn.healthyWait.String() + ": restarting VPN")
_, _ = s.vpn.looper.ApplyStatus(ctx, constants.Stopped)
_, _ = s.vpn.looper.ApplyStatus(ctx, constants.Running)
_, _ = s.vpn.loop.ApplyStatus(ctx, constants.Stopped)
_, _ = s.vpn.loop.ApplyStatus(ctx, constants.Running)
s.vpn.healthyWait += *s.config.VPN.Addition
s.vpn.healthyTimer = time.NewTimer(s.vpn.healthyWait)
}

View File

@@ -14,8 +14,10 @@ func (s *Server) Run(ctx context.Context, done chan<- struct{}) {
go s.runHealthcheckLoop(ctx, loopDone)
server := http.Server{
Addr: s.config.ServerAddress,
Handler: s.handler,
Addr: s.config.ServerAddress,
Handler: s.handler,
ReadHeaderTimeout: s.config.ReadHeaderTimeout,
ReadTimeout: s.config.ReadTimeout,
}
serverDone := make(chan struct{})
go func() {

View File

@@ -5,15 +5,9 @@ import (
"net"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/vpn"
"github.com/qdm12/gluetun/internal/models"
)
var _ ServerRunner = (*Server)(nil)
type ServerRunner interface {
Run(ctx context.Context, done chan<- struct{})
}
type Server struct {
logger Logger
handler *handler
@@ -23,15 +17,20 @@ type Server struct {
}
func NewServer(config settings.Health,
logger Logger, vpnLooper vpn.Looper) *Server {
logger Logger, vpnLoop StatusApplier) *Server {
return &Server{
logger: logger,
handler: newHandler(),
dialer: &net.Dialer{},
config: config,
vpn: vpnHealth{
looper: vpnLooper,
loop: vpnLoop,
healthyWait: *config.VPN.Initial,
},
}
}
type StatusApplier interface {
ApplyStatus(ctx context.Context, status models.LoopStatus) (
outcome string, err error)
}

View File

@@ -1,21 +1,12 @@
package httpproxy
type Logger interface {
infoErrorer
Debug(s string)
infoer
Warn(s string)
errorer
}
type infoErrorer interface {
infoer
errorer
}
type infoer interface {
Info(s string)
}
type errorer interface {
Error(s string)
}

View File

@@ -1,4 +1,3 @@
// Package httpproxy defines an interface to run an HTTP(s) proxy server.
package httpproxy
import (
@@ -12,18 +11,9 @@ import (
"github.com/qdm12/gluetun/internal/models"
)
var _ Looper = (*Loop)(nil)
type Looper interface {
Runner
loopstate.Getter
loopstate.Applier
SettingsGetSetter
}
type Loop struct {
statusManager loopstate.Manager
state state.Manager
statusManager *loopstate.State
state *state.State
// Other objects
logger Logger
// Internal channels and locks

View File

@@ -6,10 +6,6 @@ import (
"github.com/qdm12/gluetun/internal/constants"
)
type Runner interface {
Run(ctx context.Context, done chan<- struct{})
}
func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
defer close(done)
@@ -27,7 +23,7 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
settings := l.state.GetSettings()
server := New(runCtx, settings.ListeningAddress, l.logger,
*settings.Stealth, *settings.Log, *settings.User,
*settings.Password)
*settings.Password, settings.ReadHeaderTimeout, settings.ReadTimeout)
errorCh := make(chan error)
go server.Run(runCtx, errorCh)

View File

@@ -8,25 +8,35 @@ import (
)
type Server struct {
address string
handler http.Handler
logger infoErrorer
internalWG *sync.WaitGroup
address string
handler http.Handler
logger infoErrorer
internalWG *sync.WaitGroup
readHeaderTimeout time.Duration
readTimeout time.Duration
}
func New(ctx context.Context, address string, logger Logger,
stealth, verbose bool, username, password string) *Server {
stealth, verbose bool, username, password string,
readHeaderTimeout, readTimeout time.Duration) *Server {
wg := &sync.WaitGroup{}
return &Server{
address: address,
handler: newHandler(ctx, wg, logger, stealth, verbose, username, password),
logger: logger,
internalWG: wg,
address: address,
handler: newHandler(ctx, wg, logger, stealth, verbose, username, password),
logger: logger,
internalWG: wg,
readHeaderTimeout: readHeaderTimeout,
readTimeout: readTimeout,
}
}
func (s *Server) Run(ctx context.Context, errorCh chan<- error) {
server := http.Server{Addr: s.address, Handler: s.handler}
server := http.Server{
Addr: s.address,
Handler: s.handler,
ReadHeaderTimeout: s.readHeaderTimeout,
ReadTimeout: s.readTimeout,
}
go func() {
<-ctx.Done()
const shutdownGraceDuration = 2 * time.Second

View File

@@ -4,11 +4,8 @@ import (
"context"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/httpproxy/state"
)
type SettingsGetSetter = state.SettingsGetSetter
func (l *Loop) GetSettings() (settings settings.HTTPProxy) {
return l.state.GetSettings()
}

View File

@@ -8,26 +8,12 @@ import (
"github.com/qdm12/gluetun/internal/constants"
)
type SettingsGetSetter interface {
SettingsGetter
SettingsSetter
}
type SettingsGetter interface {
GetSettings() (settings settings.HTTPProxy)
}
func (s *State) GetSettings() (settings settings.HTTPProxy) {
s.settingsMu.RLock()
defer s.settingsMu.RUnlock()
return s.settings
}
type SettingsSetter interface {
SetSettings(ctx context.Context,
settings settings.HTTPProxy) (outcome string)
}
func (s *State) SetSettings(ctx context.Context,
settings settings.HTTPProxy) (outcome string) {
s.settingsMu.Lock()

View File

@@ -1,19 +1,14 @@
package state
import (
"context"
"sync"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/loopstate"
"github.com/qdm12/gluetun/internal/models"
)
var _ Manager = (*State)(nil)
type Manager interface {
SettingsGetSetter
}
func New(statusApplier loopstate.Applier,
func New(statusApplier StatusApplier,
settings settings.HTTPProxy) *State {
return &State{
statusApplier: statusApplier,
@@ -22,7 +17,12 @@ func New(statusApplier loopstate.Applier,
}
type State struct {
statusApplier loopstate.Applier
statusApplier StatusApplier
settings settings.HTTPProxy
settingsMu sync.RWMutex
}
type StatusApplier interface {
ApplyStatus(ctx context.Context, status models.LoopStatus) (
outcome string, err error)
}

View File

@@ -2,13 +2,10 @@ package httpserver
import (
"regexp"
"time"
gomock "github.com/golang/mock/gomock"
)
func durationPtr(d time.Duration) *time.Duration { return &d }
var _ Logger = (*testLogger)(nil)
type testLogger struct{}

Some files were not shown because too many files have changed in this diff Show More