mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-04 03:27:23 -05:00 
			
		
		
		
	Support configuring less restrictive TLS client auth requirements
Caddyfile parameter "clients" of "tls" henceforth accepts a special first modifier. It is one of, and effects: * request = tls.RequestClientCert * require = tls.RequireAnyClientCert * verify_if_given = tls.VerifyClientCertIfGiven * (none) = tls.RequireAndVerifyClientCert The use-case for this is as follows: A middleware would serve items to the public, but if a certificate were given the middleware would permit file manipulation. And, in a different plugin such as a forum or blog, not verifying a client cert would be nice for registration: said blog would subsequently only compare the SPKI of a client certificate.
This commit is contained in:
		
							parent
							
								
									f31875dfde
								
							
						
					
					
						commit
						69c2d78f69
					
				@ -83,10 +83,30 @@ func Setup(c *setup.Controller) (middleware.Middleware, error) {
 | 
				
			|||||||
					c.TLS.Ciphers = append(c.TLS.Ciphers, value)
 | 
										c.TLS.Ciphers = append(c.TLS.Ciphers, value)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			case "clients":
 | 
								case "clients":
 | 
				
			||||||
				c.TLS.ClientCerts = c.RemainingArgs()
 | 
									clientCertList := c.RemainingArgs()
 | 
				
			||||||
				if len(c.TLS.ClientCerts) == 0 {
 | 
									if len(clientCertList) == 0 {
 | 
				
			||||||
					return nil, c.ArgErr()
 | 
										return nil, c.ArgErr()
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									listStart, mustProvideCA := 1, true
 | 
				
			||||||
 | 
									switch clientCertList[0] {
 | 
				
			||||||
 | 
									case "request":
 | 
				
			||||||
 | 
										c.TLS.ClientAuth = tls.RequestClientCert
 | 
				
			||||||
 | 
										mustProvideCA = false
 | 
				
			||||||
 | 
									case "require":
 | 
				
			||||||
 | 
										c.TLS.ClientAuth = tls.RequireAnyClientCert
 | 
				
			||||||
 | 
										mustProvideCA = false
 | 
				
			||||||
 | 
									case "verify_if_given":
 | 
				
			||||||
 | 
										c.TLS.ClientAuth = tls.VerifyClientCertIfGiven
 | 
				
			||||||
 | 
									default:
 | 
				
			||||||
 | 
										c.TLS.ClientAuth = tls.RequireAndVerifyClientCert
 | 
				
			||||||
 | 
										listStart = 0
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if mustProvideCA && len(clientCertList) <= listStart {
 | 
				
			||||||
 | 
										return nil, c.ArgErr()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									c.TLS.ClientCerts = clientCertList[listStart:]
 | 
				
			||||||
			case "load":
 | 
								case "load":
 | 
				
			||||||
				c.Args(&loadDir)
 | 
									c.Args(&loadDir)
 | 
				
			||||||
				c.TLS.Manual = true
 | 
									c.TLS.Manual = true
 | 
				
			||||||
 | 
				
			|||||||
@ -189,34 +189,69 @@ func TestSetupParseWithWrongOptionalParams(t *testing.T) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestSetupParseWithClientAuth(t *testing.T) {
 | 
					func TestSetupParseWithClientAuth(t *testing.T) {
 | 
				
			||||||
 | 
						// Test missing client cert file
 | 
				
			||||||
	params := `tls ` + certFile + ` ` + keyFile + ` {
 | 
						params := `tls ` + certFile + ` ` + keyFile + ` {
 | 
				
			||||||
			clients client_ca.crt client2_ca.crt
 | 
								clients
 | 
				
			||||||
		}`
 | 
							}`
 | 
				
			||||||
	c := setup.NewTestController(params)
 | 
						c := setup.NewTestController(params)
 | 
				
			||||||
	_, err := Setup(c)
 | 
						_, err := Setup(c)
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		t.Errorf("Expected no errors, got: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if count := len(c.TLS.ClientCerts); count != 2 {
 | 
					 | 
				
			||||||
		t.Fatalf("Expected two client certs, had %d", count)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if actual := c.TLS.ClientCerts[0]; actual != "client_ca.crt" {
 | 
					 | 
				
			||||||
		t.Errorf("Expected first client cert file to be '%s', but was '%s'", "client_ca.crt", actual)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if actual := c.TLS.ClientCerts[1]; actual != "client2_ca.crt" {
 | 
					 | 
				
			||||||
		t.Errorf("Expected second client cert file to be '%s', but was '%s'", "client2_ca.crt", actual)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Test missing client cert file
 | 
					 | 
				
			||||||
	params = `tls ` + certFile + ` ` + keyFile + ` {
 | 
					 | 
				
			||||||
			clients
 | 
					 | 
				
			||||||
		}`
 | 
					 | 
				
			||||||
	c = setup.NewTestController(params)
 | 
					 | 
				
			||||||
	_, err = Setup(c)
 | 
					 | 
				
			||||||
	if err == nil {
 | 
						if err == nil {
 | 
				
			||||||
		t.Errorf("Expected an error, but no error returned")
 | 
							t.Errorf("Expected an error, but no error returned")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						noCAs, twoCAs := []string{}, []string{"client_ca.crt", "client2_ca.crt"}
 | 
				
			||||||
 | 
						for caseNumber, caseData := range []struct {
 | 
				
			||||||
 | 
							params         string
 | 
				
			||||||
 | 
							clientAuthType tls.ClientAuthType
 | 
				
			||||||
 | 
							expectedErr    bool
 | 
				
			||||||
 | 
							expectedCAs    []string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{"", tls.NoClientCert, false, noCAs},
 | 
				
			||||||
 | 
							{`tls ` + certFile + ` ` + keyFile + ` {
 | 
				
			||||||
 | 
								clients client_ca.crt client2_ca.crt
 | 
				
			||||||
 | 
							}`, tls.RequireAndVerifyClientCert, false, twoCAs},
 | 
				
			||||||
 | 
							// now come modifier
 | 
				
			||||||
 | 
							{`tls ` + certFile + ` ` + keyFile + ` {
 | 
				
			||||||
 | 
								clients request
 | 
				
			||||||
 | 
							}`, tls.RequestClientCert, false, noCAs},
 | 
				
			||||||
 | 
							{`tls ` + certFile + ` ` + keyFile + ` {
 | 
				
			||||||
 | 
								clients require
 | 
				
			||||||
 | 
							}`, tls.RequireAnyClientCert, false, noCAs},
 | 
				
			||||||
 | 
							{`tls ` + certFile + ` ` + keyFile + ` {
 | 
				
			||||||
 | 
								clients verify_if_given client_ca.crt client2_ca.crt
 | 
				
			||||||
 | 
							}`, tls.VerifyClientCertIfGiven, false, twoCAs},
 | 
				
			||||||
 | 
							{`tls ` + certFile + ` ` + keyFile + ` {
 | 
				
			||||||
 | 
								clients verify_if_given
 | 
				
			||||||
 | 
							}`, tls.VerifyClientCertIfGiven, true, noCAs},
 | 
				
			||||||
 | 
						} {
 | 
				
			||||||
 | 
							c := setup.NewTestController(caseData.params)
 | 
				
			||||||
 | 
							_, err := Setup(c)
 | 
				
			||||||
 | 
							if caseData.expectedErr {
 | 
				
			||||||
 | 
								if err == nil {
 | 
				
			||||||
 | 
									t.Errorf("In case %d: Expected an error, got: %v", caseNumber, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Errorf("In case %d: Expected no errors, got: %v", caseNumber, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if caseData.clientAuthType != c.TLS.ClientAuth {
 | 
				
			||||||
 | 
								t.Errorf("In case %d: Expected TLS client auth type %v, got: %v",
 | 
				
			||||||
 | 
									caseNumber, caseData.clientAuthType, c.TLS.ClientAuth)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if count := len(c.TLS.ClientCerts); count < len(caseData.expectedCAs) {
 | 
				
			||||||
 | 
								t.Fatalf("In case %d: Expected %d client certs, had %d", caseNumber, len(caseData.expectedCAs), count)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for idx, expected := range caseData.expectedCAs {
 | 
				
			||||||
 | 
								if actual := c.TLS.ClientCerts[idx]; actual != expected {
 | 
				
			||||||
 | 
									t.Errorf("In case %d: Expected %dth client cert file to be '%s', but was '%s'",
 | 
				
			||||||
 | 
										caseNumber, idx, expected, actual)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestSetupParseWithKeyType(t *testing.T) {
 | 
					func TestSetupParseWithKeyType(t *testing.T) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
package server
 | 
					package server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/tls"
 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/mholt/caddy/middleware"
 | 
						"github.com/mholt/caddy/middleware"
 | 
				
			||||||
@ -75,4 +76,5 @@ type TLSConfig struct {
 | 
				
			|||||||
	ProtocolMaxVersion       uint16
 | 
						ProtocolMaxVersion       uint16
 | 
				
			||||||
	PreferServerCipherSuites bool
 | 
						PreferServerCipherSuites bool
 | 
				
			||||||
	ClientCerts              []string
 | 
						ClientCerts              []string
 | 
				
			||||||
 | 
						ClientAuth               tls.ClientAuthType
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -379,17 +379,19 @@ func DefaultErrorFunc(w http.ResponseWriter, r *http.Request, status int) {
 | 
				
			|||||||
// setupClientAuth sets up TLS client authentication only if
 | 
					// setupClientAuth sets up TLS client authentication only if
 | 
				
			||||||
// any of the TLS configs specified at least one cert file.
 | 
					// any of the TLS configs specified at least one cert file.
 | 
				
			||||||
func setupClientAuth(tlsConfigs []TLSConfig, config *tls.Config) error {
 | 
					func setupClientAuth(tlsConfigs []TLSConfig, config *tls.Config) error {
 | 
				
			||||||
	var clientAuth bool
 | 
						whatClientAuth := tls.NoClientCert
 | 
				
			||||||
	for _, cfg := range tlsConfigs {
 | 
						for _, cfg := range tlsConfigs {
 | 
				
			||||||
		if len(cfg.ClientCerts) > 0 {
 | 
							if whatClientAuth < cfg.ClientAuth { // Use the most restrictive.
 | 
				
			||||||
			clientAuth = true
 | 
								whatClientAuth = cfg.ClientAuth
 | 
				
			||||||
			break
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if clientAuth {
 | 
						if whatClientAuth != tls.NoClientCert {
 | 
				
			||||||
		pool := x509.NewCertPool()
 | 
							pool := x509.NewCertPool()
 | 
				
			||||||
		for _, cfg := range tlsConfigs {
 | 
							for _, cfg := range tlsConfigs {
 | 
				
			||||||
 | 
								if len(cfg.ClientCerts) == 0 {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			for _, caFile := range cfg.ClientCerts {
 | 
								for _, caFile := range cfg.ClientCerts {
 | 
				
			||||||
				caCrt, err := ioutil.ReadFile(caFile) // Anyone that gets a cert from this CA can connect
 | 
									caCrt, err := ioutil.ReadFile(caFile) // Anyone that gets a cert from this CA can connect
 | 
				
			||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
@ -401,7 +403,7 @@ func setupClientAuth(tlsConfigs []TLSConfig, config *tls.Config) error {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		config.ClientCAs = pool
 | 
							config.ClientCAs = pool
 | 
				
			||||||
		config.ClientAuth = tls.RequireAndVerifyClientCert
 | 
							config.ClientAuth = whatClientAuth
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user