Skip to content

Commit

Permalink
Add ssl/client/i/dn resource. Closes #113
Browse files Browse the repository at this point in the history
  • Loading branch information
Héctor Hurtado committed Sep 10, 2020
1 parent 6d24cc4 commit a17cc48
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 2 deletions.
26 changes: 26 additions & 0 deletions docs/source/concepts/resource_tree.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ Overview
│ │ └──── content The contents of the file uploaded in the form field <name>
│ └──── body HTTP request body
|─ ssl
│ └──── client
│ └──── i
│ └──── dn Subject's DN common name coming ina request through SSL with mTLS
│─ route
│ └──── id Id of the route that matched this request.
Expand Down Expand Up @@ -383,6 +387,28 @@ then, when handling the request:
foobar
``/ssl/client/i/dn`` Resource
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The IP address of the host making the incoming request.

Sample Usage
^^^^^^^^^^^^

If the user runs:

.. code-block:: console
$ curl --cacert path/to/CAfile --cert path/to/clientcredentials http://kapow.example:8080
using a client certificate with DN=subject@example.net then, when handling the request:

.. code-block:: console
$ kapow get /ssl/client/i/dn
subject@example.net
``/route/id`` Resource
~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
11 changes: 11 additions & 0 deletions internal/server/data/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,17 @@ func getRequestFileContent(w http.ResponseWriter, r *http.Request, h *model.Hand
}
}

func getSSLClietnDN(w http.ResponseWriter, r *http.Request, h *model.Handler) {
if h.Request.TLS == nil {
httperror.ErrorJSON(w, ResourceItemNotFound, http.StatusNotFound)
} else if h.Request.TLS.VerifiedChains == nil {
httperror.ErrorJSON(w, ResourceItemNotFound, http.StatusNotFound)
} else {
w.Header().Add("Content-Type", "application/octet-stream")
_, _ = w.Write([]byte(h.Request.TLS.VerifiedChains[0][0].Subject.CommonName))
}
}

func getRouteId(w http.ResponseWriter, r *http.Request, h *model.Handler) {
w.Header().Add("Content-Type", "application/octet-stream")
_, _ = w.Write([]byte(h.Route.ID))
Expand Down
105 changes: 104 additions & 1 deletion internal/server/data/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ package data

import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
Expand Down Expand Up @@ -1137,7 +1141,106 @@ func TestGetRequestFileContent500sWhenHandlerRequestErrors(t *testing.T) {
}
}

// DOING #113: /request/ssl/client/i/dn
func TestGetSSLClientDNReturns404IfNotHTTPS(t *testing.T) {
h := model.Handler{
Request: httptest.NewRequest("POST", "/", nil),
Writer: httptest.NewRecorder(),
}
r := httptest.NewRequest("GET", "/not-important-here", nil)
w := httptest.NewRecorder()

getSSLClietnDN(w, r, &h)

if res := w.Result(); res.StatusCode != http.StatusNotFound {
t.Errorf("Status code mismatch. Expected: %d, got: %d", http.StatusNotFound, res.StatusCode)
}
}

func TestGetSSLClientDNReturns404IfHTTPSButNotmTLS(t *testing.T) {
h := model.Handler{
Request: httptest.NewRequest("POST", "https://www.foo.bar:8080/", nil),
Writer: httptest.NewRecorder(),
}
r := httptest.NewRequest("GET", "/not-important-here", nil)
w := httptest.NewRecorder()

getSSLClietnDN(w, r, &h)

if res := w.Result(); res.StatusCode != http.StatusNotFound {
t.Errorf("Status code mismatch. Expected: %d, got: %d", http.StatusNotFound, res.StatusCode)
}
}

func TestGetSSLClientDN200sOnHappyPath(t *testing.T) {
h := model.Handler{
Request: httptest.NewRequest("POST", "https://www.foo.bar:8080/", nil),
Writer: httptest.NewRecorder(),
}
h.Request.TLS.VerifiedChains = [][]*x509.Certificate{{new(x509.Certificate)}}
r := httptest.NewRequest("GET", "/not-important-here", nil)
w := httptest.NewRecorder()

getSSLClietnDN(w, r, &h)

if res := w.Result(); res.StatusCode != http.StatusOK {
t.Errorf("Status code mismatch. Expected: %d, got: %d", http.StatusOK, res.StatusCode)
}
}

