Build openvpn configuration from scratch

This commit is contained in:
Quentin McGaw (desktop)
2020-02-07 13:55:24 +00:00
parent 6a9cd7ed9c
commit 69796e1ff9
11 changed files with 204 additions and 439 deletions

86
internal/pia/conf.go Normal file
View File

@@ -0,0 +1,86 @@
package pia
import (
"fmt"
"net"
"github.com/qdm12/golibs/files"
"github.com/qdm12/private-internet-access-docker/internal/constants"
"github.com/qdm12/private-internet-access-docker/internal/models"
)
func (c *configurator) BuildConf(region models.PIARegion, protocol models.NetworkProtocol,
encryption models.PIAEncryption, uid, gid int) (IPs []net.IP, port uint16, err error) {
var X509CRL, certificate string // depends on encryption
var cipherAlgo, authAlgo string // depends on encryption
if encryption == constants.PIAEncryptionNormal {
cipherAlgo = "aes-128-cbc"
authAlgo = "sha1"
X509CRL = constants.PIAX509CRL_NORMAL
certificate = constants.PIACertificate_NORMAL
if protocol == constants.UDP {
port = 1198
} else {
port = 502
}
} else { // strong
cipherAlgo = "aes-256-cbc"
authAlgo = "sha256"
X509CRL = constants.PIAX509CRL_STRONG
certificate = constants.PIACertificate_STRONG
if protocol == constants.UDP {
port = 1197
} else {
port = 501
}
}
subdomain := constants.PIARegionToSubdomainMapping[region]
IPs, err = c.lookupIP(subdomain + ".privateinternetaccess.com")
if err != nil {
return nil, 0, err
}
lines := []string{
"client",
"dev tun",
"nobind",
"persist-key",
"persist-tun",
"tls-client",
"remote-cert-tls server",
"compress",
"verb 1", // TODO env variable
"reneg-sec 0",
// Added constant values
"mute-replay-warnings",
"user nonrootuser",
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
"auth-retry nointeract",
"disable-occ",
// Modified variables
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
fmt.Sprintf("proto %s", string(protocol)),
fmt.Sprintf("cipher %s", cipherAlgo),
fmt.Sprintf("auth %s", authAlgo),
}
for _, IP := range IPs {
lines = append(lines, fmt.Sprintf("remote %s %d", IP.String(), port))
}
lines = append(lines, []string{
"<crl-verify>",
"-----BEGIN X509 CRL-----",
X509CRL,
"-----END X509 CRL-----",
"</crl-verify>",
}...)
lines = append(lines, []string{
"<ca>",
"-----BEGIN CERTIFICATE-----",
certificate,
"-----END CERTIFICATE-----",
"</ca>",
"",
}...)
err = c.fileManager.WriteLinesToFile(string(constants.OpenVPNConf), lines, files.FileOwnership(uid, gid), files.FilePermissions(0400))
return IPs, port, err
}

View File

@@ -1,61 +0,0 @@
package pia
import (
"archive/zip"
"bytes"
"fmt"
"io/ioutil"
"strings"
"github.com/qdm12/private-internet-access-docker/internal/constants"
"github.com/qdm12/private-internet-access-docker/internal/models"
)
func (c *configurator) DownloadOvpnConfig(encryption models.PIAEncryption,
protocol models.NetworkProtocol, region models.PIARegion) (lines []string, err error) {
c.logger.Info("%s: downloading openvpn configuration files", logPrefix)
URL := buildZipURL(encryption, protocol)
content, status, err := c.client.GetContent(URL)
if err != nil {
return nil, err
} else if status != 200 {
return nil, fmt.Errorf("HTTP Get %s resulted in HTTP status code %d", URL, status)
}
filename := fmt.Sprintf("%s.ovpn", region)
fileContent, err := getFileInZip(content, filename)
if err != nil {
return nil, fmt.Errorf("%s: %w", URL, err)
}
lines = strings.Split(string(fileContent), "\n")
return lines, nil
}
func buildZipURL(encryption models.PIAEncryption, protocol models.NetworkProtocol) (URL string) {
URL = string(constants.PIAOpenVPNURL) + "/openvpn"
if encryption == constants.PIAEncryptionStrong {
URL += "-strong"
}
if protocol == constants.TCP {
URL += "-tcp"
}
return URL + ".zip"
}
func getFileInZip(zipContent []byte, filename string) (fileContent []byte, err error) {
contentLength := int64(len(zipContent))
r, err := zip.NewReader(bytes.NewReader(zipContent), contentLength)
if err != nil {
return nil, err
}
for _, f := range r.File {
if f.Name == filename {
readCloser, err := f.Open()
if err != nil {
return nil, err
}
defer readCloser.Close()
return ioutil.ReadAll(readCloser)
}
}
return nil, fmt.Errorf("%s not found in zip archive file", filename)
}

