commit
f1fb00efdd
8 changed files with 191 additions and 9 deletions
59
Documentation/security_guide.md
Normal file
59
Documentation/security_guide.md
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
# Security Guide
|
||||||
|
|
||||||
|
According to the [OpenID Connect Spec](http://openid.net/specs/openid-connect-core-1_0.html),
|
||||||
|
TLS [is required](http://openid.net/specs/openid-connect-core-1_0.html#TLSRequirements) for connection between the client and the dex server.
|
||||||
|
This guide explains how to set up a TLS connection for dex.
|
||||||
|
|
||||||
|
# Create certificates, key files.
|
||||||
|
|
||||||
|
To get up and running you will need:
|
||||||
|
- Certificate Authority file (CA cert).
|
||||||
|
- Certificate file for the server signed by the CA above.
|
||||||
|
- Private Key file of the server, used by the server to exchange keys during TLS handshake.
|
||||||
|
|
||||||
|
There are a lot of tools and guides available to help you generate these files:
|
||||||
|
- Use 'openssl' command line. The guide can be found [here](http://www.g-loaded.eu/2005/11/10/be-your-own-ca/)
|
||||||
|
- Use [etcd-ca](https://github.com/coreos/etcd-ca), which is a simple certificate manager written in Go. Despite the its name, it can be used in other cases than etcd as well.
|
||||||
|
- Use Cloudflare's [cfssl](https://github.com/cloudflare/cfssl), we also provide example configs [here](../examples/tls-setup), which is as simple as run `make`.
|
||||||
|
|
||||||
|
# Start the server using TLS
|
||||||
|
|
||||||
|
Assume we already generated a CA file, a server certificate and a key file, then in order to make the dex server accept TLS connections, we will run the `dex-worker` with `--cert-file=$PATH_TO_SERVER_CERTIFICATE` and `--key-file=$PATH_TO_SERVER_KEY_FILE`, For example:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
./build
|
||||||
|
./bin/dex-worker \
|
||||||
|
--cert-file=examples/tls-setup/certs/dex.pem \
|
||||||
|
--key-file=examples/tls-setup/certs/dex-key.pem \
|
||||||
|
--listen="https://127.0.0.1:5556" \
|
||||||
|
--issuer="https://127.0.0.1:5556" \
|
||||||
|
--clients=./static/fixtures/clients.json \
|
||||||
|
--connectors=./static/fixtures/connectors.json.sample \
|
||||||
|
--email-cfg=./static/fixtures/emailer.json.sample \
|
||||||
|
--users=./static/fixtures/users.json.sample \
|
||||||
|
--no-db
|
||||||
|
|
||||||
|
```
|
||||||
|
Where: <br/>
|
||||||
|
`--cert-file` and `--key-file` tells the dex-worker which certificate and key file to use. <br/>
|
||||||
|
`--listen` tells dex-worker where to receive requests. <br/>
|
||||||
|
`--clients`, `--connectors`, `--email-cfg` and `--users` tells dex-worker where to find user/client data when database access is not enabled (`--no-db`). <br/>
|
||||||
|
|
||||||
|
When establishing connection to the server, we will need to provide the CA file to client unless the server's ceritificate is signed by CAs that already trusted by client's machine. For example:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
./build
|
||||||
|
./bin/example-app \
|
||||||
|
--trusted-ca-file=examples/tls-setup/certs/ca.pem \
|
||||||
|
--client-id="XXX" \
|
||||||
|
--client-secret="secrete" \
|
||||||
|
--redirect-url="http://127.0.0.1:5555/callback" \
|
||||||
|
--discovery="https://127.0.0.1:5556" \
|
||||||
|
--listen="http://127.0.0.1:5555"
|
||||||
|
```
|
||||||
|
Where: <br/>
|
||||||
|
`--trusted-ca-file` tells the app where to find the CA file that we will trust. Note that if the server's certificate is self-signed, then the server's certificate is also the CA file here. <br/>
|
||||||
|
`--client-id`, `--client-secret` and `--redirect-url` are the registered client's identity information. <br/>
|
||||||
|
`--discovery` and `--listen` tell the app where to connect to the server and where to handle requests. <br/>
|
||||||
|
|
||||||
|
Next, you can go to http://127.0.0.1:5555 to register, login and enjoy your OIDC tokens generated by dex server.
|
|
@ -27,8 +27,11 @@ func init() {
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fs := flag.NewFlagSet("dex-worker", flag.ExitOnError)
|
fs := flag.NewFlagSet("dex-worker", flag.ExitOnError)
|
||||||
listen := fs.String("listen", "http://0.0.0.0:5556", "")
|
listen := fs.String("listen", "http://127.0.0.1:5556", "the address that the server will listen on")
|
||||||
issuer := fs.String("issuer", "http://127.0.0.1:5556", "")
|
issuer := fs.String("issuer", "http://127.0.0.1:5556", "the issuer's location")
|
||||||
|
certFile := fs.String("tls-cert-file", "", "the server's certificate file for TLS connection")
|
||||||
|
keyFile := fs.String("tls-key-file", "", "the server's private key file for TLS connection")
|
||||||
|
|
||||||
templates := fs.String("html-assets", "./static/html", "directory of html template files")
|
templates := fs.String("html-assets", "./static/html", "directory of html template files")
|
||||||
|
|
||||||
emailTemplateDirs := flagutil.StringSliceFlag{"./static/email"}
|
emailTemplateDirs := flagutil.StringSliceFlag{"./static/email"}
|
||||||
|
@ -75,13 +78,30 @@ func main() {
|
||||||
log.EnableTimestamps()
|
log.EnableTimestamps()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate listen address.
|
||||||
lu, err := url.Parse(*listen)
|
lu, err := url.Parse(*listen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Unable to use --listen flag: %v", err)
|
log.Fatalf("Invalid listen address %q: %v", *listen, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if lu.Scheme != "http" {
|
switch lu.Scheme {
|
||||||
log.Fatalf("Unable to listen using scheme %s", lu.Scheme)
|
case "http":
|
||||||
|
case "https":
|
||||||
|
if *certFile == "" || *keyFile == "" {
|
||||||
|
log.Fatalf("Must provide certificate file and private key file")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Fatalf("Only 'http' and 'https' schemes are supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate issuer address.
|
||||||
|
iu, err := url.Parse(*issuer)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Invalid issuer URL %q: %v", *issuer, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if iu.Scheme != "http" && iu.Scheme != "https" {
|
||||||
|
log.Fatalf("Only 'http' and 'https' schemes are supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
scfg := server.ServerConfig{
|
scfg := server.ServerConfig{
|
||||||
|
@ -145,7 +165,11 @@ func main() {
|
||||||
|
|
||||||
log.Infof("Binding to %s...", httpsrv.Addr)
|
log.Infof("Binding to %s...", httpsrv.Addr)
|
||||||
go func() {
|
go func() {
|
||||||
|
if lu.Scheme == "http" {
|
||||||
log.Fatal(httpsrv.ListenAndServe())
|
log.Fatal(httpsrv.ListenAndServe())
|
||||||
|
} else {
|
||||||
|
log.Fatal(httpsrv.ListenAndServeTLS(*certFile, *keyFile))
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
<-srv.Run()
|
<-srv.Run()
|
||||||
|
|
|
@ -2,9 +2,12 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -27,6 +30,8 @@ func main() {
|
||||||
redirectURL := fs.String("redirect-url", "http://127.0.0.1:5555/callback", "")
|
redirectURL := fs.String("redirect-url", "http://127.0.0.1:5555/callback", "")
|
||||||
clientID := fs.String("client-id", "", "")
|
clientID := fs.String("client-id", "", "")
|
||||||
clientSecret := fs.String("client-secret", "", "")
|
clientSecret := fs.String("client-secret", "", "")
|
||||||
|
caFile := fs.String("trusted-ca-file", "", "the TLS CA file, if empty then the host's root CA will be used")
|
||||||
|
|
||||||
discovery := fs.String("discovery", "https://accounts.google.com", "")
|
discovery := fs.String("discovery", "https://accounts.google.com", "")
|
||||||
logDebug := fs.Bool("log-debug", false, "log debug-level information")
|
logDebug := fs.Bool("log-debug", false, "log debug-level information")
|
||||||
logTimestamps := fs.Bool("log-timestamps", false, "prefix log lines with timestamps")
|
logTimestamps := fs.Bool("log-timestamps", false, "prefix log lines with timestamps")
|
||||||
|
@ -71,9 +76,22 @@ func main() {
|
||||||
Secret: *clientSecret,
|
Secret: *clientSecret,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tlsConfig tls.Config
|
||||||
|
if *caFile != "" {
|
||||||
|
roots := x509.NewCertPool()
|
||||||
|
pemBlock, err := ioutil.ReadFile(*caFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to read ca file: %v", err)
|
||||||
|
}
|
||||||
|
roots.AppendCertsFromPEM(pemBlock)
|
||||||
|
tlsConfig.RootCAs = roots
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient := &http.Client{Transport: &http.Transport{TLSClientConfig: &tlsConfig}}
|
||||||
|
|
||||||
var cfg oidc.ProviderConfig
|
var cfg oidc.ProviderConfig
|
||||||
for {
|
for {
|
||||||
cfg, err = oidc.FetchProviderConfig(http.DefaultClient, *discovery)
|
cfg, err = oidc.FetchProviderConfig(httpClient, *discovery)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -86,6 +104,7 @@ func main() {
|
||||||
log.Infof("Fetched provider config from %s: %#v", *discovery, cfg)
|
log.Infof("Fetched provider config from %s: %#v", *discovery, cfg)
|
||||||
|
|
||||||
ccfg := oidc.ClientConfig{
|
ccfg := oidc.ClientConfig{
|
||||||
|
HTTPClient: httpClient,
|
||||||
ProviderConfig: cfg,
|
ProviderConfig: cfg,
|
||||||
Credentials: cc,
|
Credentials: cc,
|
||||||
RedirectURL: *redirectURL,
|
RedirectURL: *redirectURL,
|
||||||
|
@ -226,9 +245,9 @@ func handleCallbackFunc(c *oidc.Client) http.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s := fmt.Sprintf(`<html><body>Claims: %v <br>
|
s := fmt.Sprintf(`<html><body><p>Token: %v</p><p>Claims: %v </p>
|
||||||
<a href="/resend?jwt=%s">Resend Verification Email</a>
|
<a href="/resend?jwt=%s">Resend Verification Email</a>
|
||||||
</body></html>`, claims, tok.Encode())
|
</body></html>`, tok.Encode(), claims, tok.Encode())
|
||||||
w.Write([]byte(s))
|
w.Write([]byte(s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
25
examples/tls-setup/Makefile
Normal file
25
examples/tls-setup/Makefile
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
.PHONY: cfssl ca req clean
|
||||||
|
|
||||||
|
CFSSL = @env PATH=$(GOPATH)/bin:$(PATH) cfssl
|
||||||
|
JSON = env PATH=$(GOPATH)/bin:$(PATH) cfssljson
|
||||||
|
|
||||||
|
all: cfssl ca req
|
||||||
|
|
||||||
|
cfssl:
|
||||||
|
go get -u -tags nopkcs11 github.com/cloudflare/cfssl/cmd/cfssl
|
||||||
|
go get -u github.com/cloudflare/cfssl/cmd/cfssljson
|
||||||
|
|
||||||
|
ca:
|
||||||
|
mkdir -p certs
|
||||||
|
$(CFSSL) gencert -initca config/ca-csr.json | $(JSON) -bare certs/ca
|
||||||
|
|
||||||
|
req:
|
||||||
|
$(CFSSL) gencert \
|
||||||
|
-ca certs/ca.pem \
|
||||||
|
-ca-key certs/ca-key.pem \
|
||||||
|
-config config/ca-config.json \
|
||||||
|
config/req-csr.json | $(JSON) -bare certs/dex
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf certs
|
||||||
|
|
8
examples/tls-setup/README.md
Normal file
8
examples/tls-setup/README.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Similar to etcd's [tls-setup](https://github.com/coreos/etcd/tree/master/hack/tls-setup), this demonstrates using Cloudflare's [cfssl](https://github.com/cloudflare/cfssl) to easily generate certificates for an dex server.
|
||||||
|
|
||||||
|
Defaults generate an ECDSA-384 root and leaf certificates for `localhost`.
|
||||||
|
|
||||||
|
**Instructions**
|
||||||
|
|
||||||
|
1. Install git, go, and make
|
||||||
|
2. Run `make` to generate the certs
|
13
examples/tls-setup/config/ca-config.json
Normal file
13
examples/tls-setup/config/ca-config.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"signing": {
|
||||||
|
"default": {
|
||||||
|
"usages": [
|
||||||
|
"signing",
|
||||||
|
"key encipherment",
|
||||||
|
"server auth",
|
||||||
|
"client auth"
|
||||||
|
],
|
||||||
|
"expiry": "8760h"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
examples/tls-setup/config/ca-csr.json
Normal file
16
examples/tls-setup/config/ca-csr.json
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"CN": "Autogenerated CA",
|
||||||
|
"key": {
|
||||||
|
"algo": "ecdsa",
|
||||||
|
"size": 384
|
||||||
|
},
|
||||||
|
"names": [
|
||||||
|
{
|
||||||
|
"O": "Honest Achmed's Used Certificates",
|
||||||
|
"OU": "Hastily-Generated Values Divison",
|
||||||
|
"L": "San Francisco",
|
||||||
|
"ST": "California",
|
||||||
|
"C": "US"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
18
examples/tls-setup/config/req-csr.json
Normal file
18
examples/tls-setup/config/req-csr.json
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"CN": "dex",
|
||||||
|
"hosts": [
|
||||||
|
"localhost",
|
||||||
|
"127.0.0.1"
|
||||||
|
],
|
||||||
|
"key": {
|
||||||
|
"algo": "ecdsa",
|
||||||
|
"size": 384
|
||||||
|
},
|
||||||
|
"names": [
|
||||||
|
{
|
||||||
|
"O": "autogenerated",
|
||||||
|
"OU": "dex server",
|
||||||
|
"L": "the internet"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Reference in a new issue