Browse Source

TLS client side auth now even works.

pull/28/head
Simon Eisenmann 11 years ago
parent
commit
eb13945be8
  1. 36
      server.conf.in
  2. 19
      src/app/spreed-speakfreely-server/connection.go
  3. 24
      src/app/spreed-speakfreely-server/hub.go
  4. 66
      src/app/spreed-speakfreely-server/users.go
  5. 8
      src/app/spreed-speakfreely-server/ws.go

36
server.conf.in

@ -108,14 +108,44 @@ enabled = false
; validation. This usually only makes sense with a front end HTTPS proxy which ; validation. This usually only makes sense with a front end HTTPS proxy which
; does the authentication and injects the user id as HTTP header for sessions ; does the authentication and injects the user id as HTTP header for sessions
; REST requests. In mode httpheader, allowRegistration should be false. ; REST requests. In mode httpheader, allowRegistration should be false.
; certificate:
; The userid is provided as CommonName with a certificate provided with TLS
; client authentication. When you use this with a front end proxy for TLS
; termination, that proxy has to validate the certificate and inject certain
; headers into the proxy connection. Furthermode in certificate mode the
; server can act as a CA to sign incoming user requests with a private key
; when allowRegistration is true.
;mode = sharedsecret ;mode = sharedsecret
; The shared secred for HMAC validation in "sharedsecret" mode. Best use 32 or ; The shared secred for HMAC validation in "sharedsecret" mode. Best use 32 or
; 64 bytes of random data. ; 64 bytes of random data.
;sharedsecret_secret = some-secret-do-not-keep ;sharedsecret_secret = some-secret-do-not-keep
; The HTTP header name where to find the userid in "httpheader" mode. ; The HTTP header name where to find the userid in "httpheader" mode.
;httpheader_header = x-userid ;httpheader_header = x-userid
; Full path to PEM encoded private key to use for user creation in "certificate"
; The server can create new userids if enabled. Set allowRegistration to true to ; mode. Keep this commented if you do not want the server to sign certificate
; enable userid creation/registration. Users are created to match the settings ; requests.
;certificate_key = userskey.key
; Full path to PEM encoded certificate to use for user validation in
; "certificate" mode. When allowRegistration is true and certificate_key is also
; set then the server will act as a CA and sign incoming user registrations and
; return certificates to users as registration.
;certificate_certificate = usersca.crt
; The HTTP header name where to find if the TLS lient authentication was
; successfull. The value of this header is matched to
; certificate_verifiedHeaderValue and only if there is a match, the proxy
; handled TLS client authentication is accepted as success.
;certificate_verifiedHeader = x-verified
;certificate_verifiedHeaderValue = SUCCESS
; The HTTP header name where to find the userid extracted by a proxy after
; TLS client authentication. If not set certificate_certificateHeader will be
; used to retrieve the userid from the CommonName field of the certificate.
;certificate_useridHeader = x-userid
; The HTTP header name where to find the PEM encoded certificate authenticated
; by a front end proxy. This configuration this is optional if you have set a
; certificate_useridHeader value and the front end TLS proxy does inject the
; userid into that header.
;certificate_certificateHeader = x-certificate
; If enabled the server can create new userids. Set allowRegistration to true to
; enable userid creation/registration. Users are created according the settings
; of the currently configured mode (see above). ; of the currently configured mode (see above).
;allowRegistration = false ;allowRegistration = false

19
src/app/spreed-speakfreely-server/connection.go

