Skip to content

Commit

Permalink
Refactor code to manage RootCAs and add them to LetsEncrypt
Browse files Browse the repository at this point in the history
  • Loading branch information
vkuznet committed Jan 13, 2022
1 parent b39467f commit 383502a
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 82 deletions.
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand All @@ -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=
Expand All @@ -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=
Expand All @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
3 changes: 3 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
196 changes: 118 additions & 78 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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{
Expand All @@ -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
}

Expand Down Expand Up @@ -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()
Expand All @@ -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))
Expand Down
6 changes: 6 additions & 0 deletions x509.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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))
}
}

0 comments on commit 383502a

Please sign in to comment.