View File

@@ -1,31 +0,0 @@
package pia
import (
"fmt"
"net"
"strings"
"github.com/qdm12/private-internet-access-docker/internal/constants"
)
func (c *configurator) ModifyLines(lines []string, IPs []net.IP, port uint16) (modifiedLines []string) {
c.logger.Info("%s: adapting openvpn configuration for server IP addresses and port %d", logPrefix, port)
// Remove lines
for _, line := range lines {
if strings.Contains(line, "privateinternetaccess.com") ||
strings.Contains(line, "resolv-retry") {
continue
}
modifiedLines = append(modifiedLines, line)
}
// Add lines
for _, IP := range IPs {
modifiedLines = append(modifiedLines, fmt.Sprintf("remote %s %d", IP.String(), port))
}
modifiedLines = append(modifiedLines, "auth-user-pass "+string(constants.OpenVPNAuthConf))
modifiedLines = append(modifiedLines, "auth-retry nointeract")
modifiedLines = append(modifiedLines, "pull-filter ignore \"auth-token\"") // prevent auth failed loops
modifiedLines = append(modifiedLines, "user nonrootuser")
modifiedLines = append(modifiedLines, "mute-replay-warnings")
return modifiedLines
}

View File

@@ -1,31 +0,0 @@
package pia
import (
"io/ioutil"
"net"
"strings"
"testing"
loggingMocks "github.com/qdm12/golibs/logging/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_ModifyLines(t *testing.T) {
t.Parallel()
original, err := ioutil.ReadFile("testdata/ovpn.golden")
require.NoError(t, err)
originalLines := strings.Split(string(original), "\n")
expected, err := ioutil.ReadFile("testdata/ovpn.modified.golden")
require.NoError(t, err)
expectedLines := strings.Split(string(expected), "\n")
var port uint16 = 3000
IPs := []net.IP{net.IP{100, 10, 10, 10}, net.IP{100, 20, 20, 20}}
logger := &loggingMocks.Logger{}
logger.On("Info", "%s: adapting openvpn configuration for server IP addresses and port %d", logPrefix, port).Once()
c := &configurator{logger: logger}
modifiedLines := c.ModifyLines(originalLines, IPs, port)
assert.Equal(t, expectedLines, modifiedLines)
logger.AssertExpectations(t)
}

View File

@@ -1,54 +0,0 @@
package pia
import (
"fmt"
"net"
"strconv"
"strings"
"github.com/qdm12/private-internet-access-docker/internal/constants"
"github.com/qdm12/private-internet-access-docker/internal/models"
)
func (c *configurator) ParseConfig(lines []string) (IPs []net.IP, port uint16, device models.VPNDevice, err error) {
c.logger.Info("%s: parsing openvpn configuration", logPrefix)
remoteLineFound := false
deviceLineFound := false
for _, line := range lines {
if strings.HasPrefix(line, "remote ") {
remoteLineFound = true
words := strings.Fields(line)
if len(words) != 3 {
return nil, 0, "", fmt.Errorf("line %q misses information", line)
}
host := words[1]
if err := c.verifyPort(words[2]); err != nil {
return nil, 0, "", fmt.Errorf("line %q has an invalid port: %w", line, err)
}
portUint64, _ := strconv.ParseUint(words[2], 10, 16)
port = uint16(portUint64)
IPs, err = c.lookupIP(host)
if err != nil {
return nil, 0, "", err
}
} else if strings.HasPrefix(line, "dev ") {
deviceLineFound = true
fields := strings.Fields(line)
if len(fields) != 2 {
return nil, 0, "", fmt.Errorf("line %q misses information", line)
}
device = models.VPNDevice(fields[1] + "0")
if device != constants.TUN && device != constants.TAP {
return nil, 0, "", fmt.Errorf("device %q is not valid", device)
}
}
}
if remoteLineFound && deviceLineFound {
c.logger.Info("%s: Found %d PIA server IP addresses, port %d and device %s", logPrefix, len(IPs), port, device)
return IPs, port, device, nil
} else if !remoteLineFound {
return nil, 0, "", fmt.Errorf("remote line not found in Openvpn configuration")
} else {
return nil, 0, "", fmt.Errorf("device line not found in Openvpn configuration")
}
}