func TestGetSSLClientDNSetsOctectStreamContentType(t *testing.T) {
h := model.Handler{
Request: httptest.NewRequest("POST", "https://www.foo.bar:8080/", nil),
Writer: httptest.NewRecorder(),
}
h.Request.TLS.VerifiedChains = [][]*x509.Certificate{{new(x509.Certificate)}}
r := httptest.NewRequest("GET", "/not-important-here", nil)
w := httptest.NewRecorder()

getSSLClietnDN(w, r, &h)

res := w.Result()
if v := res.Header.Get("Content-Type"); v != "application/octet-stream" {
t.Errorf("Status code mismatch. Expected: %q, got: %q", "application/octet-stream", v)
}
}

func mockAuthenticateClient(tls *tls.ConnectionState) error {
fileData, err := ioutil.ReadFile("./testdata/client_chain.crt")
if err != nil {
return fmt.Errorf("Error loading certificates file: %v", err)
}

asn1Data, _ := pem.Decode(fileData)
certs, err := x509.ParseCertificates(asn1Data.Bytes)
if err != nil {
return fmt.Errorf("Error parsing certificates data: %v", err)
}

tls.VerifiedChains = [][]*x509.Certificate{certs}
tls.PeerCertificates = []*x509.Certificate{tls.VerifiedChains[0][0]}

return nil
}

func TestGetSSLClientDNReturnsCorrectDN(t *testing.T) {
h := model.Handler{
Request: httptest.NewRequest("POST", "https://www.foo.bar:8080/", nil),
Writer: httptest.NewRecorder(),
}
if err := mockAuthenticateClient(h.Request.TLS); err != nil {
t.Error(err)
}
r := httptest.NewRequest("GET", "/not-important-here", nil)
w := httptest.NewRecorder()

getSSLClietnDN(w, r, &h)

res := w.Result()

if body, _ := ioutil.ReadAll(res.Body); string(body) != h.Request.TLS.VerifiedChains[0][0].Subject.CommonName {
t.Errorf("Body mismatch. Expected: %q, got: %q", h.Request.TLS.VerifiedChains[0][0].Subject.CommonName, string(body))
}
}

