docker-flow / docker-flow-proxy

Docker Flow Proxy
https://docker-flow.github.io/docker-flow-proxy/
MIT License
315 stars 189 forks source link

Client Ceritificate verification for one service only #74

Closed remy-tiitre closed 2 years ago

remy-tiitre commented 5 years ago

It is probably not possible right now. First 2 things that pop into mind is that verifyClientSsl and addReqHeader, setReqHeader do not support indexes. So either all services require client sertificate or none of them do.

But I have a deeper problem, what if I want to implement it like its done in plain HAProxy frontend https-in bind *:443 ssl crt /etc/haproxy/cert/server.pem ca-file /etc/haproxy/cert/ca.pem verify optional crt-ignore-err all crl-file /etc/haproxy/cert/root_crl.pem use_backend ssl-error unless { ssl_c_verify 0 } use_backend sso-with-ca-cert if { ssl_fc_has_crt } default_backend sso-no-ca-cert

backend sso-with-ca-cert http-request set-header X-SSL-Client-Verify %[ssl_c_verify] http-request set-header X-Client-Certificate %[ssl_c_der,base64] http-request set-header X-Forwarded-Host %[dst] http-request set-header X-Forwarded-Port %[dst_port] http-request set-header X-Forwarded-Proto https ... backend sso-no-ca-cert ...

backend ssl-error option http-server-close redirect location /certificate-expired.html if { ssl_c_verify 10 } ! { path /certificate-expired.html } redirect location /certificate-revoked.html if { ssl_c_verify 23 } ! { path /certificate-revoked.html } redirect location /other-certificate-error.html unless { ssl_c_verify 0 } ! { path //other-certificate-error.html } ...

Example https://raymii.org/s/tutorials/haproxy_client_side_ssl_certificates.html Notice how theres different options depending how the client certification validation went.

thomasjpfan commented 5 years ago

Currently, DFP does not support this use case. This would take a little bit of thinking to iron out the edge cases to implement this feature.

remy-tiitre commented 5 years ago

How and when are these backend templates used in /cfg/tmpl folder? I understand that haproxy.tmpl is the base for everything else. All other templates are merged together there. But what about those service based templates? I see a convention that for every service theres servise-[be|fe]*.cfg file. All fe files are empty. when I manually create files there, will they be picked up? Lets say that I add docker configuration object that maps to /cfg/tmpl/some_service_name-be.cfg, will it be added to haproxy? And why are there those -fe files? I'm trying to figure out if I could get away with be/fe config file templates or I have to modify this haproxy.tmpl. Last one would be a little more annoing as I would have to check your modifications to it every time I update the docker image.

thomasjpfan commented 5 years ago

How and when are these backend templates used in /cfg/tmpl folder?

The templates are used during startup and every-time a service is updated or removed.

when I manually create files there, will they be picked up?

If you create the files there with a configuration object, DFP would try to write to these paths, which may error out, since usually configuration files are not writable.

And why are there those -fe files?

From my understanding, they are not normally empty. In http mode these should contain the use_backend to direct request to the corresponding backend.

I'm trying to figure out if I could get away with be/fe config file templates or I have to modify this haproxy.tmpl.

I am open to changing how DFP does templating to open it up to your use case. You can already add your desired binding by setting DEFAULT_PORTS=443 ssl crt /etc/haproxy/cert/server.pem ca-file /etc/haproxy/cert/ca.pem verify optional crt-ignore-err all crl-file /etc/haproxy/cert/root_crl.pem, and service specific backends by setting com.df.backendExtra. What is missing is allow the user to define a custom use_backend for each service, in your case you need use_backend ssl-error unless { ssl_c_verify 0 }. This would involve updating DFP to support com.df.useBackendRules=unless { ssl_c_verify 0 }.

remy-tiitre commented 5 years ago

My usecase is probably too complicated for DFP to handle with environment variables and service labels only. Though, if the approach were like OpenShift is doing it, then probably it would be a little easier. First they have 3 different frontends -> http, https and custom ports. But what I ended up is modifying the template to something like this:

...
listen https_in
    bind *:443
    mode tcp

    acl clienthello req_ssl_hello_type 1
    tcp-request inspect-delay 5s
    tcp-request content accept if clienthello

    # Deny clients not sending an SNI header in 5 seconds
    tcp-request content reject

    acl require_client_certificate req.ssl_sni -i some.domain.where.i.want.client.certificate

    use-server tls_client_certificate if require_client_certificate
    use-server tls_default unless require_client_certificate

    server tls_client_certificate abns@tls_client_certificate send-proxy-v2
    server tls_default abns@tls_default send-proxy-v2

frontend tls_client_certificate
    bind abns@tls_client_certificate accept-proxy ssl strict-sni crt-list /cfg/crt-list.txt ca-file /cfg/cert.some.nice-ca.pem verify optional crt-ignore-err all alpn h2,http/1.1
    mode {{.DefaultReqMode}}
    {{- if eq .DefaultReqMode "http" }}
    option forwardfor
    {{- end}}

    acl client_cert_verified ssl_c_used ssl_c_verify 0
    use_backend some_backend_with_client_cert-be if client_cert_verified
    default_backend some_backend_when_cert_fails-be

frontend tls_default{{.DefaultBinds}}
    bind abns@tls_default accept-proxy ssl strict-sni crt-list /cfg/crt-list.txt alpn h2,http/1.1
    mode {{.DefaultReqMode}}
    {{- if eq .DefaultReqMode "http" }}
    option forwardfor
    {{- end}}

{{.ExtraFrontend}}{{.ContentFrontend}}{{.ContentFrontendTcp}}{{.ContentFrontendSNI}}{{.ContentListen}}

backend some_backend_with_client_cert-be
    http-request set-header X-Forwarded-Host some.domain.where.i.want.client.certificate
    http-request set-header X-Forwarded-Port %[dst_port]
    http-request set-header X-Forwarded-Proto https if { ssl_fc }
    http-request set-header X-SSL-Client-Verify %[ssl_c_verify]
    http-request set-header X-Client-Certificate %[ssl_c_der,base64]
    http-request set-header X-Real-IP %[src]
    server some_nice_backend some_nice_backend:8080

backend some_backend_when_cert_fails-be
    option http-server-close
    http-request set-header X-Forwarded-Host authid.lhv.ee.prelive
    http-request set-header X-Forwarded-Port %[dst_port]
    http-request set-header X-Forwarded-Proto https if { ssl_fc }
    http-request set-header X-SSL-Client-Verify %[ssl_c_verify]
    http-request set-header X-Real-IP %[src]
    server some_nice_backend some_nice_backend:8080

Main problem is with {{.DefaultBinds}} variable, as it has all the different bind information in one variable. So I was forced to add DEFAULT_PORTS=80 environment variable and hardcode the CA_FILE location.

vfarcic commented 5 years ago

This project needs adoption. I moved to Kubernetes and cannot dedicate time to this project anymore. Similarly, involvement from other contributors dropped as well. Please consider contributing yourself if you think this project is useful.

lle0x commented 2 years ago

Dear @remy-tiitre

If this issue is still relevant, please feel free to leave a comment here.

lle0x commented 2 years ago

Closed due to inactivity