View File

@@ -1,99 +0,0 @@
package pia
import (
"fmt"
"io/ioutil"
"net"
"strings"
"testing"
loggingMocks "github.com/qdm12/golibs/logging/mocks"
"github.com/qdm12/golibs/verification"
"github.com/qdm12/private-internet-access-docker/internal/constants"
"github.com/qdm12/private-internet-access-docker/internal/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_ParseConfig(t *testing.T) {
t.Parallel()
original, err := ioutil.ReadFile("testdata/ovpn.golden")
require.NoError(t, err)
exampleLines := strings.Split(string(original), "\n")
tests := map[string]struct {
lines []string
lookupIPs []net.IP
lookupIPErr error
IPs []net.IP
port uint16
device models.VPNDevice
err error
}{
"no data": {
err: fmt.Errorf("remote line not found in Openvpn configuration"),
},
"bad remote line": {
lines: []string{"remote field2"},
err: fmt.Errorf("line \"remote field2\" misses information"),
},
"bad remote port": {
lines: []string{"remote field2 port"},
err: fmt.Errorf("line \"remote field2 port\" has an invalid port: port \"port\" is not a valid integer"),
},
"lookupIP error": {
lines: []string{"remote host 1000"},
lookupIPErr: fmt.Errorf("lookup error"),
err: fmt.Errorf("lookup error"),
},
"missing dev line": {
lines: []string{"remote host 1994"},
err: fmt.Errorf("device line not found in Openvpn configuration"),
},
"bad dev line": {
lines: []string{"dev field2 field3"},
err: fmt.Errorf("line \"dev field2 field3\" misses information"),
},
"bad device": {
lines: []string{"dev xx"},
err: fmt.Errorf("device \"xx0\" is not valid"),
},
"valid lines": {
lines: []string{"remote host 1194", "dev tap", "blabla"},
port: 1194,
device: constants.TAP,
},
"real data": {
lines: exampleLines,
lookupIPs: []net.IP{{100, 100, 100, 100}, {100, 100, 200, 200}},
IPs: []net.IP{{100, 100, 100, 100}, {100, 100, 200, 200}},
port: 1198,
device: constants.TUN,
},
}
for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
logger := &loggingMocks.Logger{}
logger.On("Info", "%s: parsing openvpn configuration", logPrefix).Once()
if tc.err == nil {
logger.On("Info", "%s: Found %d PIA server IP addresses, port %d and device %s", logPrefix, len(tc.IPs), tc.port, tc.device).Once()
}
lookupIP := func(host string) ([]net.IP, error) {
return tc.lookupIPs, tc.lookupIPErr
}
c := &configurator{logger: logger, verifyPort: verification.NewVerifier().VerifyPort, lookupIP: lookupIP}
IPs, port, device, err := c.ParseConfig(tc.lines)
if tc.err != nil {
require.Error(t, err)
assert.Equal(t, tc.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, tc.IPs, IPs)
assert.Equal(t, tc.port, port)
assert.Equal(t, tc.device, device)
logger.AssertExpectations(t)
})
}
}

View File

