Skip to content
This repository has been archived by the owner on Mar 18, 2019. It is now read-only.

Commit

Permalink
Added TLS support.
Browse files Browse the repository at this point in the history
  • Loading branch information
sirsean committed Dec 8, 2015
1 parent e4e2942 commit 1283760
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 20 deletions.
55 changes: 55 additions & 0 deletions script/gen-certs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# To run this, make a virtualenv and pip install cryptography.
# If you're using virtualenvwrapper you can just mktmpenv

import datetime
import ipaddress
import uuid

from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID


one_day = datetime.timedelta(1, 0, 0)
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
builder = x509.CertificateBuilder()
builder = builder.subject_name(x509.Name([
x509.NameAttribute(NameOID.COMMON_NAME, u'127.0.0.1'),
]))
builder = builder.issuer_name(x509.Name([
x509.NameAttribute(NameOID.COMMON_NAME, u'127.0.0.1'),
]))
builder = builder.not_valid_before(datetime.datetime.today() - one_day)
builder = builder.not_valid_after(datetime.datetime(2018, 12, 31))
builder = builder.serial_number(int(uuid.uuid4()))
builder = builder.public_key(private_key.public_key())
builder = builder.add_extension(
x509.SubjectAlternativeName([
x509.IPAddress(ipaddress.IPv4Address(u'127.0.0.1'))
]),
critical=False,
)
builder = builder.add_extension(
x509.KeyUsage(True, False, True, False, True, True, True, False, False),
critical=False,
)
builder = builder.add_extension(
x509.BasicConstraints(ca=True, path_length=None),
critical=False,
)
certificate = builder.sign(
private_key=private_key, algorithm=hashes.SHA256(),
backend=default_backend()
)
print(certificate.public_bytes(serialization.Encoding.PEM))
print(private_key.private_bytes(
serialization.Encoding.PEM,
serialization.PrivateFormat.PKCS8,
serialization.NoEncryption()
))
69 changes: 50 additions & 19 deletions srslog.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package srslog

