mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 02:02:43 +00:00
Add support for forwarded Tls-Client-Cert (#17272)
* Add support for x_forwarded_for_client_cert_header * add changelog entry * add tests for a badly and properly formatted certs * both conditions should be true * handle case where r.TLS is nil * prepend client_certs to PeerCertificates list * Add support for x_forwarded_for_client_cert_header * add changelog entry * add tests for a badly and properly formatted certs * both conditions should be true * handle case where r.TLS is nil * prepend client_certs to PeerCertificates list * add option for decoders to handle different proxies * Add support for x_forwarded_for_client_cert_header * add changelog entry * add tests for a badly and properly formatted certs * both conditions should be true * handle case where r.TLS is nil * prepend client_certs to PeerCertificates list * add option for decoders to handle different proxies * fix tests * fix typo --------- Co-authored-by: Alexander Scheel <alex.scheel@hashicorp.com> Co-authored-by: Scott Miller <smiller@hashicorp.com> Co-authored-by: Violet Hynes <violet.hynes@hashicorp.com>
This commit is contained in:
3
changelog/17272.txt
Normal file
3
changelog/17272.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
```release-note:improvement
|
||||||
|
auth/cert: Adds support for TLS certificate authenticaion through a reverse proxy that terminates the SSL connection
|
||||||
|
```
|
||||||
@@ -64,6 +64,14 @@ func tcpListenerFactory(l *configutil.Listener, _ io.Writer, ui cli.Ui) (net.Lis
|
|||||||
if len(l.XForwardedForAuthorizedAddrs) > 0 {
|
if len(l.XForwardedForAuthorizedAddrs) > 0 {
|
||||||
props["x_forwarded_for_reject_not_authorized"] = strconv.FormatBool(l.XForwardedForRejectNotAuthorized)
|
props["x_forwarded_for_reject_not_authorized"] = strconv.FormatBool(l.XForwardedForRejectNotAuthorized)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(l.XForwardedForAuthorizedAddrs) > 0 {
|
||||||
|
props["x_forwarded_for_client_cert_header"] = fmt.Sprintf("%s", l.XForwardedForClientCertHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(l.XForwardedForAuthorizedAddrs) > 0 {
|
||||||
|
props["x_forwarded_for_client_cert_header_decoders"] = fmt.Sprintf("%s", l.XForwardedForClientCertHeaderDecoders)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig, reloadFunc, err := listenerutil.TLSConfig(l, props, ui)
|
tlsConfig, reloadFunc, err := listenerutil.TLSConfig(l, props, ui)
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ package http
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -255,4 +257,80 @@ func TestHandler_XForwardedFor(t *testing.T) {
|
|||||||
t.Fatalf("bad body: %s", buf.String())
|
t.Fatalf("bad body: %s", buf.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Next: test an invalid certificate being sent
|
||||||
|
t.Run("reject_bad_cert_in_header", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testHandler := func(props *vault.HandlerProperties) http.Handler {
|
||||||
|
origHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(r.RemoteAddr))
|
||||||
|
})
|
||||||
|
listenerConfig := getListenerConfigForMarshalerTest(goodAddr)
|
||||||
|
listenerConfig.XForwardedForClientCertHeader = "X-Forwarded-Tls-Client-Cert"
|
||||||
|
listenerConfig.XForwardedForClientCertHeaderDecoders = "URL,BASE64"
|
||||||
|
return WrapForwardedForHandler(origHandler, listenerConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
|
||||||
|
HandlerFunc: HandlerFunc(testHandler),
|
||||||
|
})
|
||||||
|
cluster.Start()
|
||||||
|
defer cluster.Cleanup()
|
||||||
|
client := cluster.Cores[0].Client
|
||||||
|
|
||||||
|
req := client.NewRequest("GET", "/")
|
||||||
|
req.Headers = make(http.Header)
|
||||||
|
req.Headers.Set("x-forwarded-for", "5.6.7.8")
|
||||||
|
req.Headers.Set("x-forwarded-tls-client-cert", `BAD_TEXTMIIDtTCCAp2gAwIBAgIUf%2BjhKTFBnqSs34II0WS1L4QsbbAwDQYJKoZIhvcNAQEL%0ABQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzQxWhcNMjUw%0AMTA1MTAyODExWjAbMRkwFwYDVQQDExBjZXJ0LmV4YW1wbGUuY29tMIIBIjANBgkq%0AhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsZx0Svr82YJpFpIy4fJNW5fKA6B8mhxS%0ATRAVnygAftetT8puHflY0ss7Y6X2OXjsU0PRn%2B1PswtivhKi%2BeLtgWkUF9cFYFGn%0ASgMld6ZWRhNheZhA6ZfQmeM%2FBF2pa5HK2SDF36ljgjL9T%2BnWrru2Uv0BCoHzLAmi%0AYYMiIWplidMmMO5NTRG3k%2B3AN0TkfakB6JVzjLGhTcXdOcVEMXkeQVqJMAuGouU5%0AdonyqtnaHuIJGuUdy54YDnX86txhOQhAv6r7dHXzZxS4pmLvw8UI1rsSf%2FGLcUVG%0AB%2B5%2BAAGF5iuHC3N2DTl4xz3FcN4Cb4w9pbaQ7%2BmCzz%2BanqiJfyr2nwIDAQABo4H1%0AMIHyMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUm%2B%2Be%0AHpyM3p708bgZJuRYEdX1o%2BUwHwYDVR0jBBgwFoAUncSzT%2F6HMexyuiU9%2F7EgHu%2Bo%0Ak5swOwYIKwYBBQUHAQEELzAtMCsGCCsGAQUFBzAChh9odHRwOi8vMTI3LjAuMC4x%0AOjgyMDAvdjEvcGtpL2NhMCEGA1UdEQQaMBiCEGNlcnQuZXhhbXBsZS5jb22HBH8A%0AAAEwMQYDVR0fBCowKDAmoCSgIoYgaHR0cDovLzEyNy4wLjAuMTo4MjAwL3YxL3Br%0AaS9jcmwwDQYJKoZIhvcNAQELBQADggEBABsuvmPSNjjKTVN6itWzdQy%2BSgMIrwfs%0AX1Yb9Lefkkwmp9ovKFNQxa4DucuCuzXcQrbKwWTfHGgR8ct4rf30xCRoA7dbQWq4%0AaYqNKFWrRaBRAaaYZ%2FO1ApRTOrXqRx9Eqr0H1BXLsoAq%2BmWassL8sf6siae%2BCpwA%0AKqBko5G0dNXq5T4i2LQbmoQSVetIrCJEeMrU%2BidkuqfV2h1BQKgSEhFDABjFdTCN%0AQDAHsEHsi2M4%2FjRW9fqEuhHSDfl2n7tkFUI8wTHUUCl7gXwweJ4qtaSXIwKXYzNj%0AxqKHA8Purc1Yfybz4iE1JCROi9fInKlzr5xABq8nb9Qc%2FJ9DIQM%2BXmk%3D`)
|
||||||
|
resp, err := client.RawRequest(req)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
buf.ReadFrom(resp.Body)
|
||||||
|
if !strings.Contains(buf.String(), "failed to base64 decode the client certificate: ") {
|
||||||
|
t.Fatalf("bad body: %v", buf.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Next: test a valid (unverified) certificate being sent
|
||||||
|
t.Run("pass_cert", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testHandler := func(props *vault.HandlerProperties) http.Handler {
|
||||||
|
origHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(base64.StdEncoding.EncodeToString(r.TLS.PeerCertificates[0].Raw)))
|
||||||
|
})
|
||||||
|
listenerConfig := getListenerConfigForMarshalerTest(goodAddr)
|
||||||
|
listenerConfig.XForwardedForClientCertHeader = "X-Forwarded-Tls-Client-Cert"
|
||||||
|
listenerConfig.XForwardedForClientCertHeaderDecoders = "URL,BASE64"
|
||||||
|
return WrapForwardedForHandler(origHandler, listenerConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
|
||||||
|
HandlerFunc: HandlerFunc(testHandler),
|
||||||
|
})
|
||||||
|
cluster.Start()
|
||||||
|
defer cluster.Cleanup()
|
||||||
|
client := cluster.Cores[0].Client
|
||||||
|
|
||||||
|
req := client.NewRequest("GET", "/")
|
||||||
|
req.Headers = make(http.Header)
|
||||||
|
req.Headers.Set("x-forwarded-for", "5.6.7.8")
|
||||||
|
testcertificate := `MIIDtTCCAp2gAwIBAgIUf%2BjhKTFBnqSs34II0WS1L4QsbbAwDQYJKoZIhvcNAQEL%0ABQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzQxWhcNMjUw%0AMTA1MTAyODExWjAbMRkwFwYDVQQDExBjZXJ0LmV4YW1wbGUuY29tMIIBIjANBgkq%0AhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsZx0Svr82YJpFpIy4fJNW5fKA6B8mhxS%0ATRAVnygAftetT8puHflY0ss7Y6X2OXjsU0PRn%2B1PswtivhKi%2BeLtgWkUF9cFYFGn%0ASgMld6ZWRhNheZhA6ZfQmeM%2FBF2pa5HK2SDF36ljgjL9T%2BnWrru2Uv0BCoHzLAmi%0AYYMiIWplidMmMO5NTRG3k%2B3AN0TkfakB6JVzjLGhTcXdOcVEMXkeQVqJMAuGouU5%0AdonyqtnaHuIJGuUdy54YDnX86txhOQhAv6r7dHXzZxS4pmLvw8UI1rsSf%2FGLcUVG%0AB%2B5%2BAAGF5iuHC3N2DTl4xz3FcN4Cb4w9pbaQ7%2BmCzz%2BanqiJfyr2nwIDAQABo4H1%0AMIHyMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUm%2B%2Be%0AHpyM3p708bgZJuRYEdX1o%2BUwHwYDVR0jBBgwFoAUncSzT%2F6HMexyuiU9%2F7EgHu%2Bo%0Ak5swOwYIKwYBBQUHAQEELzAtMCsGCCsGAQUFBzAChh9odHRwOi8vMTI3LjAuMC4x%0AOjgyMDAvdjEvcGtpL2NhMCEGA1UdEQQaMBiCEGNlcnQuZXhhbXBsZS5jb22HBH8A%0AAAEwMQYDVR0fBCowKDAmoCSgIoYgaHR0cDovLzEyNy4wLjAuMTo4MjAwL3YxL3Br%0AaS9jcmwwDQYJKoZIhvcNAQELBQADggEBABsuvmPSNjjKTVN6itWzdQy%2BSgMIrwfs%0AX1Yb9Lefkkwmp9ovKFNQxa4DucuCuzXcQrbKwWTfHGgR8ct4rf30xCRoA7dbQWq4%0AaYqNKFWrRaBRAaaYZ%2FO1ApRTOrXqRx9Eqr0H1BXLsoAq%2BmWassL8sf6siae%2BCpwA%0AKqBko5G0dNXq5T4i2LQbmoQSVetIrCJEeMrU%2BidkuqfV2h1BQKgSEhFDABjFdTCN%0AQDAHsEHsi2M4%2FjRW9fqEuhHSDfl2n7tkFUI8wTHUUCl7gXwweJ4qtaSXIwKXYzNj%0AxqKHA8Purc1Yfybz4iE1JCROi9fInKlzr5xABq8nb9Qc%2FJ9DIQM%2BXmk%3D`
|
||||||
|
req.Headers.Set("x-forwarded-tls-client-cert", testcertificate)
|
||||||
|
resp, err := client.RawRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
buf.ReadFrom(resp.Body)
|
||||||
|
testcertificate, _ = url.QueryUnescape(testcertificate)
|
||||||
|
if !strings.Contains(buf.String(), strings.ReplaceAll(testcertificate, "\n", "")) {
|
||||||
|
t.Fatalf("bad body: %v vs %v", buf.String(), testcertificate)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ package http
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -507,6 +510,8 @@ func WrapForwardedForHandler(h http.Handler, l *configutil.Listener) http.Handle
|
|||||||
hopSkips := l.XForwardedForHopSkips
|
hopSkips := l.XForwardedForHopSkips
|
||||||
authorizedAddrs := l.XForwardedForAuthorizedAddrs
|
authorizedAddrs := l.XForwardedForAuthorizedAddrs
|
||||||
rejectNotAuthz := l.XForwardedForRejectNotAuthorized
|
rejectNotAuthz := l.XForwardedForRejectNotAuthorized
|
||||||
|
clientCertHeader := l.XForwardedForClientCertHeader
|
||||||
|
clientCertHeaderDecoders := l.XForwardedForClientCertHeaderDecoders
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
headers, headersOK := r.Header[textproto.CanonicalMIMEHeaderKey("X-Forwarded-For")]
|
headers, headersOK := r.Header[textproto.CanonicalMIMEHeaderKey("X-Forwarded-For")]
|
||||||
if !headersOK || len(headers) == 0 {
|
if !headersOK || len(headers) == 0 {
|
||||||
@@ -589,6 +594,60 @@ func WrapForwardedForHandler(h http.Handler, l *configutil.Listener) http.Handle
|
|||||||
}
|
}
|
||||||
|
|
||||||
r.RemoteAddr = net.JoinHostPort(acc[indexToUse], port)
|
r.RemoteAddr = net.JoinHostPort(acc[indexToUse], port)
|
||||||
|
|
||||||
|
// Import the Client Certificate forwarded by the reverse proxy
|
||||||
|
// There should be only 1 instance of the header, but looping allows for more flexibility
|
||||||
|
clientCertHeaders, clientCertHeadersOK := r.Header[textproto.CanonicalMIMEHeaderKey(clientCertHeader)]
|
||||||
|
if clientCertHeadersOK && len(clientCertHeaders) > 0 {
|
||||||
|
var client_certs []*x509.Certificate
|
||||||
|
for _, header := range clientCertHeaders {
|
||||||
|
// Multiple certs should be comma delimetered
|
||||||
|
vals := strings.Split(header, ",")
|
||||||
|
for _, v := range vals {
|
||||||
|
actions := strings.Split(clientCertHeaderDecoders, ",")
|
||||||
|
for _, action := range actions {
|
||||||
|
switch action {
|
||||||
|
case "URL":
|
||||||
|
decoded, err := url.QueryUnescape(v)
|
||||||
|
if err != nil {
|
||||||
|
respondError(w, http.StatusBadRequest, fmt.Errorf("failed to url unescape the client certificate: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v = decoded
|
||||||
|
case "BASE64":
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(v)
|
||||||
|
if err != nil {
|
||||||
|
respondError(w, http.StatusBadRequest, fmt.Errorf("failed to base64 decode the client certificate: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v = string(decoded[:])
|
||||||
|
case "DER":
|
||||||
|
decoded, _ := pem.Decode([]byte(v))
|
||||||
|
if decoded == nil {
|
||||||
|
respondError(w, http.StatusBadRequest, fmt.Errorf("failed to convert the client certificate to DER format: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v = string(decoded.Bytes[:])
|
||||||
|
default:
|
||||||
|
respondError(w, http.StatusBadRequest, fmt.Errorf("unknown decode option specified: %s", action))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := x509.ParseCertificate([]byte(v))
|
||||||
|
if err != nil {
|
||||||
|
respondError(w, http.StatusBadRequest, fmt.Errorf("failed to parse the client certificate: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client_certs = append(client_certs, cert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.TLS == nil {
|
||||||
|
respondError(w, http.StatusBadRequest, fmt.Errorf("Server must use TLS for certificate authentication"))
|
||||||
|
} else {
|
||||||
|
r.TLS.PeerCertificates = append(client_certs, r.TLS.PeerCertificates...)
|
||||||
|
}
|
||||||
|
}
|
||||||
h.ServeHTTP(w, r)
|
h.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,14 +94,16 @@ type Listener struct {
|
|||||||
ProxyProtocolAuthorizedAddrs []*sockaddr.SockAddrMarshaler `hcl:"-"`
|
ProxyProtocolAuthorizedAddrs []*sockaddr.SockAddrMarshaler `hcl:"-"`
|
||||||
ProxyProtocolAuthorizedAddrsRaw interface{} `hcl:"proxy_protocol_authorized_addrs,alias:ProxyProtocolAuthorizedAddrs"`
|
ProxyProtocolAuthorizedAddrsRaw interface{} `hcl:"proxy_protocol_authorized_addrs,alias:ProxyProtocolAuthorizedAddrs"`
|
||||||
|
|
||||||
XForwardedForAuthorizedAddrs []*sockaddr.SockAddrMarshaler `hcl:"-"`
|
XForwardedForAuthorizedAddrs []*sockaddr.SockAddrMarshaler `hcl:"-"`
|
||||||
XForwardedForAuthorizedAddrsRaw interface{} `hcl:"x_forwarded_for_authorized_addrs,alias:XForwardedForAuthorizedAddrs"`
|
XForwardedForAuthorizedAddrsRaw interface{} `hcl:"x_forwarded_for_authorized_addrs,alias:XForwardedForAuthorizedAddrs"`
|
||||||
XForwardedForHopSkips int64 `hcl:"-"`
|
XForwardedForHopSkips int64 `hcl:"-"`
|
||||||
XForwardedForHopSkipsRaw interface{} `hcl:"x_forwarded_for_hop_skips,alias:XForwardedForHopSkips"`
|
XForwardedForHopSkipsRaw interface{} `hcl:"x_forwarded_for_hop_skips,alias:XForwardedForHopSkips"`
|
||||||
XForwardedForRejectNotPresent bool `hcl:"-"`
|
XForwardedForRejectNotPresent bool `hcl:"-"`
|
||||||
XForwardedForRejectNotPresentRaw interface{} `hcl:"x_forwarded_for_reject_not_present,alias:XForwardedForRejectNotPresent"`
|
XForwardedForRejectNotPresentRaw interface{} `hcl:"x_forwarded_for_reject_not_present,alias:XForwardedForRejectNotPresent"`
|
||||||
XForwardedForRejectNotAuthorized bool `hcl:"-"`
|
XForwardedForRejectNotAuthorized bool `hcl:"-"`
|
||||||
XForwardedForRejectNotAuthorizedRaw interface{} `hcl:"x_forwarded_for_reject_not_authorized,alias:XForwardedForRejectNotAuthorized"`
|
XForwardedForRejectNotAuthorizedRaw interface{} `hcl:"x_forwarded_for_reject_not_authorized,alias:XForwardedForRejectNotAuthorized"`
|
||||||
|
XForwardedForClientCertHeader string `hcl:"x_forwarded_for_client_cert_header,alias:XForwardedForClientCertHeader"`
|
||||||
|
XForwardedForClientCertHeaderDecoders string `hcl:"x_forwarded_for_client_cert_header_decoders,alias:XForwardedForClientCertHeaderDecoders"`
|
||||||
|
|
||||||
SocketMode string `hcl:"socket_mode"`
|
SocketMode string `hcl:"socket_mode"`
|
||||||
SocketUser string `hcl:"socket_user"`
|
SocketUser string `hcl:"socket_user"`
|
||||||
|
|||||||
@@ -220,6 +220,26 @@ default value in the `"/sys/config/ui"` [API endpoint](/vault/api-docs/system/co
|
|||||||
connecting client's IP, for example `3.4.5.6`. Note this requires the load balancer
|
connecting client's IP, for example `3.4.5.6`. Note this requires the load balancer
|
||||||
to send the connecting client's IP in the `X-Forwarded-For` header.
|
to send the connecting client's IP in the `X-Forwarded-For` header.
|
||||||
|
|
||||||
|
- `x_forwarded_for_client_cert_header` `(string: "")` –
|
||||||
|
Specifies the header that will be used for the client certificate.
|
||||||
|
This is required if you use the [TLS Certificates Auth Method](/docs/auth/cert) and your
|
||||||
|
vault server is behind a reverse proxy.
|
||||||
|
|
||||||
|
- `x_forwarded_for_client_cert_header_decoders` `(string: "")` –
|
||||||
|
Comma delimited list that specifies the decoders that will be used to decode the client certificate.
|
||||||
|
This is required if you use the [TLS Certificates Auth Method](/docs/auth/cert) and your
|
||||||
|
vault server is behind a reverse proxy. The resulting certificate should be in DER format.
|
||||||
|
Available Values:
|
||||||
|
|
||||||
|
- BASE64 - Runs Base64 decode
|
||||||
|
- DER - Converts a pem certificate to der
|
||||||
|
- URL - Runs URL decode
|
||||||
|
|
||||||
|
Known Values:
|
||||||
|
|
||||||
|
- Traefik = "BASE64"
|
||||||
|
- NGINX = "URL,DER"
|
||||||
|
|
||||||
- `x_forwarded_for_hop_skips` `(string: "0")` – The number of addresses that will be
|
- `x_forwarded_for_hop_skips` `(string: "0")` – The number of addresses that will be
|
||||||
skipped from the _rear_ of the set of hops. For instance, for a header value
|
skipped from the _rear_ of the set of hops. For instance, for a header value
|
||||||
of `1.2.3.4, 2.3.4.5, 3.4.5.6, 4.5.6.7`, if this value is set to `"1"`, the address that
|
of `1.2.3.4, 2.3.4.5, 3.4.5.6, 4.5.6.7`, if this value is set to `"1"`, the address that
|
||||||
|
|||||||
Reference in New Issue
Block a user