2024-10-11 19:27:29 +00:00

349 lines
8.4 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/pkcs8"
)
type OpenVPNProviderSettings struct {
Ping int
RemoteCertTLS bool
Ciphers []string
Auth string
CAs []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
SetEnv map[string]string
ExtraLines []string
UDPLines []string
IPv6Lines []string
}
//nolint:gocognit,gocyclo
func OpenVPNConfig(provider OpenVPNProviderSettings,
connection models.Connection,
settings settings.OpenVPN, ipv6Supported bool,
) []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("proto", connection.Protocol)
lines.add("remote", connection.IP.String(), fmt.Sprint(connection.Port))
if *settings.User != "" {
lines.add("auth-user-pass", openvpn.AuthConf)
}
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)
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 !ipv6Supported {
lines.add("pull-filter", "ignore", `"tun-ipv6"`)
lines.add("pull-filter", "ignore", `"route-ipv6"`)
lines.add("pull-filter", "ignore", `"ifconfig-ipv6"`)
lines.addLines(provider.IPv6Lines)
}
for envKey, envValue := range provider.SetEnv {
lines.add("setenv", envKey, envValue)
}
for _, ca := range provider.CAs {
lines.addLines(WrapOpenvpnCA(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 != "" {
encryptedBase64DERKey := *settings.EncryptedKey
// OpenVPN above 2.4 does not support old encryption schemes such as
// DES-CBC, so decrypt and reencrypt the key.
// This is a workaround for VPN secure.
var err error
encryptedBase64DERKey, err = pkcs8.UpgradeEncryptedKey(encryptedBase64DERKey, *settings.KeyPassphrase)
if err != nil {
// TODO return an error instead.
panic(fmt.Sprintf("upgrading encrypted key: %s", err))
}
lines.add("askpass", openvpn.AskPassPath)
lines.addLines(WrapOpenvpnEncryptedKey(encryptedBase64DERKey))
}
if *settings.Cert != "" {
lines.addLines(WrapOpenvpnCert(*settings.Cert))
}
if *settings.Key != "" {
lines.addLines(WrapOpenvpnKey(*settings.Key))
}
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 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>",
}
}