import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"log"
"net"
"os"
Expand Down Expand Up @@ -68,11 +71,12 @@ const (

// A Writer is a connection to a syslog server.
type Writer struct {
priority Priority
tag string
hostname string
network string
raddr string
priority Priority
tag string
hostname string
network string
raddr string
tlsConfig *tls.Config

mu sync.Mutex // guards conn
conn serverConn
Expand All @@ -85,7 +89,7 @@ type Writer struct {
// return a type that satisfies this interface and simply calls the C
// library syslog function.
type serverConn interface {
writeString(p Priority, hostname, tag, s, nl string) error
writeString(p Priority, hostname, tag, s string) error
close() error
}

Expand All @@ -107,6 +111,24 @@ func New(priority Priority, tag string) (w *Writer, err error) {
// tag.
// If network is empty, Dial will connect to the local syslog server.
func Dial(network, raddr string, priority Priority, tag string) (*Writer, error) {
return DialWithTLSConfig(network, raddr, priority, tag, nil)
}

func DialWithTLSCertPath(network, raddr string, priority Priority, tag, certPath string) (*Writer, error) {
pool := x509.NewCertPool()
serverCert, err := ioutil.ReadFile(certPath)
if err != nil {
return nil, err
}
pool.AppendCertsFromPEM(serverCert)
config := tls.Config{
RootCAs: pool,
}

return DialWithTLSConfig(network, raddr, priority, tag, &config)
}

func DialWithTLSConfig(network, raddr string, priority Priority, tag string, tlsConfig *tls.Config) (*Writer, error) {
if priority < 0 || priority > LOG_LOCAL7|LOG_DEBUG {
return nil, errors.New("log/syslog: invalid priority")
}
Expand All @@ -117,11 +139,12 @@ func Dial(network, raddr string, priority Priority, tag string) (*Writer, error)
hostname, _ := os.Hostname()

w := &Writer{
priority: priority,
tag: tag,
hostname: hostname,
network: network,
raddr: raddr,
priority: priority,
tag: tag,
hostname: hostname,
network: network,
raddr: raddr,
tlsConfig: tlsConfig,
}

w.mu.Lock()
Expand All @@ -148,6 +171,15 @@ func (w *Writer) connect() (err error) {
if w.hostname == "" {
w.hostname = "localhost"
}
} else if w.network == "tcp+tls" {
var c *tls.Conn
c, err = tls.Dial("tcp", w.raddr, w.tlsConfig)
if err == nil {
w.conn = &netConn{conn: c}
if w.hostname == "" {
w.hostname = c.LocalAddr().String()
}
}
} else {
var c net.Conn
c, err = net.Dial(w.network, w.raddr)
Expand Down Expand Up @@ -256,12 +288,11 @@ func (w *Writer) writeAndRetry(p Priority, s string) (int, error) {
// format is as follows: <PRI>TIMESTAMP HOSTNAME TAG[PID]: MSG
func (w *Writer) write(p Priority, msg string) (int, error) {
// ensure it ends in a \n
nl := ""
if !strings.HasSuffix(msg, "\n") {
nl = "\n"
msg += "\n"
}

err := w.conn.writeString(p, w.hostname, w.tag, msg, nl)
err := w.conn.writeString(p, w.hostname, w.tag, msg)
if err != nil {
return 0, err
}
Expand All @@ -271,21 +302,21 @@ func (w *Writer) write(p Priority, msg string) (int, error) {
return len(msg), nil
}

func (n *netConn) writeString(p Priority, hostname, tag, msg, nl string) error {
func (n *netConn) writeString(p Priority, hostname, tag, msg string) error {
if n.local {
// Compared to the network form below, the changes are:
// 1. Use time.Stamp instead of time.RFC3339.
// 2. Drop the hostname field from the Fprintf.
timestamp := time.Now().Format(time.Stamp)
_, err := fmt.Fprintf(n.conn, "<%d>%s %s[%d]: %s%s",
_, err := fmt.Fprintf(n.conn, "<%d>%s %s[%d]: %s",
p, timestamp,
tag, os.Getpid(), msg, nl)
tag, os.Getpid(), msg)
return err
}
timestamp := time.Now().Format(time.RFC3339)
_, err := fmt.Fprintf(n.conn, "<%d>%s %s %s[%d]: %s%s",
_, err := fmt.Fprintf(n.conn, "<%d>%s %s %s[%d]: %s",
p, timestamp, hostname,
tag, os.Getpid(), msg, nl)
tag, os.Getpid(), msg)
return err
}

Expand Down
72 changes: 71 additions & 1 deletion srslog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package srslog

import (
"bufio"
"crypto/tls"
"fmt"
"io"
"io/ioutil"
Expand Down Expand Up @@ -82,7 +83,7 @@ func runStreamSyslog(l net.Listener, done chan<- string, wg *sync.WaitGroup) {
}

func startServer(n, la string, done chan<- string) (addr string, sock io.Closer, wg *sync.WaitGroup) {
if n == "udp" || n == "tcp" {
if n == "udp" || n == "tcp" || n == "tcp+tls" {
la = "127.0.0.1:0"
} else {
// unix and unixgram: choose an address if none given
Expand Down Expand Up @@ -111,6 +112,23 @@ func startServer(n, la string, done chan<- string) (addr string, sock io.Closer,
defer wg.Done()
runPktSyslog(l, done)
}()
} else if n == "tcp+tls" {
cert, err := tls.LoadX509KeyPair("test/cert.pem", "test/privkey.pem")
if err != nil {
log.Fatalf("failed to load TLS keypair: %v", err)
}
config := tls.Config{Certificates: []tls.Certificate{cert}}
l, e := tls.Listen("tcp", la, &config)
if e != nil {
log.Fatalf("startServer failed: %v", e)
}
addr = l.Addr().String()
sock = l
wg.Add(1)
go func() {
defer wg.Done()
runStreamSyslog(l, done, wg)
}()
} else {
l, e := net.Listen(n, la)
if e != nil {
Expand Down Expand Up @@ -242,6 +260,16 @@ func TestDial(t *testing.T) {
l.Close()
}

func TestDialTLSFails(t *testing.T) {
w, err := DialWithTLSCertPath("tcp+tls", "127.0.0.1:0", LOG_ERR, "syslog_test", "test/nocertfound.pem")
if w != nil {
t.Fatalf("Should not have a writer")
}
if err == nil {
t.Fatalf("Should have failed to load the cert")
}
}

func check(t *testing.T, in, out string) {
tmpl := fmt.Sprintf("<%d>%%s %%s syslog_test[%%d]: %s\n", LOG_USER+LOG_INFO, in)
if hostname, err := os.Hostname(); err != nil {
Expand Down Expand Up @@ -296,6 +324,48 @@ func TestWrite(t *testing.T) {
}
}

func TestTLSWrite(t *testing.T) {
tests := []struct {
pri Priority
pre string
msg string
exp string
}{
{LOG_USER | LOG_ERR, "syslog_test", "", "%s %s syslog_test[%d]: \n"},
{LOG_USER | LOG_ERR, "syslog_test", "write test", "%s %s syslog_test[%d]: write test\n"},
// Write should not add \n if there already is one
{LOG_USER | LOG_ERR, "syslog_test", "write test 2\n", "%s %s syslog_test[%d]: write test 2\n"},
}

if hostname, err := os.Hostname(); err != nil {
t.Fatalf("Error retrieving hostname")
} else {
for _, test := range tests {
done := make(chan string)
addr, sock, srvWG := startServer("tcp+tls", "", done)
defer srvWG.Wait()
defer sock.Close()

l, err := DialWithTLSCertPath("tcp+tls", addr, test.pri, test.pre, "test/cert.pem")
if err != nil {
t.Fatalf("syslog.Dial() failed: %v", err)
}
defer l.Close()
_, err = io.WriteString(l, test.msg)
if err != nil {
t.Fatalf("WriteString() failed: %v", err)
}
rcvd := <-done
test.exp = fmt.Sprintf("<%d>", test.pri) + test.exp
var parsedHostname, timestamp string
var pid int
if n, err := fmt.Sscanf(rcvd, test.exp, &timestamp, &parsedHostname, &pid); n != 3 || err != nil || hostname != parsedHostname {
t.Errorf("s.Info() = '%q', didn't match '%q' (%d %s)", rcvd, test.exp, n, err)
}
}
}
}

func TestConcurrentWrite(t *testing.T) {
addr, sock, srvWG := startServer("udp", "", make(chan string, 1))
defer srvWG.Wait()
Expand Down
18 changes: 18 additions & 0 deletions test/cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC4DCCAcigAwIBAgIQK5t5Skr1T6GQQn6u9VSXZTANBgkqhkiG9w0BAQsFADAU
MRIwEAYDVQQDDAkxMjcuMC4wLjEwHhcNMTUxMjA3MTAxMTI0WhcNMTgxMjMxMDAw
MDAwWjAUMRIwEAYDVQQDDAkxMjcuMC4wLjEwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDr9UU6xYOCGQgmXOA3HH6ZW/xKATDzxn58nQWXy/1kqI84NFPb
9elpWUxU0D86xcU7YZDV1I79N00SaDTYyHAMLHi6WUt55nokLTRA15mxskRbYE8H
5ANKB1+fBDP1LLtGgIhfU2mkfxJpxtSkcSgmXIi0hstIGzGT98QIsSdUeCpZWF9s
KmajGV7wJtzpNcUO8BOh5sd5s37K2A5C7w9HZvINPV+/1FDP5GalOhg94eprfV0y
78z/+Sxqr7wc2rTw27cjf5waTgQYgEbzBowMQJP1ojI646vYBrg3TEX7tjnC7f8E
+Y7xe614c0Bj8XhJZbNV0MIG8Ka49HlUTcejAgMBAAGjLjAsMA8GA1UdEQQIMAaH
BH8AAAEwCwYDVR0PBAQDAgGuMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQAD
ggEBAOdc2KSjnQfd8ZS2nAO9opo9D82q+03LrC6ZGun5/NvAazUS+uJ5wyTpx9A5
45s3Cib7eCrlHsB7UbslUm33+VVeSmj+VCBYkW5iY6kLth007pVonmxCi/hF37dm
y8XoSifJA7CdPRptI8LAHn/H34Awl43vaGHQDLaaCgFBDTtKWxjvdKS2aSvXWvjC
81k+i5mb1OiJSoioLSgtX94txde/gDI87yrKWNLBQ7MqPzSg8DtRXftUeaLgcKlQ
KjYg0wx90HUwpi5Hv2E1Q09LmecyHuquYpve364xlK1lQjEdkaGn2nNCPUHOiHT7
1BAIPcvnMumWv7uBYAW5nYCaeRw=
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions test/privkey.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDr9UU6xYOCGQgm
XOA3HH6ZW/xKATDzxn58nQWXy/1kqI84NFPb9elpWUxU0D86xcU7YZDV1I79N00S
aDTYyHAMLHi6WUt55nokLTRA15mxskRbYE8H5ANKB1+fBDP1LLtGgIhfU2mkfxJp
xtSkcSgmXIi0hstIGzGT98QIsSdUeCpZWF9sKmajGV7wJtzpNcUO8BOh5sd5s37K
2A5C7w9HZvINPV+/1FDP5GalOhg94eprfV0y78z/+Sxqr7wc2rTw27cjf5waTgQY
gEbzBowMQJP1ojI646vYBrg3TEX7tjnC7f8E+Y7xe614c0Bj8XhJZbNV0MIG8Ka4
9HlUTcejAgMBAAECggEBAMHtYIuwH6iCOD+HX8QLyET05AJSvk/smLKEPz+GKWlc
W/FemHmUv9SUzvZ5/S2ps7NdObN0slyM4ew59w0gl2558nN9xlmWwlYPTP3p9Oil
0iajnfCnRsjGDKHdy3I65GRKaUqnfJD020ZSYxwP4Ga+8KAmlNZbe0DYhqZ6Kw/w
6wwYNvxptjd85Q+jJGj9nyUxL+68LROYNcZga4PQIqNvF1yOvCMIyGKSptfmLRDg
nXAs5vJHPNXdZ5+PC1MmiP2wxC3hZncFVTACoW35++CC6CLmg2OfTOyFZ9FMEQ57
oI99Ed7bjanEioMj8fSk49qIIdFix09ArLa+Rid8fTECgYEA+1w65b5Oq4YU0zXE
C23bgy3D/gZ+vhxCuRU8NLrIKsyFIZuCcA7YU+U/D/ngI52xLEcvSp/NAH/xKHh8
Lop0cKA7oysMxMM4BtEC5N+SquRJKsgg+V36DDNGja4D/4HZ9IlLzTGHL51FI4hd
o489YqgpG0YdCpg5GfoWgQTufIkCgYEA8FBCZQjlFg/cifUewNPQ9mzOz2TyxFTp
olwsqJTnxLgXBJ4N6rdc61bLrWowv7yBHG7/kyMY6s7oWZZCk8EcU682P4DI3X0B
MxuaccMYWc7ttRJEpXWArxiDVCc2sDHVUR23pt0tsSRhQXHlO8HE3679yfG6YGxA
F9WRBct9D8sCgYA6DXMM3IcO1ki4/xHoEddA1LEPWjCrd5txY5YkF39jYxjcSi41
8zfDKI8IAY3iq+jfcRFbCs0t8F6iGjGUDiYWXOtpI+gvCWdHK76fXYNiNJcxakcz
UKEPcEg7MJV7zWGpOIxpN6chOBFfw37c55gl0PCte+P5Lm8BsODBq4HpAQKBgB2Q
0kpZ6M1pECoM9UamCLx4sI0Fj3SmOcRW8Mug3k7uky5nP7ET9COkHxTrzqmYSI41
/c2dcNBaum1jNje1d4W4NcVkU9IkMgSWrc63QQSzl71CTR3KMhXYvzeYR3sv9l2v
eUvXRGrZ3flOSPSsJ0uZ3PF+gv6f8ta72MbMvUs3AoGBAPTkm1N56EI4KZIO+uq8
lh094Lytty1MjfhgVf1sRzE+kPxzyo1g2LpnWQ43UZMD5bQ7fR3rr9ofCzPa9OlX
IRXUv4mNFzzDmEZwrrt1TA/EjkosdtqcCg0syvBUEBCkYTdvSxaDCw80sS0DVlEH
gqIra7+MAgb8y+m47tZyO7KZ
-----END PRIVATE KEY-----

0 comments on commit 1283760

Please sign in to comment.