@ -27,6 +27,7 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"io" "io"
"log" "log"
"net/http"
"sync" "sync"
"time" "time"
) )
@ -55,8 +56,9 @@ const (
type Connection struct { type Connection struct {
// References. // References.
h *Hub h *Hub
ws *websocket.Conn ws *websocket.Conn
request *http.Request
// Data handling. // Data handling.
condition *sync.Cond condition *sync.Cond
@ -72,15 +74,14 @@ type Connection struct {
IsRegistered bool IsRegistered bool
Hello bool Hello bool
Version string Version string
RemoteAddr string
} }
func NewConnection(h *Hub, ws *websocket.Conn, remoteAddr string) *Connection { func NewConnection(h *Hub, ws *websocket.Conn, request *http.Request) *Connection {
c := &Connection{ c := &Connection{
h: h, h: h,
ws: ws, ws: ws,
RemoteAddr: remoteAddr, request: request,
} }
c.condition = sync.NewCond(&c.mutex) c.condition = sync.NewCond(&c.mutex)
@ -112,7 +113,7 @@ func (c *Connection) close() {
func (c *Connection) register() error { func (c *Connection) register() error {
s := c.h.CreateSession(nil) s := c.h.CreateSession(c.request, nil)
c.h.registerHandler(c, s) c.h.registerHandler(c, s)
return nil return nil
} }
@ -120,7 +121,7 @@ func (c *Connection) register() error {
func (c *Connection) reregister(token string) error { func (c *Connection) reregister(token string) error {
if st, err := c.h.DecodeSessionToken(token); err == nil { if st, err := c.h.DecodeSessionToken(token); err == nil {
s := c.h.CreateSession(st) s := c.h.CreateSession(c.request, st)
c.h.registerHandler(c, s) c.h.registerHandler(c, s)
} else { } else {
log.Println("Error while decoding session token", err) log.Println("Error while decoding session token", err)

24
src/app/spreed-speakfreely-server/hub.go

@ -32,6 +32,7 @@ import (
"fmt" "fmt"
"github.com/gorilla/securecookie" "github.com/gorilla/securecookie"
"log" "log"
"net/http"
"strings" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -81,6 +82,7 @@ type Hub struct {
buddyImages ImageCache buddyImages ImageCache
realm string realm string
tokenName string tokenName string
useridRetriever func(*http.Request) (string, error)
} }
func NewHub(version string, config *Config, sessionSecret, turnSecret, realm string) *Hub { func NewHub(version string, config *Config, sessionSecret, turnSecret, realm string) *Hub {
@ -164,22 +166,30 @@ func (h *Hub) CreateTurnData(id string) *DataTurn {
} }
func (h *Hub) CreateSession(st *SessionToken) *Session { func (h *Hub) CreateSession(request *http.Request, st *SessionToken) *Session {
// NOTE(longsleep): Is it required to make this a secure cookie, // NOTE(longsleep): Is it required to make this a secure cookie,
// random data in itself should be sufficent if we do not validate // random data in itself should be sufficent if we do not validate
// session ids somewhere? // session ids somewhere?
var session *Session var session *Session
var userid string
usersEnabled := h.config.UsersEnabled
if usersEnabled && h.useridRetriever != nil {
userid, _ = h.useridRetriever(request)
}
if st == nil { if st == nil {
sid := NewRandomString(32) sid := NewRandomString(32)
id, _ := h.tickets.Encode("id", sid) id, _ := h.tickets.Encode("id", sid)
session = NewSession(id, sid, "") session = NewSession(id, sid, userid)
log.Println("Created new session id", len(id), id, sid) log.Println("Created new session id", len(id), id, sid, userid)
} else { } else {
userid := st.Userid if userid == "" {
if !h.config.UsersEnabled { userid = st.Userid
}
if !usersEnabled {
userid = "" userid = ""
} }
session = NewSession(st.Id, st.Sid, userid) session = NewSession(st.Id, st.Sid, userid)
@ -307,14 +317,14 @@ func (h *Hub) registerHandler(c *Connection, s *Session) {
if ec, ok := h.connectionTable[c.Id]; ok { if ec, ok := h.connectionTable[c.Id]; ok {
ec.IsRegistered = false ec.IsRegistered = false
ec.close() ec.close()
//log.Printf("Register (%d) from %s: %s (existing)\n", c.Idx, c.RemoteAddr, c.Id) //log.Printf("Register (%d) from %s: %s (existing)\n", c.Idx, c.Id)
} }
h.connectionTable[c.Id] = c h.connectionTable[c.Id] = c
h.sessionTable[c.Id] = s h.sessionTable[c.Id] = s
//fmt.Println("registered", c.Id) //fmt.Println("registered", c.Id)
h.mutex.Unlock() h.mutex.Unlock()
//log.Printf("Register (%d) from %s: %s\n", c.Idx, c.RemoteAddr, c.Id) //log.Printf("Register (%d) from %s: %s\n", c.Idx, c.Id)
h.server.OnRegister(c) h.server.OnRegister(c)
} }

66
src/app/spreed-speakfreely-server/users.go

@ -48,6 +48,7 @@ var (
) )
type UsersHandler interface { type UsersHandler interface {
Get(request *http.Request) (string, error)
Validate(snr *SessionNonceRequest, request *http.Request) (string, error) Validate(snr *SessionNonceRequest, request *http.Request) (string, error)
Create(snr *UserNonce, request *http.Request) (*UserNonce, error) Create(snr *UserNonce, request *http.Request) (*UserNonce, error)
} }
@ -64,6 +65,10 @@ func (uh *UsersSharedsecretHandler) createHMAC(useridCombo string) string {
} }
func (uh *UsersSharedsecretHandler) Get(request *http.Request) (userid string, err error) {
return
}
func (uh *UsersSharedsecretHandler) Validate(snr *SessionNonceRequest, request *http.Request) (string, error) { func (uh *UsersSharedsecretHandler) Validate(snr *SessionNonceRequest, request *http.Request) (string, error) {
// Parse UseridCombo. // Parse UseridCombo.
@ -105,20 +110,20 @@ type UsersHTTPHeaderHandler struct {
headerName string headerName string
} }
func (uh *UsersHTTPHeaderHandler) Validate(snr *SessionNonceRequest, request *http.Request) (string, error) { func (uh *UsersHTTPHeaderHandler) Get(request *http.Request) (userid string, err error) {
userid = request.Header.Get(uh.headerName)
userid := request.Header.Get(uh.headerName)
if userid == "" { if userid == "" {
return "", errors.New("no userid provided") err = errors.New("no userid provided")
} }
return userid, nil return
}
func (uh *UsersHTTPHeaderHandler) Validate(snr *SessionNonceRequest, request *http.Request) (string, error) {
return uh.Get(request)
} }
func (uh *UsersHTTPHeaderHandler) Create(un *UserNonce, request *http.Request) (*UserNonce, error) { func (uh *UsersHTTPHeaderHandler) Create(un *UserNonce, request *http.Request) (*UserNonce, error) {
return nil, errors.New("create is not possible in httpheader mode") return nil, errors.New("create is not possible in httpheader mode")
} }
type UsersCertificateHandler struct { type UsersCertificateHandler struct {
@ -151,8 +156,25 @@ func (uh *UsersCertificateHandler) makeTemplate(commonName string) (*x509.Certif
} }
func (uh *UsersCertificateHandler) Get(request *http.Request) (userid string, err error) {
if len(request.TLS.VerifiedChains) == 0 {
return
}
chain := request.TLS.VerifiedChains[0]
if len(chain) == 0 {
return
}
cert := chain[0]
userid = cert.Subject.CommonName
log.Printf("Client certificate found for user: %s\n", userid)
return
}
func (uh *UsersCertificateHandler) Validate(snr *SessionNonceRequest, request *http.Request) (string, error) { func (uh *UsersCertificateHandler) Validate(snr *SessionNonceRequest, request *http.Request) (string, error) {
return "", errors.New("certificate validation not implemented") return uh.Get(request)
} }
func (uh *UsersCertificateHandler) Create(un *UserNonce, request *http.Request) (*UserNonce, error) { func (uh *UsersCertificateHandler) Create(un *UserNonce, request *http.Request) (*UserNonce, error) {
@ -240,6 +262,8 @@ func NewUsers(hub *Hub, realm string, runtime phoenix.Runtime) *Users {
secret, _ := runtime.GetString("users", "sharedsecret_secret") secret, _ := runtime.GetString("users", "sharedsecret_secret")
if secret != "" { if secret != "" {
handler = &UsersSharedsecretHandler{secret: []byte(secret)} handler = &UsersSharedsecretHandler{secret: []byte(secret)}
} else {
log.Println("Cannot enable sharedsecret users handler: No secret.")
} }
case "httpheader": case "httpheader":
headerName, _ := runtime.GetString("users", "httpheader_header") headerName, _ := runtime.GetString("users", "httpheader_header")
@ -249,6 +273,7 @@ func NewUsers(hub *Hub, realm string, runtime phoenix.Runtime) *Users {
handler = &UsersHTTPHeaderHandler{headerName: headerName} handler = &UsersHTTPHeaderHandler{headerName: headerName}
case "certificate": case "certificate":
uh := &UsersCertificateHandler{} uh := &UsersCertificateHandler{}
// TODO(longsleep): Add validFor to configuration.
uh.validFor = 24 * time.Hour * 365 uh.validFor = 24 * time.Hour * 365
keyFn, _ := runtime.GetString("users", "certificate_key") keyFn, _ := runtime.GetString("users", "certificate_key")
certificateFn, _ := runtime.GetString("users", "certificate_certificate") certificateFn, _ := runtime.GetString("users", "certificate_certificate")
@ -264,23 +289,34 @@ func NewUsers(hub *Hub, realm string, runtime phoenix.Runtime) *Users {
if certificates, err := x509.ParseCertificates(certificate.Certificate[0]); err == nil { if certificates, err := x509.ParseCertificates(certificate.Certificate[0]); err == nil {
uh.certificate = certificates[0] uh.certificate = certificates[0]
log.Printf("Users certificate loaded from %s\n", certificateFn) log.Printf("Users certificate loaded from %s\n", certificateFn)
handler = uh
} else { } else {
log.Printf("Failed to parse users certificate: %s\n", err) log.Printf("Failed to parse users certificate: %s\n", err)
} }
} else { } else {
log.Printf("Failed to load users certificate: %s\n", err) log.Printf("Failed to load users certificate: %s\n", err)
} }
} else {
log.Println("Cannot enable certificate users handler: No certificate.")
} }
handler = uh
default: default:
mode = "" mode = ""
} }
if handler == nil { if handler != nil {
handler = &UsersSharedsecretHandler{secret: []byte("")}
} // Register handler Get at the hub.
hub.useridRetriever = func(request *http.Request) (userid string, err error) {
userid, err = handler.Get(request)
if userid != "" {
log.Printf("Users handler get success: %s\n", userid)
}
return
}
log.Printf("Enabled users handler '%s'\n", mode)
log.Printf("Enabled users handler '%s'\n", mode) }
return &Users{ return &Users{
hub: hub, hub: hub,
@ -293,6 +329,10 @@ func NewUsers(hub *Hub, realm string, runtime phoenix.Runtime) *Users {
// Post is used to create new userids for this server. // Post is used to create new userids for this server.
func (users *Users) Post(request *http.Request) (int, interface{}, http.Header) { func (users *Users) Post(request *http.Request) (int, interface{}, http.Header) {
if users.handler == nil {
return 404, "No handler found", http.Header{"Content-Type": {"text/plain"}}
}
var snr *SessionNonceRequest var snr *SessionNonceRequest
switch request.Header.Get("Content-Type") { switch request.Header.Get("Content-Type") {

8
src/app/spreed-speakfreely-server/ws.go

@ -71,15 +71,9 @@ func makeWsHubHandler(h *Hub) http.HandlerFunc {
// Read request details. // Read request details.
r.ParseForm() r.ParseForm()
token := r.FormValue("t") token := r.FormValue("t")
remoteAddr := r.RemoteAddr
if remoteAddr == "@" || remoteAddr == "127.0.0.1" {
if r.Header["X-Forwarded-For"][0] != "" {
remoteAddr = r.Header["X-Forwarded-For"][0]
}
}
// Create a new connection instance. // Create a new connection instance.
c := NewConnection(h, ws, remoteAddr) c := NewConnection(h, ws, r)
if token != "" { if token != "" {
if err := c.reregister(token); err != nil { if err := c.reregister(token); err != nil {
log.Println(err) log.Println(err)

Loading…
Cancel
Save