Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for TLS client authentication at the host level #47

Merged
merged 5 commits into from
Mar 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions armor.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package armor

import (
"crypto/tls"
"sync"
"time"

Expand Down Expand Up @@ -75,6 +76,8 @@ type (
Paths Paths `json:"paths"`
Plugins []plugin.Plugin `json:"-"`
Echo *echo.Echo `json:"-"`
ClientCAs []string `json:"client_ca"`
TLSConfig *tls.Config `json:"-"`
}

Path struct {
Expand Down
1 change: 1 addition & 0 deletions http.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func (a *Armor) NewHTTP() (h *HTTP) {
ReadTimeout: a.ReadTimeout * time.Second,
WriteTimeout: a.WriteTimeout * time.Second,
}
e.TLSServer.TLSConfig.GetConfigForClient = a.GetConfigForClient
e.AutoTLSManager.Email = a.TLS.Email
e.AutoTLSManager.Client = new(acme.Client)
if a.TLS.DirectoryURL != "" {
Expand Down
66 changes: 66 additions & 0 deletions tls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package armor

import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
)

// GetConfigForClient implements the Config.GetClientCertificate callback
func (a *Armor) GetConfigForClient(clientHelloInfo *tls.ClientHelloInfo) (*tls.Config, error) {
// Get the host from the hello info
host := a.Hosts[clientHelloInfo.ServerName]
if len(host.ClientCAs) == 0 {
return nil, nil
}

// Use existing host config if exist
if host.TLSConfig != nil {
return host.TLSConfig, nil
}

// Build and save the host config
host.TLSConfig = a.buildTLSConfig(clientHelloInfo, host)

return host.TLSConfig, nil
}

func (a *Armor) buildTLSConfig(clientHelloInfo *tls.ClientHelloInfo, host *Host) *tls.Config {
// Copy the configurations from the regular server
tlsConfig := new(tls.Config)
*tlsConfig = *a.Echo.TLSServer.TLSConfig

// Set the client validation and the certification pool
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
tlsConfig.ClientCAs = a.buildClientCertPool(host)

return tlsConfig
}

func (a *Armor) buildClientCertPool(host *Host) (certPool *x509.CertPool) {
certPool = x509.NewCertPool()

// Loop every CA certs given as base64 DER encoding
for _, clientCAString := range host.ClientCAs {
// Decode base64
derBytes, err := base64.StdEncoding.DecodeString(clientCAString)
if err != nil {
continue
}
if len(derBytes) == 0 {
continue
}

// Parse the DER encoded certificate
var caCert *x509.Certificate
caCert, err = x509.ParseCertificate(derBytes)
if err != nil {
continue
}

// Add the certificate to CertPool
certPool.AddCert(caCert)
}

return certPool
}
55 changes: 29 additions & 26 deletions website/content/guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,41 +9,42 @@ description = "Armor configuration"
Armor accepts configuration in YAML format, command-line option `-c` can be used
to specify a config file, e.g. `armor -c config.yaml`.

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

`tls`

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

`hosts`

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

`paths`

Name | Type | Description
:--- | :--- | :----------
`plugins` | array | Path plugins
| Name | Type | Description |
| :-------- | :---- | :----------- |
| `plugins` | array | Path plugins |

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

Expand Down Expand Up @@ -92,6 +93,8 @@ hosts:
targets:
- url: http://api
armor.labstack.com:
client_ca_der:
- "MIIDSzCCAjOgAwI......E/lYx0qGtr0xHQ=="
paths:
"/":
plugins:
Expand Down