diff --git a/go.mod b/go.mod index 9ff520d..e505a83 100644 --- a/go.mod +++ b/go.mod @@ -16,11 +16,11 @@ require ( github.com/stretchr/testify v1.7.0 github.com/thomasdarimont/go-kc-example v0.0.0-20170529223628-e3951d8faa4c github.com/tklauser/go-sysconf v0.3.9 // indirect - github.com/vkuznet/auth-proxy-server/auth v0.0.0-20220111151419-9dc8dfa65541 - github.com/vkuznet/auth-proxy-server/cric v0.0.0-20220111151419-9dc8dfa65541 - github.com/vkuznet/auth-proxy-server/logging v0.0.0-20220111151419-9dc8dfa65541 + github.com/vkuznet/auth-proxy-server/auth v0.0.0-20220113115019-b39467ff0a7e + github.com/vkuznet/auth-proxy-server/cric v0.0.0-20220113115019-b39467ff0a7e + github.com/vkuznet/auth-proxy-server/logging v0.0.0-20220113115019-b39467ff0a7e github.com/vkuznet/x509proxy v0.0.0-20210801171832-e47b94db99b6 // indirect - golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 + golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce golang.org/x/net v0.0.0-20220111093109-d55c255bac03 // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 golang.org/x/sys v0.0.0-20220111092808-5a964db01320 // indirect diff --git a/go.sum b/go.sum index 255679b..302abd7 100644 --- a/go.sum +++ b/go.sum @@ -176,6 +176,8 @@ github.com/vkuznet/auth-proxy-server/auth v0.0.0-20211108132053-61a3126f4984 h1: github.com/vkuznet/auth-proxy-server/auth v0.0.0-20211108132053-61a3126f4984/go.mod h1:jr+4XDinkJS6ufG/VQxuKwLaQWyiqMq86ybOj09zHlA= github.com/vkuznet/auth-proxy-server/auth v0.0.0-20220111151419-9dc8dfa65541 h1:K4Gn5mj8eqy7bmSaOakfGwNdSffnHGj5G1UKeCdsvFs= github.com/vkuznet/auth-proxy-server/auth v0.0.0-20220111151419-9dc8dfa65541/go.mod h1:jr+4XDinkJS6ufG/VQxuKwLaQWyiqMq86ybOj09zHlA= +github.com/vkuznet/auth-proxy-server/auth v0.0.0-20220113115019-b39467ff0a7e h1:F4OpV48tAuX5uwG+6Es6gXMjxpKXvohge7n2yq5jdr0= +github.com/vkuznet/auth-proxy-server/auth v0.0.0-20220113115019-b39467ff0a7e/go.mod h1:jr+4XDinkJS6ufG/VQxuKwLaQWyiqMq86ybOj09zHlA= github.com/vkuznet/auth-proxy-server/cric v0.0.0-20210921141219-8a5ffd672a0d h1:mAa9co5Sb5lN796GkRu188c8Jm1vgJshUOWrHnHOb40= github.com/vkuznet/auth-proxy-server/cric v0.0.0-20210921141219-8a5ffd672a0d/go.mod h1:oZHYrCYGRR2XUoByGkZwu7jNBtHsgznYvDutJBhttLM= github.com/vkuznet/auth-proxy-server/cric v0.0.0-20210927152403-bd73fdada09e h1:e82InqQcdR3BuioCfDEMjj0k2dkwjU1lV4p9bgV4ev0= @@ -188,6 +190,8 @@ github.com/vkuznet/auth-proxy-server/cric v0.0.0-20211108132053-61a3126f4984 h1: github.com/vkuznet/auth-proxy-server/cric v0.0.0-20211108132053-61a3126f4984/go.mod h1:oZHYrCYGRR2XUoByGkZwu7jNBtHsgznYvDutJBhttLM= github.com/vkuznet/auth-proxy-server/cric v0.0.0-20220111151419-9dc8dfa65541 h1:HoXwe/xXKdzgQXttHhOwkfS/t0MpHCy+PytzVcQ7OP0= github.com/vkuznet/auth-proxy-server/cric v0.0.0-20220111151419-9dc8dfa65541/go.mod h1:oZHYrCYGRR2XUoByGkZwu7jNBtHsgznYvDutJBhttLM= +github.com/vkuznet/auth-proxy-server/cric v0.0.0-20220113115019-b39467ff0a7e h1:6QdY2wo01Lm8yD6yU8mSpIgD45WZIAdT/NxPW8Hixjo= +github.com/vkuznet/auth-proxy-server/cric v0.0.0-20220113115019-b39467ff0a7e/go.mod h1:yP3Sl6/oZgNaQ9BA36+NPWn7rEt7CA9kd6fDfUgkgNc= github.com/vkuznet/auth-proxy-server/logging v0.0.0-20210921141219-8a5ffd672a0d h1:RB2r6z9x28G2rBdQvU8cpu7V95WdgNj9S8+UzxLFXBU= github.com/vkuznet/auth-proxy-server/logging v0.0.0-20210921141219-8a5ffd672a0d/go.mod h1:rEHBafUXWUWp8o4zkvTooUDTHDhV4oj4trnO1fI2l4k= github.com/vkuznet/auth-proxy-server/logging v0.0.0-20210927152403-bd73fdada09e h1:e+WMNegDbquc8CNvOyEYZ+y/VGRjIE59hHNidHMWN/s= @@ -200,6 +204,8 @@ github.com/vkuznet/auth-proxy-server/logging v0.0.0-20211108132053-61a3126f4984 github.com/vkuznet/auth-proxy-server/logging v0.0.0-20211108132053-61a3126f4984/go.mod h1:rEHBafUXWUWp8o4zkvTooUDTHDhV4oj4trnO1fI2l4k= github.com/vkuznet/auth-proxy-server/logging v0.0.0-20220111151419-9dc8dfa65541 h1:QlLt9FvK6LxckXOYlqkcGEUobnoUoLEomes1u7psvXs= github.com/vkuznet/auth-proxy-server/logging v0.0.0-20220111151419-9dc8dfa65541/go.mod h1:rEHBafUXWUWp8o4zkvTooUDTHDhV4oj4trnO1fI2l4k= +github.com/vkuznet/auth-proxy-server/logging v0.0.0-20220113115019-b39467ff0a7e h1:k9bd+sFMDiRzEoPNmF7/mKKtWIP8QBPPBsSKMTOBY48= +github.com/vkuznet/auth-proxy-server/logging v0.0.0-20220113115019-b39467ff0a7e/go.mod h1:rEHBafUXWUWp8o4zkvTooUDTHDhV4oj4trnO1fI2l4k= github.com/vkuznet/x509proxy v0.0.0-20210801171832-e47b94db99b6 h1:Y5LCuH9nfTZ6srI5NaoKKbcDb01zqTHw8678++4fw0c= github.com/vkuznet/x509proxy v0.0.0-20210801171832-e47b94db99b6/go.mod h1:gfEPE3azFe+K/nMLezta3+kTiumttEYDawGAE72IYfM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -225,6 +231,8 @@ golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSE golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI= +golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -495,6 +503,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main.go b/main.go index 5616fa5..d1d99c0 100644 --- a/main.go +++ b/main.go @@ -340,6 +340,9 @@ func main() { log.Printf("%+v\n", Config) } + // read RootCAs once + _rootCAs = RootCAs() + // setup StartTime and metrics last update time StartTime = time.Now() MetricsLastUpdateTime = time.Now() diff --git a/oauth.go b/oauth.go index 5ee5158..1804392 100644 --- a/oauth.go +++ b/oauth.go @@ -641,6 +641,7 @@ func oauthProxyServer() { // start HTTPs server if Config.LetsEncrypt { server := LetsEncryptServer(Config.DomainNames...) + log.Println("Start OAuth HTTPs server with LetsEncrypt", Config.DomainNames) log.Fatal(server.ListenAndServeTLS("", "")) } else { // check if provided crt/key files exists diff --git a/utils.go b/utils.go index 60654c0..6ac9929 100644 --- a/utils.go +++ b/utils.go @@ -67,14 +67,14 @@ func printHTTPRequest(r *http.Request, msg string) { log.Printf("\n\nFinding value of \"Accept\" %q\n", r.Header["Accept"]) } -// helper function to construct http server with TLS -func getServer(serverCrt, serverKey string, customVerify bool) (*http.Server, error) { - // start HTTP or HTTPs server based on provided configuration +// RootCAs returns cert pool of our root CAs +func RootCAs() *x509.CertPool { + log.Println("Load RootCAs from", Config.RootCAs) rootCAs := x509.NewCertPool() files, err := ioutil.ReadDir(Config.RootCAs) if err != nil { log.Printf("Unable to list files in '%s', error: %v\n", Config.RootCAs, err) - return nil, err + return rootCAs } for _, finfo := range files { fname := fmt.Sprintf("%s/%s", Config.RootCAs, finfo.Name()) @@ -93,6 +93,84 @@ func getServer(serverCrt, serverKey string, customVerify bool) (*http.Server, er log.Println("Load CA file", fname) } } + return rootCAs +} + +// global rootCAs +var _rootCAs *x509.CertPool + +// VerifyPeerCertificate function provides custom verification of client's +// certificate, see details +// https://golang.org/pkg/crypto/tls/#example_Config_verifyPeerCertificate +// https://www.example-code.com/golang/cert.asp +// https://golang.org/pkg/crypto/x509/pkix/#Extension +func VerifyPeerCertificate(certificates [][]byte, _ [][]*x509.Certificate) error { + if Config.Verbose > 1 { + log.Println("call custom tlsConfig.VerifyPeerCertificate") + } + certs := make([]*x509.Certificate, len(certificates)) + for i, asn1Data := range certificates { + cert, err := x509.ParseCertificate(asn1Data) + if err != nil { + return errors.New("tls: failed to parse certificate from server: " + err.Error()) + } + if Config.Verbose > 1 { + log.Println("Issuer", cert.Issuer) + log.Println("Subject", cert.Subject) + log.Println("emails", cert.EmailAddresses) + } + // check validity of user certificate + tstamp := time.Now().Unix() + if cert.NotBefore.Unix() > tstamp || cert.NotAfter.Unix() < tstamp { + msg := fmt.Sprintf("Expired user certificate, valid from %v to %v\n", cert.NotBefore, cert.NotAfter) + return errors.New(msg) + } + // dump cert UnhandledCriticalExtensions + for _, ext := range cert.UnhandledCriticalExtensions { + if Config.Verbose > 1 { + log.Printf("Cetificate extension: %+v\n", ext) + } + continue + } + if len(cert.UnhandledCriticalExtensions) == 0 && cert != nil { + certs[i] = cert + } + } + if Config.Verbose > 1 { + log.Println("### number of certs", len(certs)) + for _, cert := range certs { + if cert != nil { + log.Printf("issuer %v subject %v valid from %v till %v\n", cert.Issuer, cert.Subject, cert.NotBefore, cert.NotAfter) + } + } + } + opts := x509.VerifyOptions{ + Roots: _rootCAs, + Intermediates: x509.NewCertPool(), + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + } + if len(certs) > 0 && certs[0] != nil { + for _, cert := range certs[1:] { + opts.Intermediates.AddCert(cert) + } + _, err := certs[0].Verify(opts) + return err + } + for _, cert := range certs { + if cert == nil { + continue + } + _, err := cert.Verify(opts) + if err != nil { + return err + } + } + return nil +} + +// helper function to construct http server with TLS +func getServer(serverCrt, serverKey string, customVerify bool) (*http.Server, error) { + // start HTTP or HTTPs server based on provided configuration var tlsConfig *tls.Config // see go doc tls.VersionTLS13 for different versions @@ -115,7 +193,7 @@ func getServer(serverCrt, serverKey string, customVerify bool) (*http.Server, er } else if Config.MaxTLSVersion == "tls13" { maxVer = tls.VersionTLS13 } - log.Println("set tlsConfig with min version", minVer) + log.Printf("set tlsConfig with min=%d max=%d versions", minVer, maxVer) // if we do not require custom verification we'll load server crt/key and present to client if customVerify == false { cert, err := tls.LoadX509KeyPair(serverCrt, serverKey) @@ -126,7 +204,7 @@ func getServer(serverCrt, serverKey string, customVerify bool) (*http.Server, er tlsConfig = &tls.Config{ MinVersion: uint16(minVer), MaxVersion: uint16(maxVer), - RootCAs: rootCAs, + RootCAs: _rootCAs, Certificates: []tls.Certificate{cert}, } } else { // otherwise we'll perform custom verification of client's certificates @@ -137,75 +215,9 @@ func getServer(serverCrt, serverKey string, customVerify bool) (*http.Server, er MaxVersion: uint16(maxVer), InsecureSkipVerify: true, ClientAuth: tls.RequestClientCert, - RootCAs: rootCAs, - } - // see concrete example here: - // https://golang.org/pkg/crypto/tls/#example_Config_verifyPeerCertificate - // https://www.example-code.com/golang/cert.asp - // https://golang.org/pkg/crypto/x509/pkix/#Extension - tlsConfig.VerifyPeerCertificate = func(certificates [][]byte, _ [][]*x509.Certificate) error { - if Config.Verbose > 1 { - log.Println("call custom tlsConfig.VerifyPeerCertificate") - } - certs := make([]*x509.Certificate, len(certificates)) - for i, asn1Data := range certificates { - cert, err := x509.ParseCertificate(asn1Data) - if err != nil { - return errors.New("tls: failed to parse certificate from server: " + err.Error()) - } - if Config.Verbose > 1 { - log.Println("Issuer", cert.Issuer) - log.Println("Subject", cert.Subject) - log.Println("emails", cert.EmailAddresses) - } - // check validity of user certificate - tstamp := time.Now().Unix() - if cert.NotBefore.Unix() > tstamp || cert.NotAfter.Unix() < tstamp { - msg := fmt.Sprintf("Expired user certificate, valid from %v to %v\n", cert.NotBefore, cert.NotAfter) - return errors.New(msg) - } - // dump cert UnhandledCriticalExtensions - for _, ext := range cert.UnhandledCriticalExtensions { - if Config.Verbose > 1 { - log.Printf("Cetificate extension: %+v\n", ext) - } - continue - } - if len(cert.UnhandledCriticalExtensions) == 0 && cert != nil { - certs[i] = cert - } - } - if Config.Verbose > 1 { - log.Println("### number of certs", len(certs)) - for _, cert := range certs { - if cert != nil { - log.Printf("issuer %v subject %v valid from %v till %v\n", cert.Issuer, cert.Subject, cert.NotBefore, cert.NotAfter) - } - } - } - opts := x509.VerifyOptions{ - Roots: rootCAs, - Intermediates: x509.NewCertPool(), - KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - } - if len(certs) > 0 && certs[0] != nil { - for _, cert := range certs[1:] { - opts.Intermediates.AddCert(cert) - } - _, err := certs[0].Verify(opts) - return err - } - for _, cert := range certs { - if cert == nil { - continue - } - _, err := cert.Verify(opts) - if err != nil { - return err - } - } - return nil + RootCAs: _rootCAs, } + tlsConfig.VerifyPeerCertificate = VerifyPeerCertificate } addr := fmt.Sprintf(":%d", Config.Port) server := &http.Server{ @@ -222,20 +234,34 @@ func getServer(serverCrt, serverKey string, customVerify bool) (*http.Server, er // LetsEncryptServer provides HTTPs server with Let's encrypt for // given domain names (hosts) func LetsEncryptServer(hosts ...string) *http.Server { + // setup LetsEncrypt cert manager certManager := autocert.Manager{ Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist(hosts...), Cache: autocert.DirCache("certs"), } + tlsConfig := &tls.Config{ + // Set InsecureSkipVerify to skip the default validation we are + // replacing. This will not disable VerifyPeerCertificate. + InsecureSkipVerify: true, + ClientAuth: tls.RequestClientCert, + RootCAs: _rootCAs, + GetCertificate: certManager.GetCertificate, + } + tlsConfig.VerifyPeerCertificate = VerifyPeerCertificate + + // start HTTP server with our rootCAs and LetsEncrypt certificates server := &http.Server{ - Addr: ":https", - TLSConfig: &tls.Config{ - GetCertificate: certManager.GetCertificate, - }, + Addr: ":https", + TLSConfig: tlsConfig, + // TLSConfig: &tls.Config{ + // GetCertificate: certManager.GetCertificate, + // }, } // start cert Manager goroutine go http.ListenAndServe(":http", certManager.HTTPHandler(nil)) + log.Println("Starting LetsEncrypt HTTPs server") return server } @@ -268,15 +294,26 @@ func findCN(subject string) (string, error) { func getUserData(r *http.Request) map[string]interface{} { userData := make(map[string]interface{}) if r.TLS == nil { + if Config.Verbose > 0 { + log.Printf("HTTP request does not support TLS, %+v", r) + } return userData } certs := r.TLS.PeerCertificates + if Config.Verbose > 0 { + log.Printf("found %d peer certificates in HTTP request", len(certs)) + log.Printf("HTTP request %+v", r) + log.Printf("HTTP request TLS %+v", r.TLS) + } for _, asn1Data := range certs { cert, err := x509.ParseCertificate(asn1Data.Raw) if err != nil { log.Println("x509RequestHandler tls: failed to parse certificate from server: " + err.Error()) } if len(cert.UnhandledCriticalExtensions) > 0 { + if Config.Verbose > 0 { + log.Println("cert.UnhandledCriticalExtensions equal to", len(cert.UnhandledCriticalExtensions)) + } continue } start := time.Now() @@ -290,6 +327,9 @@ func getUserData(r *http.Request) map[string]interface{} { } subjects = append(subjects, s) } + if Config.Verbose > 0 { + log.Println("cert subjects", subjects) + } rec, err := cric.FindUser(subjects) if Config.Verbose > 0 { log.Printf("found user %+v error=%v elapsed time %v\n", rec, err, time.Since(start)) diff --git a/x509.go b/x509.go index 4d3449b..b411837 100644 --- a/x509.go +++ b/x509.go @@ -40,6 +40,10 @@ func x509RequestHandler(w http.ResponseWriter, r *http.Request) { status := http.StatusOK tstamp := int64(start.UnixNano() / 1000000) // use milliseconds for MONIT userData := getUserData(r) + if Config.Verbose > 0 { + log.Println("userData", userData) + } + // set CMS headers based on provided user certificate level := false if Config.Verbose > 3 { @@ -95,6 +99,7 @@ func x509ProxyServer() { // start HTTPS server if Config.LetsEncrypt { server := LetsEncryptServer(Config.DomainNames...) + log.Println("Start X509 HTTPs server with LetsEncrypt", Config.DomainNames) log.Fatal(server.ListenAndServeTLS("", "")) } else { // check if provided crt/key files exists @@ -105,6 +110,7 @@ func x509ProxyServer() { if err != nil { log.Fatalf("unable to start x509 server, error %v\n", err) } + log.Println("Start X509 HTTPs server with", serverCrt, serverKey) log.Fatal(server.ListenAndServeTLS(serverCrt, serverKey)) } }