Skip to content

Commit 336bc51

Browse files
authored
Merge pull request #47 from alexandrestein/master
Add support for TLS client authentication at the host level
2 parents 0732f4e + f38642d commit 336bc51

File tree

4 files changed

+99
-26
lines changed

4 files changed

+99
-26
lines changed

armor.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package armor
22

33
import (
4+
"crypto/tls"
45
"sync"
56
"time"
67

@@ -75,6 +76,8 @@ type (
7576
Paths Paths `json:"paths"`
7677
Plugins []plugin.Plugin `json:"-"`
7778
Echo *echo.Echo `json:"-"`
79+
ClientCAs []string `json:"client_ca"`
80+
TLSConfig *tls.Config `json:"-"`
7881
}
7982

8083
Path struct {

http.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func (a *Armor) NewHTTP() (h *HTTP) {
4949
ReadTimeout: a.ReadTimeout * time.Second,
5050
WriteTimeout: a.WriteTimeout * time.Second,
5151
}
52+
e.TLSServer.TLSConfig.GetConfigForClient = a.GetConfigForClient
5253
e.AutoTLSManager.Email = a.TLS.Email
5354
e.AutoTLSManager.Client = new(acme.Client)
5455
if a.TLS.DirectoryURL != "" {

tls.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package armor
2+
3+
import (
4+
"crypto/tls"
5+
"crypto/x509"
6+
"encoding/base64"
7+
)
8+
9+
// GetConfigForClient implements the Config.GetClientCertificate callback
10+
func (a *Armor) GetConfigForClient(clientHelloInfo *tls.ClientHelloInfo) (*tls.Config, error) {
11+
// Get the host from the hello info
12+
host := a.Hosts[clientHelloInfo.ServerName]
13+
if len(host.ClientCAs) == 0 {
14+
return nil, nil
15+
}
16+
17+
// Use existing host config if exist
18+
if host.TLSConfig != nil {
19+
return host.TLSConfig, nil
20+
}
21+
22+
// Build and save the host config
23+
host.TLSConfig = a.buildTLSConfig(clientHelloInfo, host)
24+
25+
return host.TLSConfig, nil
26+
}
27+
28+
func (a *Armor) buildTLSConfig(clientHelloInfo *tls.ClientHelloInfo, host *Host) *tls.Config {
29+
// Copy the configurations from the regular server
30+
tlsConfig := new(tls.Config)
31+
*tlsConfig = *a.Echo.TLSServer.TLSConfig
32+
33+
// Set the client validation and the certification pool
34+
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
35+
tlsConfig.ClientCAs = a.buildClientCertPool(host)
36+
37+
return tlsConfig
38+
}
39+
40+
func (a *Armor) buildClientCertPool(host *Host) (certPool *x509.CertPool) {
41+
certPool = x509.NewCertPool()
42+
43+
// Loop every CA certs given as base64 DER encoding
44+
for _, clientCAString := range host.ClientCAs {
45+
// Decode base64
46+
derBytes, err := base64.StdEncoding.DecodeString(clientCAString)
47+
if err != nil {
48+
continue
49+
}
50+
if len(derBytes) == 0 {
51+
continue
52+
}
53+
54+
// Parse the DER encoded certificate
55+
var caCert *x509.Certificate
56+
caCert, err = x509.ParseCertificate(derBytes)
57+
if err != nil {
58+
continue
59+
}
60+
61+
// Add the certificate to CertPool
62+
certPool.AddCert(caCert)
63+
}
64+
65+
return certPool
66+
}

website/content/guide/configuration.md

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,41 +9,42 @@ description = "Armor configuration"
99
Armor accepts configuration in YAML format, command-line option `-c` can be used
1010
to specify a config file, e.g. `armor -c config.yaml`.
1111

12-
Name | Type | Description
13-
:--- | :--- | :----------
14-
`address` | string | HTTP listen address e.g. `:8080` listens to all IP address on port 8080
15-
`read_timeout` | number | Maximum duration in seconds before timing out read of the request
16-
`write_timeout` | number | Maximum duration before timing out write of the response
17-
`tls` | object | TLS configuration
18-
`plugins` | array | Global plugins
19-
`hosts` | object | Virtual hosts
12+
| Name | Type | Description |
13+
| :-------------- | :----- | :---------------------------------------------------------------------- |
14+
| `address` | string | HTTP listen address e.g. `:8080` listens to all IP address on port 8080 |
15+
| `read_timeout` | number | Maximum duration in seconds before timing out read of the request |
16+
| `write_timeout` | number | Maximum duration before timing out write of the response |
17+
| `tls` | object | TLS configuration |
18+
| `plugins` | array | Global plugins |
19+
| `hosts` | object | Virtual hosts |
2020

2121
`tls`
2222

23-
Name | Type | Description
24-
:--- | :--- | :----------
25-
`address` | string | HTTPS listen address. Default value `:80`
26-
`cert_file` | string | Certificate file
27-
`key_file` | string | Key file
28-
`auto` | bool | Enable automatic certificates from https://letsencrypt.org
29-
`cache_dir` | string | Cache directory to store certificates from https://letsencrypt.org. Default value `~/.armor/cache`.
30-
`email` | string | Email optionally specifies a contact email address.
31-
`directory_url` | string | Defines the ACME CA directory endpoint. If empty, LetsEncryptURL is used (acme.LetsEncryptURL).
23+
| Name | Type | Description |
24+
| :-------------- | :----- | :-------------------------------------------------------------------------------------------------- |
25+
| `address` | string | HTTPS listen address. Default value `:80` |
26+
| `cert_file` | string | Certificate file |
27+
| `key_file` | string | Key file |
28+
| `auto` | bool | Enable automatic certificates from https://letsencrypt.org |
29+
| `cache_dir` | string | Cache directory to store certificates from https://letsencrypt.org. Default value `~/.armor/cache`. |
30+
| `email` | string | Email optionally specifies a contact email address. |
31+
| `directory_url` | string | Defines the ACME CA directory endpoint. If empty, LetsEncryptURL is used (acme.LetsEncryptURL). |
3232

3333
`hosts`
3434

35-
Name | Type | Description
36-
:--- | :--- | :----------
37-
`cert_file` | string | Certificate file
38-
`key_file` | string | Key file
39-
`plugins` | array | Host plugins
40-
`paths` | object | Paths
35+
| Name | Type | Description |
36+
| :---------- | :----- | :-------------------------------------------------------------------------------------------------------------------------- |
37+
| `cert_file` | string | Certificate file |
38+
| `key_file` | string | Key file |
39+
| `plugins` | array | Host plugins |
40+
| `paths` | object | Paths |
41+
| `client_ca` | array | A list of client CA (certificate authority) certificate encoded as base64 DER. If set client must provide valid certificate |
4142

4243
`paths`
4344

44-
Name | Type | Description
45-
:--- | :--- | :----------
46-
`plugins` | array | Path plugins
45+
| Name | Type | Description |
46+
| :-------- | :---- | :----------- |
47+
| `plugins` | array | Path plugins |
4748

4849
## [Plugins]({{< ref "plugins/redirect.md">}})
4950

@@ -92,6 +93,8 @@ hosts:
9293
targets:
9394
- url: http://api
9495
armor.labstack.com:
96+
client_ca_der:
97+
- "MIIDSzCCAjOgAwI......E/lYx0qGtr0xHQ=="
9598
paths:
9699
"/":
97100
plugins:

0 commit comments

Comments
 (0)