@@ -16,10 +16,8 @@ const logPrefix = "PIA configurator"
// Configurator contains methods to download, read and modify the openvpn configuration to connect as a client
type Configurator interface {
DownloadOvpnConfig(encryption models.PIAEncryption,
protocol models.NetworkProtocol, region models.PIARegion) (lines []string, err error)
ParseConfig(lines []string) (IPs []net.IP, port uint16, device models.VPNDevice, err error)
ModifyLines(lines []string, IPs []net.IP, port uint16) (modifiedLines []string)
BuildConf(region models.PIARegion, protocol models.NetworkProtocol,
encryption models.PIAEncryption, uid, gid int) (IPs []net.IP, port uint16, err error)
GetPortForward() (port uint16, err error)
WritePortForward(filepath models.Filepath, port uint16) (err error)
AllowPortForwardFirewall(device models.VPNDevice, port uint16) (err error)

View File

@@ -1,72 +0,0 @@
client
dev tun
proto udp
remote belgium.privateinternetaccess.com 1198
resolv-retry infinite
nobind
persist-key
persist-tun
cipher aes-128-cbc
auth sha1
tls-client
remote-cert-tls server
auth-user-pass
compress
verb 1
reneg-sec 0
<crl-verify>
-----BEGIN X509 CRL-----
MIICWDCCAUAwDQYJKoZIhvcNAQENBQAwgegxCzAJBgNVBAYTAlVTMQswCQYDVQQI
EwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRl
cm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAw
HgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0
ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRl
aW50ZXJuZXRhY2Nlc3MuY29tFw0xNjA3MDgxOTAwNDZaFw0zNjA3MDMxOTAwNDZa
MCYwEQIBARcMMTYwNzA4MTkwMDQ2MBECAQYXDDE2MDcwODE5MDA0NjANBgkqhkiG
9w0BAQ0FAAOCAQEAQZo9X97ci8EcPYu/uK2HB152OZbeZCINmYyluLDOdcSvg6B5
jI+ffKN3laDvczsG6CxmY3jNyc79XVpEYUnq4rT3FfveW1+Ralf+Vf38HdpwB8EW
B4hZlQ205+21CALLvZvR8HcPxC9KEnev1mU46wkTiov0EKc+EdRxkj5yMgv0V2Re
ze7AP+NQ9ykvDScH4eYCsmufNpIjBLhpLE2cuZZXBLcPhuRzVoU3l7A9lvzG9mjA
5YijHJGHNjlWFqyrn1CfYS6koa4TGEPngBoAziWRbDGdhEgJABHrpoaFYaL61zqy
MR6jC0K2ps9qyZAN74LEBedEfK7tBOzWMwr58A==
-----END X509 CRL-----
</crl-verify>
<ca>
-----BEGIN CERTIFICATE-----
MIIFqzCCBJOgAwIBAgIJAKZ7D5Yv87qDMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNV
BAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIElu
dGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3Mx
IDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkB
FiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzM1
MThaFw0zNDA0MTIxNzM1MThaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
EzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQg
QWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UE
AxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50
ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVy
bmV0YWNjZXNzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPXD
L1L9tX6DGf36liA7UBTy5I869z0UVo3lImfOs/GSiFKPtInlesP65577nd7UNzzX
lH/P/CnFPdBWlLp5ze3HRBCc/Avgr5CdMRkEsySL5GHBZsx6w2cayQ2EcRhVTwWp
cdldeNO+pPr9rIgPrtXqT4SWViTQRBeGM8CDxAyTopTsobjSiYZCF9Ta1gunl0G/
8Vfp+SXfYCC+ZzWvP+L1pFhPRqzQQ8k+wMZIovObK1s+nlwPaLyayzw9a8sUnvWB
/5rGPdIYnQWPgoNlLN9HpSmsAcw2z8DXI9pIxbr74cb3/HSfuYGOLkRqrOk6h4RC
OfuWoTrZup1uEOn+fw8CAwEAAaOCAVQwggFQMB0GA1UdDgQWBBQv63nQ/pJAt5tL
y8VJcbHe22ZOsjCCAR8GA1UdIwSCARYwggESgBQv63nQ/pJAt5tLy8VJcbHe22ZO
sqGB7qSB6zCB6DELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRMwEQYDVQQHEwpM
b3NBbmdlbGVzMSAwHgYDVQQKExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4G
A1UECxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAMTF1ByaXZhdGUg
SW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQpExdQcml2YXRlIEludGVybmV0IEFjY2Vz
czEvMC0GCSqGSIb3DQEJARYgc2VjdXJlQHByaXZhdGVpbnRlcm5ldGFjY2Vzcy5j
b22CCQCmew+WL/O6gzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQAn
a5PgrtxfwTumD4+3/SYvwoD66cB8IcK//h1mCzAduU8KgUXocLx7QgJWo9lnZ8xU
ryXvWab2usg4fqk7FPi00bED4f4qVQFVfGfPZIH9QQ7/48bPM9RyfzImZWUCenK3
7pdw4Bvgoys2rHLHbGen7f28knT2j/cbMxd78tQc20TIObGjo8+ISTRclSTRBtyC
GohseKYpTS9himFERpUgNtefvYHbn70mIOzfOJFTVqfrptf9jXa9N8Mpy3ayfodz
1wiqdteqFXkTYoSDctgKMiZ6GdocK9nMroQipIQtpnwd4yBDWIyC6Bvlkrq5TQUt
YDQ8z9v+DMO6iwyIDRiU
-----END CERTIFICATE-----
</ca>
disable-occ

View File

@@ -1,78 +0,0 @@
client
dev tun
proto udp
nobind
persist-key
persist-tun
cipher aes-128-cbc
auth sha1
tls-client
remote-cert-tls server
auth-user-pass
compress
verb 1
reneg-sec 0
<crl-verify>
-----BEGIN X509 CRL-----
MIICWDCCAUAwDQYJKoZIhvcNAQENBQAwgegxCzAJBgNVBAYTAlVTMQswCQYDVQQI
EwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRl
cm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAw
HgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0
ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRl
aW50ZXJuZXRhY2Nlc3MuY29tFw0xNjA3MDgxOTAwNDZaFw0zNjA3MDMxOTAwNDZa
MCYwEQIBARcMMTYwNzA4MTkwMDQ2MBECAQYXDDE2MDcwODE5MDA0NjANBgkqhkiG
9w0BAQ0FAAOCAQEAQZo9X97ci8EcPYu/uK2HB152OZbeZCINmYyluLDOdcSvg6B5
jI+ffKN3laDvczsG6CxmY3jNyc79XVpEYUnq4rT3FfveW1+Ralf+Vf38HdpwB8EW
B4hZlQ205+21CALLvZvR8HcPxC9KEnev1mU46wkTiov0EKc+EdRxkj5yMgv0V2Re
ze7AP+NQ9ykvDScH4eYCsmufNpIjBLhpLE2cuZZXBLcPhuRzVoU3l7A9lvzG9mjA
5YijHJGHNjlWFqyrn1CfYS6koa4TGEPngBoAziWRbDGdhEgJABHrpoaFYaL61zqy
MR6jC0K2ps9qyZAN74LEBedEfK7tBOzWMwr58A==
-----END X509 CRL-----
</crl-verify>
<ca>
-----BEGIN CERTIFICATE-----
MIIFqzCCBJOgAwIBAgIJAKZ7D5Yv87qDMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNV
BAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIElu
dGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3Mx
IDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkB
FiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzM1
MThaFw0zNDA0MTIxNzM1MThaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
EzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQg
QWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UE
AxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50
ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVy
bmV0YWNjZXNzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPXD
L1L9tX6DGf36liA7UBTy5I869z0UVo3lImfOs/GSiFKPtInlesP65577nd7UNzzX
lH/P/CnFPdBWlLp5ze3HRBCc/Avgr5CdMRkEsySL5GHBZsx6w2cayQ2EcRhVTwWp
cdldeNO+pPr9rIgPrtXqT4SWViTQRBeGM8CDxAyTopTsobjSiYZCF9Ta1gunl0G/
8Vfp+SXfYCC+ZzWvP+L1pFhPRqzQQ8k+wMZIovObK1s+nlwPaLyayzw9a8sUnvWB
/5rGPdIYnQWPgoNlLN9HpSmsAcw2z8DXI9pIxbr74cb3/HSfuYGOLkRqrOk6h4RC
OfuWoTrZup1uEOn+fw8CAwEAAaOCAVQwggFQMB0GA1UdDgQWBBQv63nQ/pJAt5tL
y8VJcbHe22ZOsjCCAR8GA1UdIwSCARYwggESgBQv63nQ/pJAt5tLy8VJcbHe22ZO
sqGB7qSB6zCB6DELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRMwEQYDVQQHEwpM
b3NBbmdlbGVzMSAwHgYDVQQKExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4G
A1UECxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAMTF1ByaXZhdGUg
SW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQpExdQcml2YXRlIEludGVybmV0IEFjY2Vz
czEvMC0GCSqGSIb3DQEJARYgc2VjdXJlQHByaXZhdGVpbnRlcm5ldGFjY2Vzcy5j
b22CCQCmew+WL/O6gzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQAn
a5PgrtxfwTumD4+3/SYvwoD66cB8IcK//h1mCzAduU8KgUXocLx7QgJWo9lnZ8xU
ryXvWab2usg4fqk7FPi00bED4f4qVQFVfGfPZIH9QQ7/48bPM9RyfzImZWUCenK3
7pdw4Bvgoys2rHLHbGen7f28knT2j/cbMxd78tQc20TIObGjo8+ISTRclSTRBtyC
GohseKYpTS9himFERpUgNtefvYHbn70mIOzfOJFTVqfrptf9jXa9N8Mpy3ayfodz
1wiqdteqFXkTYoSDctgKMiZ6GdocK9nMroQipIQtpnwd4yBDWIyC6Bvlkrq5TQUt
YDQ8z9v+DMO6iwyIDRiU
-----END CERTIFICATE-----
</ca>
disable-occ
remote 100.10.10.10 3000
remote 100.20.20.20 3000
auth-user-pass /etc/openvpn/auth.conf
auth-retry nointeract
pull-filter ignore "auth-token"
user nonrootuser
mute-replay-warnings