Documentation
¶
Overview ¶
Package profile parses OpenVPN .ovpn configuration files, and builds profiles programmatically from a Spec.
Every directive line is recorded on Profile.Directives, and every inline block tag on Profile.InlineBlocks, for the capability preflight in the caps package. A profile that names its CA, certificate or key in a separate file needs a directory to resolve it: ParsePath, ParseFileIn and ParseFileInFS have one, ParseFile and ParseString refuse such a profile with ErrNoProfileDir.
Index ¶
Examples ¶
Constants ¶
const StaticKeySize = 256
StaticKeySize is the length of an OpenVPN static key in bytes, what OpenVPN calls a 2048-bit key.
Variables ¶
var ErrFileRefEscapes = errors.New("resolves outside the profile's own directory")
ErrFileRefEscapes reports a ca, cert or key reference that resolves outside the profile's own directory.
var ErrNoProfileDir = errors.New(
"a file reference needs the profile's directory: parse with ParsePath, or with ParseFileIn when the bytes are already read")
ErrNoProfileDir reports a ca, cert or key file reference in a profile parsed without a directory to resolve it against.
Functions ¶
This section is empty.
Types ¶
type AuthFlow ¶
type AuthFlow int
AuthFlow describes which authentication mechanism the profile asks for.
const ( // FlowCertAuth is mutual-TLS client certificate authentication: the // profile embeds <cert> and <key> and asks for no credentials. FlowCertAuth AuthFlow = iota // FlowUserPass is username/password authentication, and the fallback. FlowUserPass // FlowFederated is the SAML/CRV1 flow against an identity provider. FlowFederated )
type DNSOptions ¶
type DNSOptions struct {
// Servers are dhcp-option DNS addresses, in preference order.
Servers []string `json:"servers,omitempty"`
// SearchDomains are dhcp-option DOMAIN; RouteDomains are DOMAIN-ROUTE.
SearchDomains []string `json:"search_domains,omitempty"`
RouteDomains []string `json:"route_domains,omitempty"`
}
DNSOptions is the resolver configuration to push through dhcp-option.
type Directive ¶
type Directive struct {
// Name is the directive keyword, lowercased.
Name string `json:"name"`
// Args are the whitespace-separated arguments after the keyword, nil for
// a bare directive.
Args []string `json:"args,omitempty"`
// Line is the 1-based line number, zero when the directive has no source
// line.
Line int `json:"line,omitempty"`
}
Directive is one directive line as the parser saw it.
type Endpoint ¶
type Endpoint struct {
// Host is the hostname or address. Required.
Host string `json:"host"`
// Port is zero for 1194.
Port int `json:"port,omitempty"`
// Proto is the transport in OpenVPN's spelling; empty means UDP.
Proto string `json:"proto,omitempty"`
}
Endpoint is one --remote line.
type FileRef ¶
type FileRef struct {
// Tag is the directive keyword: "ca", "cert" or "key".
Tag string
// Line is the 1-based line number the directive appeared on.
Line int
// Loaded reports that the file was read into the matching field.
Loaded bool
// Superseded reports that an inline block of the same tag was used
// instead, and the file was not opened.
Superseded bool
}
FileRef records a ca, cert or key directive that named a file, and what the parser did with it. The file name is not kept, since these records reach session reports.
type InlineBlock ¶
type InlineBlock struct {
// Tag is the block's tag name, verbatim.
Tag string
// Line is the 1-based line number of the opening tag.
Line int
}
InlineBlock records that an inline <tag>...</tag> block was present. The body is never kept, so an InlineBlock is safe to log.
type KeyDirection ¶
type KeyDirection int
KeyDirection is the --key-direction value: 0, 1, or absent. With a direction the static key is split into per-direction halves; absent uses the whole key both ways, so it is not the same as 0.
const ( // KeyDirectionAbsent means no key-direction directive. KeyDirectionAbsent KeyDirection = iota // KeyDirection0 is "key-direction 0". KeyDirection0 // KeyDirection1 is "key-direction 1". KeyDirection1 )
func ParseKeyDirection ¶
func ParseKeyDirection(s string) (KeyDirection, error)
ParseKeyDirection parses the argument of a key-direction directive: 0 or 1.
func (KeyDirection) MarshalText ¶
func (d KeyDirection) MarshalText() ([]byte, error)
MarshalText implements encoding.TextMarshaler. An out-of-range value marshals as its String placeholder, which UnmarshalText refuses.
func (KeyDirection) String ¶
func (d KeyDirection) String() string
String returns "absent", "0" or "1", or "key-direction(N)" out of range.
func (*KeyDirection) UnmarshalText ¶
func (d *KeyDirection) UnmarshalText(text []byte) error
UnmarshalText implements encoding.TextUnmarshaler, accepting "absent", "0" and "1". An unrecognised name is an error.
func (KeyDirection) Value ¶
func (d KeyDirection) Value() (int, bool)
Value returns the numeric direction and whether the profile gave one. Use it rather than int(d), which is not the direction.
type MSSFixMode ¶
type MSSFixMode int
MSSFixMode says how a numeric mssfix value is measured, from the directive's optional second word.
const ( // MSSFixLink counts the tunnel encapsulation but not the outer IP and // UDP/TCP headers: a bare "mssfix N". MSSFixLink MSSFixMode = iota // MSSFixEncap counts the outer headers as well: "mssfix N mtu". MSSFixEncap // MSSFixFixed counts no encapsulation, only the inner IPv4 and TCP // headers: "mssfix N fixed". MSSFixFixed )
func ParseMSSFixMode ¶
func ParseMSSFixMode(s string) (MSSFixMode, bool)
ParseMSSFixMode parses a mode name; the empty string is MSSFixLink. An unrecognised word is refused.
func (MSSFixMode) MarshalText ¶
func (m MSSFixMode) MarshalText() ([]byte, error)
MarshalText implements encoding.TextMarshaler. An out-of-range value marshals as its String placeholder, which UnmarshalText refuses.
func (MSSFixMode) String ¶
func (m MSSFixMode) String() string
String names the mode, or returns "mssfix-mode(N)" out of range.
func (*MSSFixMode) UnmarshalText ¶
func (m *MSSFixMode) UnmarshalText(text []byte) error
UnmarshalText implements encoding.TextUnmarshaler. An unrecognised name is an error.
type Profile ¶
type Profile struct {
// Remote, Port and Proto are Remotes[0].
Remote string
Port int
Proto Proto
// Remotes is every --remote line, in file order. Never empty in a parsed
// profile.
Remotes []Remote
// CA, Cert and Key are PEM bodies, from inline blocks or referenced files.
CA []byte
Cert []byte
Key []byte
// FileRefs records every ca, cert or key directive that named a file, in
// file order.
FileRefs []FileRef
// TLSAuth and TLSCrypt are the <tls-auth> and <tls-crypt> keys, or nil.
// The parser loads both if both are present; the connect path refuses the
// pair.
TLSAuth *StaticKey
TLSCrypt *StaticKey
// KeyDirection is --key-direction: 0, 1, or absent.
KeyDirection KeyDirection
// Cipher is the data-channel cipher name, e.g. "AES-256-GCM".
Cipher string
// Auth is the HMAC digest, e.g. "SHA256"; SHA1 when absent.
Auth string
// AuthSet reports whether the profile carried an explicit auth directive.
AuthSet bool
// Verb is OpenVPN's 0–11 verbosity; the default is 3. At 4 the verified
// server certificate is logged on each handshake.
Verb int
// RenegSec is the key renegotiation interval in seconds; 3600 when
// absent, and 0 disables client-initiated renegotiation.
RenegSec int
// RenegBytes is the renegotiation byte threshold; 0 means none.
RenegBytes int64
// BecomePrimarySec delays a renegotiated key becoming the send key; 0
// uses the default derived from reneg-sec.
BecomePrimarySec int
// HandWindowSec is --hand-window; 0 means the reference's 60
// (openvpn-2.6.22 src/openvpn/options.c:880).
HandWindowSec int
// PingInterval is the keepalive send interval in seconds, from ping or
// keepalive; 0 means the client default.
PingInterval int
// PingTimeout is the dead-link timeout in seconds, from ping-restart,
// ping-exit or keepalive. One field for all three, as in
// openvpn-2.6.22 src/openvpn/options.c:6963-6975. PingTimeoutSet
// distinguishes an explicit 0, which disables the timer, from an absent
// directive, which leaves the client default.
PingTimeout int
PingTimeoutSet bool
// PingExit reports that PingTimeout came from ping-exit. Nothing acts on
// it; the client's dead-link teardown is terminal either way.
PingExit bool
// ExplicitExitNotify is how many exit notifications a disconnect sends. A
// bare directive means 1 (openvpn-2.4.12 src/openvpn/options.c:6133).
ExplicitExitNotify int
// Compression is the data-channel framing the profile asked for, from
// comp-lzo or compress. The server does not push its own, so a client that
// ignored this would send packets a comp-lzo peer drops.
Compression compress.Mode
// AllowCompression is the --allow-compression policy.
AllowCompression compress.AllowCompression
// TunMTU is --tun-mtu; 0 means 1500.
TunMTU int
// MSSFix is the --mssfix clamp and MSSFixMode how it is measured.
// MSSFixSet distinguishes "mssfix 0", which disables clamping, from an
// absent directive, which applies OpenVPN's default.
MSSFix int
MSSFixMode MSSFixMode
MSSFixSet bool
// RemoteRandom asks the dialer to shuffle Remotes on each attempt.
// Remotes itself stays in file order.
RemoteRandom bool
// RandomHostname asks the dialer to prepend a random label to the
// hostname; some deployments have no DNS record for the bare name.
RandomHostname bool
// VerifyX509Name is the subject DN or common name the server certificate
// must present, empty for no check. It is not a hostname and is not
// matched against a SAN.
VerifyX509Name string
// VerifyX509NameMatch says which part of the subject is compared.
VerifyX509NameMatch X509NameMatch
// AuthUserPass reports that the profile carried auth-user-pass.
AuthUserPass bool
// RemoteCertTLSServer and NSCertTypeServer report "remote-cert-tls server"
// and "ns-cert-type server".
RemoteCertTLSServer bool
NSCertTypeServer bool
// Federated is set by auth-federate or "x-go-openvpn-flow saml" (formerly
// x-openlawsvpn-flow): authenticate against an identity provider.
Federated bool
// DNS is the resolver configuration from the profile's dhcp-option
// directives. What the server pushes wins where the two overlap.
DNS dns.Config
// Directives records every directive line in file order, including those
// the parser ignores, for the capability preflight.
Directives []Directive
// InlineBlocks records every inline block's tag and line, in file order,
// for the capability preflight.
InlineBlocks []InlineBlock
}
Profile holds the parsed contents of an .ovpn file.
It holds key material. TLSAuth and TLSCrypt redact under %v and json.Marshal; CA, Cert and Key do not.
func ParseFile ¶
ParseFile parses an .ovpn profile from r. A profile that names a file is refused with ErrNoProfileDir.
func ParseFileIn ¶
ParseFileIn parses an .ovpn profile from r, resolving file-referenced ca, cert and key directives against baseDir. An empty baseDir behaves as ParseFile.
func ParseFileInFS ¶
ParseFileInFS parses an .ovpn profile from r, resolving file-referenced ca, cert and key directives against fsys. A nil fsys behaves as ParseFile.
fs.FS does not confine symlinks, and os.DirFS follows one out of the tree. For a real directory pass os.OpenRoot(dir).FS(), or use ParseFileIn.
func ParsePath ¶
ParsePath opens path and parses it, resolving file references against the profile's own directory and confined to it.
func ParseString ¶
ParseString parses an in-memory profile, as ParseFile.
func (*Profile) AuthFlow ¶
AuthFlow reports which authentication mechanism the profile asks for, from the profile alone. A profile with auth-user-pass and a certificate is FlowUserPass; the certificate is still used.
func (*Profile) RequiresCredentials ¶
RequiresCredentials reports whether the profile carries auth-user-pass. The values come from the client's credential callback.
func (*Profile) Spec ¶
Spec returns a Spec that builds a profile equivalent to p. Settings at their default are left zero, and directives no Spec field covers are carried on Extra in file order.
It fails only for a profile that names its tls-auth or tls-crypt key in a file, since the key is not in p to carry.
type Proto ¶
type Proto int
Proto is the transport protocol for the VPN tunnel.
func ParseProto ¶
ParseProto normalises an OpenVPN transport spelling, as used by --proto and the third field of --remote. Address-family spellings (udp4, tcp6-client) reduce to their transport; the -server spellings are refused.
type Remote ¶
type Remote struct {
// Host is the hostname or address as written in the profile.
Host string
// Port is the port to dial.
Port int
// Proto is the transport to dial.
Proto Proto
// ProtoSet reports whether the remote line named a protocol itself.
ProtoSet bool
}
Remote is one --remote line. Port and Proto are resolved against the profile's --port and --proto when the line does not name them.
type Spec ¶
type Spec struct {
// Remotes is the --remote list, in dial order. At least one is required.
Remotes []Endpoint `json:"remotes,omitempty"`
// CA, Cert and Key are the PEM bodies of the <ca>, <cert> and <key>
// blocks.
CA []byte `json:"ca,omitempty"`
Cert []byte `json:"cert,omitempty"`
Key []byte `json:"key,omitempty"`
// TLSAuth and TLSCrypt are the bodies of <tls-auth> and <tls-crypt>, as
// "openvpn --genkey" writes them.
TLSAuth []byte `json:"tls_auth,omitempty"`
TLSCrypt []byte `json:"tls_crypt,omitempty"`
// KeyDirection is --key-direction. KeyDirectionAbsent uses the whole key
// in both directions.
KeyDirection KeyDirection `json:"key_direction,omitempty"`
// AuthUserPass asks for a username and password, which the client's
// CredentialsFn supplies.
AuthUserPass bool `json:"auth_user_pass,omitempty"`
// AuthFederate is auth-federate: use the SAML/CRV1 flow.
AuthFederate bool `json:"auth_federate,omitempty"`
// Cipher is --cipher; empty means AES-256-GCM.
Cipher string `json:"cipher,omitempty"`
// Auth is --auth; empty means absent, and SHA1 applies.
Auth string `json:"auth,omitempty"`
// RemoteCertTLSServer is "remote-cert-tls server"; NSCertTypeServer is
// "ns-cert-type server".
RemoteCertTLSServer bool `json:"remote_cert_tls_server,omitempty"`
NSCertTypeServer bool `json:"ns_cert_type_server,omitempty"`
// VerifyX509Name and VerifyX509NameMatch are --verify-x509-name.
VerifyX509Name string `json:"verify_x509_name,omitempty"`
VerifyX509NameMatch X509NameMatch `json:"verify_x509_name_match,omitempty"`
// Compression is the compression directive: "comp-lzo", "comp-lzo no",
// "compress", "compress lz4", "compress lz4-v2" or "compress stub-v2".
// Empty means none.
Compression string `json:"compression,omitempty"`
// AllowCompression is --allow-compression: "no", "asym" or "yes".
AllowCompression string `json:"allow_compression,omitempty"`
// TunMTU is --tun-mtu.
TunMTU int `json:"tun_mtu,omitempty"`
// MSSFix is --mssfix N, with MSSFixMode its optional second word.
// MSSFixOff is an explicit "mssfix 0".
MSSFix int `json:"mssfix,omitempty"`
MSSFixMode MSSFixMode `json:"mssfix_mode,omitempty"`
MSSFixOff bool `json:"mssfix_off,omitempty"`
// RenegSec is --reneg-sec; zero means 3600. NoReneg is an explicit
// "reneg-sec 0", which disables client-initiated renegotiation.
RenegSec int `json:"reneg_sec,omitempty"`
NoReneg bool `json:"no_reneg,omitempty"`
RenegBytes int64 `json:"reneg_bytes,omitempty"`
// HandWindow is --hand-window; BecomePrimary is --become-primary.
HandWindow int `json:"hand_window,omitempty"`
BecomePrimary int `json:"become_primary,omitempty"`
// Ping is the keepalive send interval and PingTimeout the dead-link
// timeout, in seconds. PingTimeoutOff is an explicit "ping-restart 0",
// which disables the dead-link timer. PingExit ends the session on
// timeout instead of restarting it, and needs PingTimeout or
// PingTimeoutOff.
Ping int `json:"ping,omitempty"`
PingTimeout int `json:"ping_timeout,omitempty"`
PingTimeoutOff bool `json:"ping_timeout_off,omitempty"`
PingExit bool `json:"ping_exit,omitempty"`
// ExplicitExitNotify is how many exit notifications a disconnect sends.
ExplicitExitNotify int `json:"explicit_exit_notify,omitempty"`
// RemoteRandom is --remote-random; RemoteRandomHostname is
// --remote-random-hostname.
RemoteRandom bool `json:"remote_random,omitempty"`
RemoteRandomHostname bool `json:"remote_random_hostname,omitempty"`
// DNS is the resolver configuration, as dhcp-option DNS, DOMAIN and
// DOMAIN-ROUTE.
DNS DNSOptions `json:"dns,omitzero"`
// Extra carries directives no field above covers, applied after them. A
// directive a field covers is refused.
Extra []Directive `json:"extra,omitempty"`
}
Spec describes a tunnel programmatically, one field per setting. A zero field is an absent directive and takes the parser's default.
A Spec serialises to JSON with its enums as names. A stored Spec is key material: CA, Cert, Key, TLSAuth and TLSCrypt round-trip verbatim.
func (Spec) Build ¶
Build assembles the profile through the parser's own assembler, so it makes every check the parser makes.
Example ¶
ExampleSpec_Build shows the defaults the assembler fills in around a Spec.
package main
import (
"fmt"
"github.com/buengese/go-openvpn/profile"
)
const testCA = `-----BEGIN CERTIFICATE-----
MIIBkTCB+wIJAKZ1x2Y3Z4Q5MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMMCXRl
-----END CERTIFICATE-----
`
func main() {
p, err := profile.Spec{
Remotes: []profile.Endpoint{{Host: "vpn.example.test"}},
CA: []byte(testCA),
AuthUserPass: true,
RemoteCertTLSServer: true,
}.Build()
if err != nil {
fmt.Println("build:", err)
return
}
fmt.Printf("dial %s:%d over %v\n", p.Remote, p.Port, p.Proto)
fmt.Printf("cipher %s, auth %s (explicit: %t)\n", p.Cipher, p.Auth, p.AuthSet)
fmt.Printf("reneg-sec %d\n", p.RenegSec)
fmt.Printf("needs credentials: %t\n", p.RequiresCredentials())
}
Output: dial vpn.example.test:1194 over udp cipher AES-256-GCM, auth SHA1 (explicit: false) reneg-sec 3600 needs credentials: true
type StaticKey ¶
type StaticKey [StaticKeySize]byte
StaticKey is an OpenVPN static key, the material behind a <tls-auth> or <tls-crypt> block.
String, GoString and MarshalJSON yield a placeholder, so a key does not leak through %v or json.Marshal of a struct holding it. Encode, or indexing the array, gets the bytes.
func ParseStaticKey ¶
ParseStaticKey parses the body of a <tls-auth> or <tls-crypt> block: an optional header, 512 hexadecimal digits in any line arrangement, and an optional footer. Blank and comment lines are ignored.
The error never contains any part of body, which is key material even when malformed.
func (StaticKey) Encode ¶
Encode renders the key as the block "openvpn --genkey" writes, which ParseStaticKey reads back to the same bytes. The result is key material.
func (StaticKey) MarshalJSON ¶
MarshalJSON encodes the placeholder string. It does not decode back into a key.
type X509NameMatch ¶
type X509NameMatch int
X509NameMatch is the match type of --verify-x509-name: which part of the server certificate's subject the value is compared against.
const ( // X509NameSubject compares against the whole subject DN, OpenVPN's // default. X509NameSubject X509NameMatch = iota // X509NameCN compares against the common name, spelled "name". X509NameCN // X509NameCNPrefix requires the common name to start with the value, // spelled "name-prefix". X509NameCNPrefix )
func ParseX509NameMatch ¶
func ParseX509NameMatch(s string) (X509NameMatch, bool)
ParseX509NameMatch normalises the optional second argument of --verify-x509-name, case-insensitively. The empty string is "subject". An unrecognised type is refused, as OpenVPN refuses it.
func (X509NameMatch) MarshalText ¶
func (m X509NameMatch) MarshalText() ([]byte, error)
MarshalText implements encoding.TextMarshaler. An out-of-range value marshals as "unknown", which UnmarshalText refuses.
func (X509NameMatch) String ¶
func (m X509NameMatch) String() string
String returns the directive spelling of the match type.
func (*X509NameMatch) UnmarshalText ¶
func (m *X509NameMatch) UnmarshalText(text []byte) error
UnmarshalText implements encoding.TextUnmarshaler. An unrecognised name is an error.