func TestGetRouteId200sOnHappyPath(t *testing.T) {
h := model.Handler{
Expand Down
5 changes: 4 additions & 1 deletion internal/server/data/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ func Run(bindAddr string, wg *sync.WaitGroup) {
{"/handlers/{handlerID}/request/body", "GET", getRequestBody},

// route
//{"/handlers/{handlerID}/route/id", "GET", getRouteId},
{"/handlers/{handlerID}/route/id", "GET", getRouteId},

// SSL stuff
{"/handlers/{handlerID}/ssl/client/i/dn", "GET", getSSLClietnDN},

// response
{"/handlers/{handlerID}/response/status", "PUT", lockResponseWriter(setResponseStatus)},
Expand Down
46 changes: 46 additions & 0 deletions internal/server/data/testdata/client_chain.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
-----BEGIN CERTIFICATE-----
MIIDdTCCAl0CAQIwDQYJKoZIhvcNAQELBQAwgZ8xCzAJBgNVBAYTAkVTMQ8wDQYD
VQQIDAZNYWRyaWQxDzANBgNVBAcMBk1hZHJpZDENMAsGA1UECgwEQkJWQTEYMBYG
A1UECwwPSW5ub3ZhdGlvbiBMYWJzMR0wGwYDVQQDDBRTZWN1cml0eS1DQS5iYnZh
LmNvbTEmMCQGCSqGSIb3DQEJARYXc2VjdXJpdHkuZ3JvdXBAYmJ2YS5jb20wHhcN
MjAwMTIzMTQwODUxWhcNMjEwMTIyMTQwODUxWjBhMQswCQYDVQQGEwJFUzEPMA0G
A1UECAwGTWFkcmlkMQ0wCwYDVQQKDARCQlZBMRgwFgYDVQQLDA9Jbm5vdmF0aW9u
IExhYnMxGDAWBgNVBAMMD0thcG93ISBjbGllbnQgMTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAJKXoqOe0S1i8c0bDLGsvibSpDmWkb/2oXn4qn8XtlLF
PSY69qeqkeLZov0nVV6zenag9Vh99uy7M4kw/pFqYv1eViDvV6I0wyKEzXNyeQmL
O11YUWfP38T+Usw0JY0Pau+ewSQkurtHmGRWC5fAgPxiyi03hD3V3eHPR60V5yE6
1wYoLqz6xJ7nkXVyVLdg6wekrMkpos3ciA3Roco4m5fbXbVGYrx8E97byT9yyKzI
kvFjQ0T4+67dPuWW+juoD0lOwfNu1WtY4PPnkUuzjUftKzD1t/a4zsLcFLafuuVR
f4qb21o2pqDOWxMMZGrceS5uEF0nI1wtdw+XMtFHdFcCAwEAATANBgkqhkiG9w0B
AQsFAAOCAQEAsnPW8pCIiejBwjQ4TTNPo5wiRNOib69ANj2lHE1gidO8HA29/ssF
U7jbcxCQf0/flv+JddnSJzmeFhrt15CL6nOZ1whSqVA1W1dAno0RYNiPUILofq50
zKNUVF+eYz24nksdI87d9j1Zri2H91p+gA1pBnIxBE8zgXZ5+u7FUrA41HOuVyAy
55EwUDloVg4WBeddb8Y/mPgXNHS7ZB0Z13+bLHeSkSWWV3Gw1OtLHJEv/j+/9K5O
KoJCyO4xSkKP7/nYYKCed4grIfOAu7iHqN/Ok9yuAOm0tbgwKmzw9EGga82YCH0M
jfd8wfVCqiZvW9SUa11fM/Np9/+04QtlNA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEITCCAwmgAwIBAgIUMAqlEXi1gcpy97bWApqtBwgqEvgwDQYJKoZIhvcNAQEL
BQAwgZ8xCzAJBgNVBAYTAkVTMQ8wDQYDVQQIDAZNYWRyaWQxDzANBgNVBAcMBk1h
ZHJpZDENMAsGA1UECgwEQkJWQTEYMBYGA1UECwwPSW5ub3ZhdGlvbiBMYWJzMR0w
GwYDVQQDDBRTZWN1cml0eS1DQS5iYnZhLmNvbTEmMCQGCSqGSIb3DQEJARYXc2Vj
dXJpdHkuZ3JvdXBAYmJ2YS5jb20wHhcNMjAwMTIyMTcxNzUwWhcNMjAwMjIxMTcx
NzUwWjCBnzELMAkGA1UEBhMCRVMxDzANBgNVBAgMBk1hZHJpZDEPMA0GA1UEBwwG
TWFkcmlkMQ0wCwYDVQQKDARCQlZBMRgwFgYDVQQLDA9Jbm5vdmF0aW9uIExhYnMx
HTAbBgNVBAMMFFNlY3VyaXR5LUNBLmJidmEuY29tMSYwJAYJKoZIhvcNAQkBFhdz
ZWN1cml0eS5ncm91cEBiYnZhLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAMM8xmTpUg2PXV6zPohGOKLiXHP3nQMfkoYLRwFYcKq0WnYmaKmIK5T0
gCLmiQMCNv/NV8+aIkd2HuTPBnobFLbyUCN9yf2Gj81wxeRydBwbjaj0dpB1Jx9W
5OdMBFxGIKmnqVN/z784Ma9cj+tv0t5LYpWIxnEgnBaiuMkQnwJFyv6aL1VNRmW7
zFMAqOiMishMKb/0UaSW53ZBFjCqmhquZ7CZYLsaB+mMDv1fOXf8jSX/WrcCCkvQ
HX04/9HxNVIe3a0Zl8CPfyUOd9njVl1VRDgjljUyMeMM4zo31enpwWOuhpfI6jU5
AB7If6xufHyN8FCvKpDy9Z9Sp5Ww1o8CAwEAAaNTMFEwHQYDVR0OBBYEFOfc0yly
jUPuoy54Ods9KKt5CITsMB8GA1UdIwQYMBaAFOfc0ylyjUPuoy54Ods9KKt5CITs
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBALvzf14HF78rowNA
XuczbkgKLbNJam0YC3ZxoB/pxcwfSZCf4E7+FAWKfmwrNqZv2PweOvDfP4Rx4T1x
VLWDr0qtcsol7gOPga8HD/1zTgK096rYs5pCxQabgIpmHhzDUnjBrQyds8U4sakP
3xzuy/eZ/ozDzCGZn8HzqYHDTcEfypMSdZUUDgN4vVE3Il3AZRSEG9X/Ov4W8tLF
dofY/JDVObXV0DAms9xcux8BfAslh8NNytP3q+uneIeuJT/eRQu+Z5GRB+mbL8X6
DmXeSSkMiVeFgD1qg9VZVSBsihBpWpJakcxvXOOj5fY2t5ovW6TR2jDjW5XjHs82
idlvJUw=
-----END CERTIFICATE-----

0 comments on commit a17cc48

Please sign in to comment.