diff --git a/server.conf.in b/server.conf.in index 69367aa5..3c757b54 100644 --- a/server.conf.in +++ b/server.conf.in @@ -108,14 +108,44 @@ enabled = false ; 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 ; 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 ; The shared secred for HMAC validation in "sharedsecret" mode. Best use 32 or ; 64 bytes of random data. ;sharedsecret_secret = some-secret-do-not-keep ; The HTTP header name where to find the userid in "httpheader" mode. ;httpheader_header = x-userid - -; The server can create new userids if enabled. Set allowRegistration to true to -; enable userid creation/registration. Users are created to match the settings +; Full path to PEM encoded private key to use for user creation in "certificate" +; mode. Keep this commented if you do not want the server to sign certificate +; 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). ;allowRegistration = false diff --git a/src/app/spreed-speakfreely-server/connection.go b/src/app/spreed-speakfreely-server/connection.go index d8191511..d50a254f 100644 --- a/src/app/spreed-speakfreely-server/connection.go +++ b/src/app/spreed-speakfreely-server/connection.go @@ -27,6 +27,7 @@ import ( "github.com/gorilla/websocket" "io" "log" + "net/http" "sync" "time" ) @@ -55,8 +56,9 @@ const ( type Connection struct { // References. - h *Hub - ws *websocket.Conn + h *Hub + ws *websocket.Conn + request *http.Request // Data handling. condition *sync.Cond @@ -72,15 +74,14 @@ type Connection struct { IsRegistered bool Hello bool 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{ - h: h, - ws: ws, - RemoteAddr: remoteAddr, + h: h, + ws: ws, + request: request, } c.condition = sync.NewCond(&c.mutex) @@ -112,7 +113,7 @@ func (c *Connection) close() { func (c *Connection) register() error { - s := c.h.CreateSession(nil) + s := c.h.CreateSession(c.request, nil) c.h.registerHandler(c, s) return nil } @@ -120,7 +121,7 @@ func (c *Connection) register() error { func (c *Connection) reregister(token string) error { 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) } else { log.Println("Error while decoding session token", err) diff --git a/src/app/spreed-speakfreely-server/hub.go b/src/app/spreed-speakfreely-server/hub.go index 96ba40c1..879f60db 100644 --- a/src/app/spreed-speakfreely-server/hub.go +++ b/src/app/spreed-speakfreely-server/hub.go @@ -32,6 +32,7 @@ import ( "fmt" "github.com/gorilla/securecookie" "log" + "net/http" "strings" "sync" "sync/atomic" @@ -81,6 +82,7 @@ type Hub struct { buddyImages ImageCache realm string tokenName string + useridRetriever func(*http.Request) (string, error) } 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, // random data in itself should be sufficent if we do not validate // session ids somewhere? var session *Session + var userid string + usersEnabled := h.config.UsersEnabled + + if usersEnabled && h.useridRetriever != nil { + userid, _ = h.useridRetriever(request) + } if st == nil { sid := NewRandomString(32) id, _ := h.tickets.Encode("id", sid) - session = NewSession(id, sid, "") - log.Println("Created new session id", len(id), id, sid) + session = NewSession(id, sid, userid) + log.Println("Created new session id", len(id), id, sid, userid) } else { - userid := st.Userid - if !h.config.UsersEnabled { + if userid == "" { + userid = st.Userid + } + if !usersEnabled { 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 { ec.IsRegistered = false 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.sessionTable[c.Id] = s //fmt.Println("registered", c.Id) 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) } diff --git a/src/app/spreed-speakfreely-server/users.go b/src/app/spreed-speakfreely-server/users.go index b86e7d25..0a27c0e5 100644 --- a/src/app/spreed-speakfreely-server/users.go +++ b/src/app/spreed-speakfreely-server/users.go @@ -48,6 +48,7 @@ var ( ) type UsersHandler interface { + Get(request *http.Request) (string, error) Validate(snr *SessionNonceRequest, request *http.Request) (string, 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) { // Parse UseridCombo. @@ -105,20 +110,20 @@ type UsersHTTPHeaderHandler struct { headerName string } -func (uh *UsersHTTPHeaderHandler) Validate(snr *SessionNonceRequest, request *http.Request) (string, error) { - - userid := request.Header.Get(uh.headerName) +func (uh *UsersHTTPHeaderHandler) Get(request *http.Request) (userid string, err error) { + userid = request.Header.Get(uh.headerName) 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) { - return nil, errors.New("create is not possible in httpheader mode") - } 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) { - return "", errors.New("certificate validation not implemented") + return uh.Get(request) } 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") if secret != "" { handler = &UsersSharedsecretHandler{secret: []byte(secret)} + } else { + log.Println("Cannot enable sharedsecret users handler: No secret.") } case "httpheader": headerName, _ := runtime.GetString("users", "httpheader_header") @@ -249,6 +273,7 @@ func NewUsers(hub *Hub, realm string, runtime phoenix.Runtime) *Users { handler = &UsersHTTPHeaderHandler{headerName: headerName} case "certificate": uh := &UsersCertificateHandler{} + // TODO(longsleep): Add validFor to configuration. uh.validFor = 24 * time.Hour * 365 keyFn, _ := runtime.GetString("users", "certificate_key") 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 { uh.certificate = certificates[0] log.Printf("Users certificate loaded from %s\n", certificateFn) + handler = uh } else { log.Printf("Failed to parse users certificate: %s\n", err) } } else { log.Printf("Failed to load users certificate: %s\n", err) } + } else { + log.Println("Cannot enable certificate users handler: No certificate.") } - handler = uh default: mode = "" } - if handler == nil { - handler = &UsersSharedsecretHandler{secret: []byte("")} - } + if handler != nil { + + // 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{ 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. 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 switch request.Header.Get("Content-Type") { diff --git a/src/app/spreed-speakfreely-server/ws.go b/src/app/spreed-speakfreely-server/ws.go index df8ab61c..0df83a76 100644 --- a/src/app/spreed-speakfreely-server/ws.go +++ b/src/app/spreed-speakfreely-server/ws.go @@ -71,15 +71,9 @@ func makeWsHubHandler(h *Hub) http.HandlerFunc { // Read request details. r.ParseForm() 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. - c := NewConnection(h, ws, remoteAddr) + c := NewConnection(h, ws, r) if token != "" { if err := c.reregister(token); err != nil { log.Println(err)