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

344 lines
8.2 KiB
Go

package utils
import (
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/openvpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/openvpn/extract"
)
type OpenVPNProviderSettings struct {
Ping int
RemoteCertTLS bool
Ciphers []string
Auth string
CA string
CRLVerify string
Cert string
Key string
RSAKey string
TLSAuth string
TLSCrypt string
MssFix uint16
FastIO bool
AuthUserPass bool
AuthToken bool
Fragment uint16
SndBuf uint32
RcvBuf uint32
// VerifyX509Name can be set to a custom name to verify against.
// Note VerifyX509Type has to be set for it to be verified.
// If it is left unset, the code will deduce a name to verify against
// using the connection hostname and according to VerifyX509Type.
VerifyX509Name string
// VerifyX509Type can be "name-prefix", "name"
VerifyX509Type string
TLSCipher string
TunMTU uint16
TunMTUExtra uint16
RenegDisabled bool
RenegSec uint16
KeyDirection string
ExtraLines []string
UDPLines []string
IPv6Lines []string
}
//nolint:gocognit,gocyclo
func OpenVPNConfig(provider OpenVPNProviderSettings,
connection models.Connection,
settings settings.OpenVPN) []string {
var lines openvpnConfigLines
lines.add("client")
lines.add("nobind")
lines.add("tls-exit") // exit OpenVPN on a TLS error
lines.add("auth-nocache") // do not cache auth credentials
lines.add("mute-replay-warnings") // these are often ignored by some VPN providers
lines.add("auth-retry", "nointeract") // retry authenticating without interaction
lines.add("suppress-timestamps") // do not log timestamps, the Gluetun logger takes care of it
lines.add("dev", settings.Interface)
lines.add("verb", fmt.Sprint(*settings.Verbosity))
lines.add("auth-user-pass", openvpn.AuthConf)
lines.add("proto", connection.Protocol)
lines.add("remote", connection.IP.String(), fmt.Sprint(connection.Port))
if !provider.AuthToken {
lines.add("pull-filter", "ignore", `"auth-token"`) // prevent auth failed loops
}
if provider.KeyDirection != "" {
lines.add("key-direction", provider.KeyDirection)
}
if provider.Ping > 0 {
lines.add("ping", fmt.Sprint(provider.Ping))
}
if provider.RenegDisabled {
lines.add("reneg-sec", "0")
} else if provider.RenegSec > 0 {
lines.add("reneg-sec", fmt.Sprint(provider.RenegSec))
}
if provider.RemoteCertTLS {
// equivalent to older 'ns-cert-type' option
lines.add("remote-cert-tls server")
}
x509Type := provider.VerifyX509Type
if x509Type != "" {
x509Name := provider.VerifyX509Name
if x509Name == "" {
// find name from connection hostname depending on type
switch x509Type {
case "name":
x509Name = connection.Hostname
case "name-prefix":
x509Name = strings.Split(connection.Hostname, ".")[0]
default:
panic(fmt.Sprintf("verify-x509-name type not supported: %q", x509Type))
}
}
lines.add("verify-x509-name", x509Name, x509Type)
}
if provider.TLSCipher != "" {
lines.add("tls-cipher", provider.TLSCipher)
}
if provider.FastIO {
lines.add("fast-io")
}
ciphers := defaultStringSlice(settings.Ciphers, provider.Ciphers)
cipherLines := CipherLines(ciphers, settings.Version)
lines.addLines(cipherLines)
auth := defaultString(*settings.Auth, provider.Auth)
if auth != "" {
lines.add("auth", auth)
}
if provider.TunMTU > 0 {
lines.add("tun-mtu", fmt.Sprint(provider.TunMTU))
}
if provider.TunMTUExtra > 0 {
lines.add("tun-mtu-extra", fmt.Sprint(provider.TunMTUExtra))
}
mssFix := defaultUint16(*settings.MSSFix, provider.MssFix)
if mssFix > 0 {
lines.add("mssfix", fmt.Sprint(mssFix))
}
if provider.Fragment > 0 {
lines.add("fragment", fmt.Sprint(provider.Fragment))
}
if provider.SndBuf > 0 {
lines.add("sndbuf", fmt.Sprint(provider.SndBuf))
}
if provider.RcvBuf > 0 {
lines.add("rcvbuf", fmt.Sprint(provider.RcvBuf))
}
if connection.Protocol == constants.UDP {
lines.add("explicit-exit-notify")
lines.addLines(provider.UDPLines)
}
if settings.ProcessUser != "root" {
lines.add("user", settings.ProcessUser)
lines.add("persist-tun")
lines.add("persist-key")
}
if *settings.IPv6 {
lines.add("tun-ipv6")
} else {
lines.add("pull-filter", "ignore", `"route-ipv6"`)
lines.add("pull-filter", "ignore", `"ifconfig-ipv6"`)
lines.addLines(provider.IPv6Lines)
}
if provider.CA != "" {
lines.addLines(WrapOpenvpnCA(provider.CA))
}
if provider.CRLVerify != "" {
lines.addLines(WrapOpenvpnCRLVerify(provider.CRLVerify))
}
if provider.Cert != "" {
lines.addLines(WrapOpenvpnCert(provider.Cert))
}
if provider.Key != "" {
lines.addLines(WrapOpenvpnKey(provider.Key))
}
if provider.RSAKey != "" {
lines.addLines(WrapOpenvpnRSAKey(provider.RSAKey))
}
if provider.TLSAuth != "" {
lines.addLines(WrapOpenvpnTLSAuth(provider.TLSAuth))
}
if provider.TLSCrypt != "" {
lines.addLines(WrapOpenvpnTLSCrypt(provider.TLSCrypt))
}
if *settings.EncryptedKey != "" {
lines.add("askpass", openvpn.AskPassPath)
keyData, err := extract.PEM([]byte(*settings.EncryptedKey))
panicOnError(err, "cannot extract PEM encrypted key")
lines.addLines(WrapOpenvpnEncryptedKey(keyData))
}
if *settings.Cert != "" {
certData, err := extract.PEM([]byte(*settings.Cert))
panicOnError(err, "cannot extract OpenVPN certificate")
lines.addLines(WrapOpenvpnCert(certData))
}
if *settings.Key != "" {
keyData, err := extract.PEM([]byte(*settings.Key))
panicOnError(err, "cannot extract OpenVPN key")
lines.addLines(WrapOpenvpnKey(keyData))
}
lines.addLines(provider.ExtraLines)
// Add a trailing empty line
lines.add("")
return lines
}
type openvpnConfigLines []string
func (o *openvpnConfigLines) add(words ...string) {
*o = append(*o, strings.Join(words, " "))
}
func (o *openvpnConfigLines) addLines(lines []string) {
for _, line := range lines {
o.add(line)
}
}
func defaultString(value, defaultValue string) string {
if value == "" {
return defaultValue
}
return value
}
func defaultUint16(value, defaultValue uint16) uint16 {
if value == 0 {
return defaultValue
}
return value
}
func defaultStringSlice(value, defaultValue []string) (
result []string) {
if len(value) > 0 {
result = make([]string, len(value))
copy(result, value)
return result
}
result = make([]string, len(defaultValue))
copy(result, defaultValue)
return result
}
func panicOnError(err error, context string) {
if err == nil {
return
}
panicMessage := fmt.Sprintf("%s: %s", context, err)
panic(panicMessage)
}
func WrapOpenvpnCA(certificate string) (lines []string) {
return []string{
"<ca>",
"-----BEGIN CERTIFICATE-----",
certificate,
"-----END CERTIFICATE-----",
"</ca>",
}
}
func WrapOpenvpnCert(clientCertificate string) (lines []string) {
return []string{
"<cert>",
"-----BEGIN CERTIFICATE-----",
clientCertificate,
"-----END CERTIFICATE-----",
"</cert>",
}
}
func WrapOpenvpnCRLVerify(x509CRL string) (lines []string) {
return []string{
"<crl-verify>",
"-----BEGIN X509 CRL-----",
x509CRL,
"-----END X509 CRL-----",
"</crl-verify>",
}
}
func WrapOpenvpnKey(clientKey string) (lines []string) {
return []string{
"<key>",
"-----BEGIN PRIVATE KEY-----",
clientKey,
"-----END PRIVATE KEY-----",
"</key>",
}
}
func WrapOpenvpnEncryptedKey(encryptedKey string) (lines []string) {
return []string{
"<key>",
"-----BEGIN ENCRYPTED PRIVATE KEY-----",
encryptedKey,
"-----END ENCRYPTED PRIVATE KEY-----",
"</key>",
}
}
func WrapOpenvpnRSAKey(rsaPrivateKey string) (lines []string) {
return []string{
"<key>",
"-----BEGIN RSA PRIVATE KEY-----",
rsaPrivateKey,
"-----END RSA PRIVATE KEY-----",
"</key>",
}
}
func WrapOpenvpnTLSAuth(staticKeyV1 string) (lines []string) {
return []string{
"<tls-auth>",
"-----BEGIN OpenVPN Static key V1-----",
staticKeyV1,
"-----END OpenVPN Static key V1-----",
"</tls-auth>",
}
}
func WrapOpenvpnTLSCrypt(staticKeyV1 string) (lines []string) {
return []string{
"<tls-crypt>",
"-----BEGIN OpenVPN Static key V1-----",
staticKeyV1,
"-----END OpenVPN Static key V1-----",
"</tls-crypt>",
}
}