Browse Source

Implemented certificate pass through from SSL frontend proxy via HTTP headers.

pull/28/head
Simon Eisenmann 11 years ago
parent
commit
ca7e6d0332
  1. 22
      server.conf.in
  2. 93
      src/app/spreed-speakfreely-server/users.go

22
server.conf.in

@ -110,7 +110,7 @@ enabled = false @@ -110,7 +110,7 @@ enabled = 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
; 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
@ -125,31 +125,29 @@ enabled = false @@ -125,31 +125,29 @@ enabled = false
; 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
; 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 client authentication was
; successfull. The value of this header is matched to
; certificate_verifiedHeaderValue and only if there is a match, the proxy
; The HTTP header name where to find if the TLS client 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. Make sure to secure
; these headers with your front end proxy (always set them). Do not use these
; settings when not using a front end proxy.
;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.
; by a front end proxy. With Nginx the required value is in $ssl_client_cert.
;certificate_certificateHeader = x-certificate
; The valid duration of generated certificates created in certificate mode when
; allowRegistration is enabled.
;certificate_validForDays = 365
; Organization to set into the created user certificates. Use a readable public
; name to make the certificate easily recognizable as certificate for your
; server so users can choose the correct certificate when prompted.
;certificate_organization= = My Spreed Server
; 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).

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

@ -31,6 +31,7 @@ import ( @@ -31,6 +31,7 @@ import (
"crypto/x509/pkix"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"github.com/longsleep/pkac"
@ -128,9 +129,13 @@ func (uh *UsersHTTPHeaderHandler) Create(un *UserNonce, request *http.Request) ( @@ -128,9 +129,13 @@ func (uh *UsersHTTPHeaderHandler) Create(un *UserNonce, request *http.Request) (
}
type UsersCertificateHandler struct {
validFor time.Duration
privateKey crypto.PrivateKey
certificate *x509.Certificate
validFor time.Duration
privateKey crypto.PrivateKey
certificate *x509.Certificate
verifiedHeader string
verifiedHeaderValue string
certificateHeader string
organization []string
}
func (uh *UsersCertificateHandler) makeTemplate(commonName string) (*x509.Certificate, error) {
@ -146,7 +151,8 @@ func (uh *UsersCertificateHandler) makeTemplate(commonName string) (*x509.Certif @@ -146,7 +151,8 @@ func (uh *UsersCertificateHandler) makeTemplate(commonName string) (*x509.Certif
return &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: commonName,
CommonName: commonName,
Organization: uh.organization,
},
NotBefore: notBefore,
NotAfter: notAfter,
@ -158,19 +164,52 @@ func (uh *UsersCertificateHandler) makeTemplate(commonName string) (*x509.Certif @@ -158,19 +164,52 @@ func (uh *UsersCertificateHandler) makeTemplate(commonName string) (*x509.Certif
}
func (uh *UsersCertificateHandler) Get(request *http.Request) (userid string, err error) {
if request.TLS == nil || len(request.TLS.VerifiedChains) == 0 {
return
}
chain := request.TLS.VerifiedChains[0]
if len(chain) == 0 {
return
if uh.verifiedHeader != "" && uh.verifiedHeaderValue != "" {
// Use incoming HTTP headers.
if request.Header.Get(uh.verifiedHeader) != uh.verifiedHeaderValue {
// Verify header does not match - ignore incoming userid.
return
}
if uh.certificateHeader != "" {
// Read userid from certificate in header if configured.
var cert tls.Certificate
var certDERBlock *pem.Block
// Whuahah this is an evil fix to get back valid PEM data from Nginx $ssl_client_cert values.
certString := strings.Replace(request.Header.Get(uh.certificateHeader), " ", "\n", -1)
certString = strings.Replace(certString, "BEGIN\n", "BEGIN ", 1)
certString = strings.Replace(certString, "END\n", "END ", 1)
certPEMBlock := []byte(certString)
for {
certDERBlock, certPEMBlock = pem.Decode(certPEMBlock)
if certDERBlock == nil {
break
}
if certDERBlock.Type == "CERTIFICATE" {
cert.Certificate = append(cert.Certificate, certDERBlock.Bytes)
}
}
if len(cert.Certificate) == 0 {
err = errors.New("failed to parse certificate PEM data")
return
}
var certificates []*x509.Certificate
if certificates, err = x509.ParseCertificates(cert.Certificate[0]); err == nil {
userid = certificates[0].Subject.CommonName
}
}
} else {
// Direct TLS termination and authentication.
if request.TLS == nil || len(request.TLS.VerifiedChains) == 0 {
return
}
chain := request.TLS.VerifiedChains[0]
if len(chain) == 0 {
return
}
cert := chain[0]
userid = cert.Subject.CommonName
}
cert := chain[0]
userid = cert.Subject.CommonName
log.Printf("Client certificate found for user: %s\n", userid)
return
}
@ -268,8 +307,12 @@ func NewUsers(hub *Hub, mode, realm string, runtime phoenix.Runtime) *Users { @@ -268,8 +307,12 @@ func NewUsers(hub *Hub, mode, realm string, runtime phoenix.Runtime) *Users {
// Register handler Get at the hub.
users.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)
if err != nil {
log.Printf("Failed to get userid from handler: %s", err)
} else {
if userid != "" {
log.Printf("Users handler get success: %s\n", userid)
}
}
return
}
@ -299,12 +342,24 @@ func (users *Users) createHandler(mode string, runtime phoenix.Runtime) (handler @@ -299,12 +342,24 @@ func (users *Users) createHandler(mode string, runtime phoenix.Runtime) (handler
handler = &UsersHTTPHeaderHandler{headerName: headerName}
case "certificate":
var err2 error
uh := &UsersCertificateHandler{}
verifiedHeader, _ := runtime.GetString("users", "certificate_verifiedHeader")
verifiedHeaderValue, _ := runtime.GetString("users", "certificate_verifiedHeaderValue")
certificateHeader, _ := runtime.GetString("users", "certificate_certificateHeader")
validForDays, _ := runtime.GetInt("users", "certificate_validForDays")
if validForDays == 0 {
validForDays = 365
}
uh.validFor = time.Duration(validForDays) * 24 * time.Hour
organization, _ := runtime.GetString("users", "certificate_organization")
if organization == "" {
organization = "My Spreed Server"
}
uh := &UsersCertificateHandler{
verifiedHeader: verifiedHeader,
verifiedHeaderValue: verifiedHeaderValue,
certificateHeader: certificateHeader,
validFor: time.Duration(validForDays) * 24 * time.Hour,
organization: []string{organization},
}
keyFn, _ := runtime.GetString("users", "certificate_key")
certificateFn, _ := runtime.GetString("users", "certificate_certificate")
if keyFn != "" && certificateFn != "" {

Loading…
Cancel
Save