diff --git a/Documentation/api.md b/Documentation/api.md index 0a93c6b4..ae36c6ff 100644 --- a/Documentation/api.md +++ b/Documentation/api.md @@ -88,6 +88,8 @@ func main() { } ``` +A clear working example of the Dex gRPC client can be found [here][../examples/grpc-client/README.md]. + ## Authentication and access control The dex API does not provide any authentication or authorization beyond TLS client auth. diff --git a/Makefile b/Makefile index a8a2ca75..43de44d9 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ export GO15VENDOREXPERIMENT=1 LD_FLAGS="-w -X $(REPO_PATH)/version.Version=$(VERSION)" -build: bin/dex bin/example-app +build: bin/dex bin/example-app bin/grpc-client bin/dex: check-go-version @go install -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/dex @@ -31,6 +31,9 @@ bin/dex: check-go-version bin/example-app: check-go-version @go install -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/example-app +bin/grpc-client: check-go-version + @go install -v -ldflags $(LD_FLAGS) $(REPO_PATH)/examples/grpc-client + .PHONY: release-binary release-binary: @go build -o _output/bin/dex -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/dex diff --git a/examples/config-dev.yaml b/examples/config-dev.yaml index 61b0babb..5aec9a14 100644 --- a/examples/config-dev.yaml +++ b/examples/config-dev.yaml @@ -24,9 +24,9 @@ web: # from the HTTP endpoints. # grpc: # addr: 127.0.0.1:5557 -# tlsCert: /etc/dex/grpc.crt -# tlsKey: /etc/dex/grpc.key -# tlsClientCA: /etc/dex/client.crt +# tlsCert: examples/grpc-client/server.crt +# tlsKey: examples/grpc-client/server.key +# tlsClientCA: /etc/dex/client.crt # Uncomment this block to enable configuration for the expiration time durations. # expiry: diff --git a/examples/grpc-client/.gitignore b/examples/grpc-client/.gitignore new file mode 100644 index 00000000..7145f5ef --- /dev/null +++ b/examples/grpc-client/.gitignore @@ -0,0 +1,5 @@ +*.key +*.crt +*.csr +index.* +serial* diff --git a/examples/grpc-client/README.md b/examples/grpc-client/README.md new file mode 100644 index 00000000..0c1f82c1 --- /dev/null +++ b/examples/grpc-client/README.md @@ -0,0 +1,62 @@ +# Running a Dex gRPC client + +Using gRPC, a client application can directly call methods on a server application as if it was a local object. The schema for Dex's gRPC API calls is defined in [`api/api.proto`][api-proto]. [`client.go`][client] is an example client program that makes a bunch of API calls to the dex server. For further details on the Dex API refer [`Documentation/api.md`][api-docs]. + +## Generating Credentials + +Before running the client or the server, TLS credentials have to be setup for secure communication. Run the `cred-gen` script to create TLS credentials for running this example. This script generates a `ca.crt`, `server.crt`, `server.key`, `client.crt`, and `client.key`. + +``` +# Used to set certificate subject alt names. +export SAN=IP.1:127.0.0.1 + +# Run the script +./examples/grpc-client/cert-gen +``` +To verify that the server and client certificates were signed by the CA, run the following commands: + +``` +openssl verify -CAfile ca.crt server.crt +openssl verify -CAfile ca.crt client.crt +``` + +## Running the Dex server + +To expose the gRPC service, the gRPC option must be enabled via the dex config file as shown below. + +```yaml +# Enables the gRPC API. +grpc: + addr: 127.0.0.1:5557 + tlsCert: server.crt + tlsKey: server.key + +``` +Start an instance of the dex server with an in-memory data store: + +``` +./bin/dex serve examples/grpc-client/config.yaml +``` + +## Running the Dex client + +Finally run the Dex client providing the CA certificate, client certificate and client key as arguments. + +``` +./bin/grpc-client -ca-crt=ca.crt -client-crt=client.crt -client-key=client.key +``` +Running the gRPC client will cause the following API calls to be made to the server +1. CreatePassword +2. ListPasswords +3. DeletePassword + +## Cleaning up + +Run the following command to destroy all the credentials files that were created by the `cert-gen` script: + +``` +./examples/grpc-client/cert-destory +``` +[api-proto]: ../../api/api.proto +[client]: client.go +[api-docs]: ../../Documentation/api.md diff --git a/examples/grpc-client/cert-destroy b/examples/grpc-client/cert-destroy new file mode 100755 index 00000000..23e0ce5c --- /dev/null +++ b/examples/grpc-client/cert-destroy @@ -0,0 +1,4 @@ +#!/bin/bash + +rm -f ca.key ca.crt server.key server.csr server.crt client.key client.csr client.crt index.* serial* +rm -rf certs crl newcerts diff --git a/examples/grpc-client/cert-gen b/examples/grpc-client/cert-gen new file mode 100755 index 00000000..8f30ff4b --- /dev/null +++ b/examples/grpc-client/cert-gen @@ -0,0 +1,35 @@ +#!/bin/bash + +if [ -z $SAN ] + then echo "Set SAN with a DNS or IP(e.g. export SAN=IP.1:127.0.0.1,IP.2:172.18.0.2)." + exit 1 +fi + +echo "Creating CA, server cert/key, and client cert/key..." + +# Creating basic files/directories +mkdir -p {certs,crl,newcerts} +touch index.txt +echo 1000 > serial + +# CA private key (unencrypted) +openssl genrsa -out ca.key 4096 +# Certificate Authority (self-signed certificate) +openssl req -config examples/grpc-client/openssl.conf -new -x509 -days 3650 -sha256 -key ca.key -extensions v3_ca -out ca.crt -subj "/CN=fake-ca" + +# Server private key (unencrypted) +openssl genrsa -out server.key 2048 +# Server certificate signing request (CSR) +openssl req -config examples/grpc-client/openssl.conf -new -sha256 -key server.key -out server.csr -subj "/CN=fake-server" +# Certificate Authority signs CSR to grant a certificate +openssl ca -batch -config examples/grpc-client/openssl.conf -extensions server_cert -days 365 -notext -md sha256 -in server.csr -out server.crt -cert ca.crt -keyfile ca.key + +# Client private key (unencrypted) +openssl genrsa -out client.key 2048 +# Signed client certificate signing request (CSR) +openssl req -config examples/grpc-client/openssl.conf -new -sha256 -key client.key -out client.csr -subj "/CN=fake-client" +# Certificate Authority signs CSR to grant a certificate +openssl ca -batch -config examples/grpc-client/openssl.conf -extensions usr_cert -days 365 -notext -md sha256 -in client.csr -out client.crt -cert ca.crt -keyfile ca.key + +# Remove CSR's +rm *.csr diff --git a/examples/grpc-client/client.go b/examples/grpc-client/client.go new file mode 100644 index 00000000..073f1fb1 --- /dev/null +++ b/examples/grpc-client/client.go @@ -0,0 +1,106 @@ +package main + +import ( + "context" + "crypto/tls" + "crypto/x509" + "flag" + "fmt" + "io/ioutil" + "log" + + "github.com/coreos/dex/api" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +func newDexClient(hostAndPort, caPath, clientCrt, clientKey string) (api.DexClient, error) { + cPool := x509.NewCertPool() + caCert, err := ioutil.ReadFile(caPath) + if err != nil { + return nil, fmt.Errorf("invalid CA crt file: %s", caPath) + } + if cPool.AppendCertsFromPEM(caCert) != true { + return nil, fmt.Errorf("failed to parse CA crt") + } + + clientCert, err := tls.LoadX509KeyPair(clientCrt, clientKey) + if err != nil { + return nil, fmt.Errorf("invalid client crt file: %s", caPath) + } + + clientTLSConfig := &tls.Config{ + RootCAs: cPool, + Certificates: []tls.Certificate{clientCert}, + } + creds := credentials.NewTLS(clientTLSConfig) + + conn, err := grpc.Dial(hostAndPort, grpc.WithTransportCredentials(creds)) + if err != nil { + return nil, fmt.Errorf("dail: %v", err) + } + return api.NewDexClient(conn), nil +} + +func main() { + caCrt := flag.String("ca-crt", "", "CA certificate") + clientCrt := flag.String("client-crt", "", "Client certificate") + clientKey := flag.String("client-key", "", "Client key") + flag.Parse() + + if *clientCrt == "" || *caCrt == "" || *clientKey == "" { + log.Fatal("Please provide CA & client certificates and client key. Usage: ./client -ca_crt= -client_crt= -client_key=") + } + + client, err := newDexClient("127.0.0.1:5557", *caCrt, *clientCrt, *clientKey) + if err != nil { + log.Fatalf("failed creating dex client: %v ", err) + } + + p := api.Password{ + Email: "test@example.com", + // bcrypt hash of the value "test1" with cost 10 + Hash: []byte("$2a$10$XVMN/Fid.Ks4CXgzo8fpR.iU1khOMsP5g9xQeXuBm1wXjRX8pjUtO"), + Username: "test", + UserId: "test", + } + + createReq := &api.CreatePasswordReq{ + Password: &p, + } + + // Create password. + if resp, err := client.CreatePassword(context.TODO(), createReq); err != nil || resp.AlreadyExists { + if resp.AlreadyExists { + log.Fatalf("Password %s already exists", createReq.Password.Email) + } + log.Fatalf("failed to create password: %v", err) + } else { + log.Printf("Created password with email %s", createReq.Password.Email) + } + + // List all passwords. + resp, err := client.ListPasswords(context.TODO(), &api.ListPasswordReq{}) + if err != nil { + log.Fatalf("failed to list password: %v", err) + } + + log.Print("Listing Passwords:\n") + for _, pass := range resp.Passwords { + log.Printf("%+v", pass) + } + + deleteReq := &api.DeletePasswordReq{ + Email: p.Email, + } + + // Delete password with email = test@example.com. + if resp, err := client.DeletePassword(context.TODO(), deleteReq); err != nil || resp.NotFound { + if resp.NotFound { + log.Fatalf("Password %s not found", deleteReq.Email) + } + log.Fatalf("failed to delete password: %v", err) + } else { + log.Printf("Deleted password with email %s", deleteReq.Email) + } +} diff --git a/examples/grpc-client/config.yaml b/examples/grpc-client/config.yaml new file mode 100644 index 00000000..f5cf8e14 --- /dev/null +++ b/examples/grpc-client/config.yaml @@ -0,0 +1,24 @@ +issuer: http://127.0.0.1:5556/dex + +storage: + type: sqlite3 + config: + file: examples/dex.db + +# Configuration for the HTTP endpoints. +web: + http: 0.0.0.0:5556 + +grpc: + addr: 127.0.0.1:5557 + tlsCert: server.crt + tlsKey: server.key + +connectors: +- type: mockCallback + id: mock + name: Example + +# Let dex keep a list of passwords which can be used to login to dex. +enablePasswordDB: true + diff --git a/examples/grpc-client/openssl.conf b/examples/grpc-client/openssl.conf new file mode 100644 index 00000000..e44be0eb --- /dev/null +++ b/examples/grpc-client/openssl.conf @@ -0,0 +1,82 @@ +# OpenSSL configuration file. +# Adapted from https://github.com/coreos/matchbox/blob/master/examples/etc/matchbox/openssl.conf + +# default environment variable values +SAN = + +[ ca ] +# `man ca` +default_ca = CA_default + +[ CA_default ] +# Directory and file locations. +dir = . +certs = $dir/certs +crl_dir = $dir/crl +new_certs_dir = $dir/newcerts +database = $dir/index.txt +serial = $dir/serial +# certificate revocation lists. +crlnumber = $dir/crlnumber +crl = $dir/crl/intermediate-ca.crl +crl_extensions = crl_ext +default_crl_days = 30 +default_md = sha256 + +name_opt = ca_default +cert_opt = ca_default +default_days = 375 +preserve = no +policy = policy_loose + +[ policy_loose ] +# Allow the CA to sign a range of certificates. +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +# `man req` +default_bits = 4096 +distinguished_name = req_distinguished_name +string_mask = utf8only +default_md = sha256 + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +stateOrProvinceName = State or Province Name +localityName = Locality Name +0.organizationName = Organization Name +organizationalUnitName = Organizational Unit Name +commonName = Common Name + +# Certificate extensions (`man x509v3_config`) + +[ v3_ca ] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer +basicConstraints = critical, CA:true, pathlen:0 +keyUsage = critical, digitalSignature, cRLSign, keyCertSign + +[ usr_cert ] +basicConstraints = CA:FALSE +nsCertType = client +nsComment = "OpenSSL Generated Client Certificate" +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer +keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth + +[ server_cert ] +basicConstraints = CA:FALSE +nsCertType = server +nsComment = "OpenSSL Generated Server Certificate" +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always +keyUsage = critical, digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth +subjectAltName = $ENV::SAN