Documentation
¶
Overview ¶
Package http provides GSSAPI (Negotiate) enabled HTTP client and server implementations supporting RFC 4559.
import (
net/http
ghttp "github.com/golang-auth/go-gssapi/v3/http"
_ "github.com/golang-auth/go-gssapi-c"
)
p, err := gssapi.NewProvider("github.com/golang-auth/go-gssapi-c")
...
Clients and transorts ¶
Create a client to use a default GSSAPI enabled transport. The client can be used anywhere a standard http.Client can be used.
client, err := ghttp.NewClient(p, nil)
...
resp, err := client.Get("https://example.com")
...
req, err := http.NewRequest("GET", "http://example.com", nil)
...
req.Header.Add("If-None-Match", `W/"wyzzy"`)
resp, err := client.Do(req)
...
To control GSSAPI parameters, create a transport:
transport := ghttp.NewTransport(
p,
http.WithOpportunistic(),
http.WithCredential(cred),
)
client := http.Client{Transport: transport}
resp, err := client.Get("https://example.com")
The GSSAPI enabled transport wraps a standard http.RoundTripper. By default it uses http.DefaultTransport. A custom round-tripper can be provided to the transport using WithInitiatorRoundTripper.
Request body handling ¶
For HTTP methods such as POST, PUT, and others that include a request body, the client must send the full body to the server regardless of the server’s response code.
Starting with Go 1.8, the http.Request.GetBody method enables supported request body types to be rewound and resent if the server responds with a 401 Unauthorized challenge.
However, for large request bodies, retransmission can be inefficient.
One way to avoid sending large bodies multiple times is to use the Expect: 100-continue header.
Normal flow:
- The client sends headers first.
- If the server responds with 100 Continue, the client sends the body.
- If the server responds with 401 Unauthorized (or any final status) before sending 100 Continue, the client does not send the body.
This approach saves bandwidth when the request is likely to be challenged, but it depends on correct server implementation of 100-continue semantics.
Why this matters:
Under HTTP/1.1 (RFC 9110, §9), if the client sends a request with a Content-Length header and no Expect: 100-continue, then the server must either read and discard the entire body, or close the connection after sending the response.
This ensures leftover body bytes do not corrupt the interpretation of the next request over the same connection.
With Expect: 100-continue:
- The body is not sent until the server signals 100 Continue.
- If the server rejects early, there are no unread body bytes and the connection protocol stays clean without draining.
Client behavior:
Support for Expect: 100-continue is disabled by default due to implementation concerns with some servers.
It can be enabled by setting a threshold (in bytes) greater than zero.
When enabled, the client will add the header to requests that:
Do not have opportunistic authentication enabled, and
Either have a body size exceeding the threshold, or have a body that is not rewindable via GetBody.
The optimal threshold depends on factors such as network bandwidth, MTU, TLS overhead, and server 100-continue reliability.
The Go net/http server forces the connection to close after the reply when the client requests a 100-continue response.
- RFC view: If Content-Length is set, the client must send the full body, even without 100 Continue.
- Practical view: Closing avoids reading (and discarding) large unused bodies and prevents ambiguity in the TCP stream.
- Impact: When rejecting requests early (for example, 401 Unauthorized), the connection will close, requiring a new one for the retry.
For large request bodies and Negotiate authentication, this is generally preferable to draining the body.
Opportunistic authentication ¶
The GSSAPI enabled transport supports opportunistic authentication as described in RFC 4559 § 4.2 . The client does not wait for the server to respond with a 401 status code before sending an authentication token. This optimization can reduce round trips between the client and server, at the cost of initializing the GSSAPI context and potentially exposing authentication credentials to the server unnecessarily.
Servers ¶
Handler is a http.Handler that performs GSSAPI authentication and then calls the next handler with the initiator name in the request context.
Use Negotiate authentication for a subset of paths:
// Use Negotiate authentication for the /foo path
http.Handle("/foo", ghttp.NewHandler(p, fooHandler))
// but not for /bar
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})
log.Fatal(http.ListenAndServe(":8080", nil))
Use Negotiate authentication for all paths:
h := ghttp.NewHandler(p, http.DefaultServeMux)
log.Fatal(http.ListenAndServe(":8080", h))
Channel binding ¶
Channel binding ties authentication events to the underlying communication channel. Its purpose is to detect man-in-the-middle attacks by cryptographically comparing an aspect of the channel known to both the initator and the acceptor that would be modified by the presence of an unauthorized intermediate.
This library supports TLS endpoint channel binding, which the Kerberos mechanism conveys as part of the AP_REQ authenticator checksum. TLS endpoint channel binding uses a hash of the TLS server certificate as the channel binding data. A man-in-the-middle attacker is presumed to not have access to the private key of the server, so even if the attacker were able to obtain a certificate for the service that is trusted by the client (for example, from a corporate TLS intercepting proxy or a rogue CA), the hash of the certificate presented to the client by the attacker will not match that of the genuine server certificate.
Configuration:
Channel binding can be configured separately for the initiator (client) and acceptor (server). Both must use TLS-protected endpoints.
The initiator decides whether to use channel bindings by setting its channel binding disposition to ChannelBindingDispositionRequire or ChannelBindingDispositionIfAvailable. The acceptor can independently decide whether to support channel binding by setting its channel binding disposition to ChannelBindingDispositionRequire or ChannelBindingDispositionIfAvailable.
Standard behavior (RFC 2743):
Under standard RFC 2743 behavior (without Channel Bound extensions), channel binding only detects mismatches: if both sides specify channel bindings that do not match, the security context establishment fails. If both ends supply matching channel bindings, or only one end supplies them, the context is established successfully and neither end can distinguish between these cases. This is the behavior observed when ChannelBindingDispositionIfAvailable is used.
Channel bound extension:
If the Channel Bound GSSAPI extension is available, the initiator may use ChannelBindingDispositionRequire to signal to the acceptor that it requires channel bindings. This sets a Microsoft Kerberos extension flag in the authenticator, causing the acceptor to require channel bindings from the initiator. If ChannelBindingDispositionRequire is configured on the acceptor, the security context establishment will fail if the initiator does not supply matching channel bindings.
Note: Using ChannelBindingDispositionRequire requires that the GSSAPI provider supports the Channel Bound GSSAPI extension.
Caveats:
There are some caveats to using channel bindings:
Opportunistic authentication with channel bindings is not supported because the server certificate is only available to the client after one round-trip to the server. The request will fail if OpportunisticFunc returns true and channel bindings are configured on the client.
For services backed by a cluster of servers, each server must use exactly the same certificate because the client uses the certificate from the first round-trip to form the channel binding data - and in any case, HTTP is stateless and there is no guarantee that subsequent requests will hit the same server. This makes it difficult to update the certificate across the cluster without causing authentication failures during the switch.
If a load balancer or reverse proxy terminates TLS, it must also use the same certificate as the backend servers. This compounds the problem of updating the certificate across the cluster.
Index ¶
- func GetDelegatedCredential(r *http.Request) (gssapi.Credential, bool)
- func HasChannelBindings(r *http.Request) bool
- func NewClient(provider gssapi.Provider, client *http.Client, options ...ClientOption) (*http.Client, error)
- func ServerWithStashConn(s *http.Server) *http.Server
- func WithHttpTrace(ctx context.Context, trace *HttpTrace) context.Context
- type ChannelBindingDisposition
- type ClientOption
- func WithIniiatorDelegationPolicy(delegationPolicy DelegationPolicy) ClientOption
- func WithInitiatorChannelBindingDisposition(disposition ChannelBindingDisposition) ClientOption
- func WithInitiatorCredential(cred gssapi.Credential) ClientOption
- func WithInitiatorExpect100Threshold(threshold int64) ClientOption
- func WithInitiatorLogFunc(logFunc func(format string, args ...interface{})) ClientOption
- func WithInitiatorMutual() ClientOption
- func WithInitiatorOpportunistic() ClientOption
- func WithInitiatorOpportunisticFunc(opportunisticFunc OpportunisticFunc) ClientOption
- func WithInitiatorRoundTripper(transport http.RoundTripper) ClientOption
- func WithInitiatorSpnFunc(spnFunc SpnFunc) ClientOption
- func WithInititorHttpLogging() ClientOption
- type DelegationPolicy
- type GSSAPITransport
- type Handler
- type HandlerOption
- type HttpTrace
- type InitiatorName
- type OpportunisticFunc
- type SpnFunc
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func GetDelegatedCredential ¶
func GetDelegatedCredential(r *http.Request) (gssapi.Credential, bool)
GetDelegatedCredential returns the delegated credential from the request context if available. This can be used by the 'next' http handler called by Handler.ServeHTTP to retrieve the credential that was delegated by the initiator during GSSAPI authentication. The credential will only be present if the initiator delegated a credential and the context flag ContextFlagDeleg is set.
func HasChannelBindings ¶
HasChannelBindings can be used by a Handler to verify whether the initiator supplied matching channel bindings. The function will only ever return true if the underlying provider supports the Channel Bound extension.
func NewClient ¶
func NewClient(provider gssapi.Provider, client *http.Client, options ...ClientOption) (*http.Client, error)
NewClient returns a http.Client that uses GSSAPITransport to enable GSSAPI authentication.
If an existing client is provided, it will be copied and the http.RoundTripper will be replaced with a new GSSAPITransport that wraps the original transport. Otherwise http.DefaultClient will be used, with a GSSAPITransport that wraps http.DefaultTransport.
Example ¶
package main
import (
"fmt"
"log"
"github.com/golang-auth/go-gssapi/v3"
ghttp "github.com/golang-auth/go-gssapi/v3/http"
)
var GssProvider = "github.com/golang-auth/go-gssapi-c"
func main() {
p, err := gssapi.NewProvider(GssProvider)
if err != nil {
log.Fatalf("Failed to create provider: %v", err)
}
opts := []ghttp.ClientOption{
ghttp.WithInitiatorMutual(),
}
client, err := ghttp.NewClient(p, nil, opts...)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
resp, err := client.Get("http://localhost:1234/")
if err != nil {
log.Fatalf("Failed to get: %v", err)
}
defer func() { _ = resp.Body.Close() }()
fmt.Println(resp.Status)
}
func ServerWithStashConn ¶
ServerWithStashConn returns a new http.Server with a http.Server.ConnContext function that stashes the net.Conn in the connection's context. The original ConnContext function is then called if it is not nil.
This is intended to be be used when channel bindings are enabled. The connection information is required by the handler to determine the server certificate used for the TLS connection.
Types ¶
type ChannelBindingDisposition ¶
type ChannelBindingDisposition int
const ( ChannelBindingDispositionIgnore ChannelBindingDisposition = iota ChannelBindingDispositionIfAvailable ChannelBindingDispositionRequire )
type ClientOption ¶
type ClientOption func(c *GSSAPITransport)
ClientOption is a function that configures a Client
func WithIniiatorDelegationPolicy ¶
func WithIniiatorDelegationPolicy(delegationPolicy DelegationPolicy) ClientOption
WithIniiatorDelegationPolicy configures the client to use a custom credential delegation policy.
func WithInitiatorChannelBindingDisposition ¶
func WithInitiatorChannelBindingDisposition(disposition ChannelBindingDisposition) ClientOption
WithInitiatorChannelBindingDisposition configures how the client handles channel binding.
func WithInitiatorCredential ¶
func WithInitiatorCredential(cred gssapi.Credential) ClientOption
WithInitiatorCredential configures the client to use a specific credential
func WithInitiatorExpect100Threshold ¶
func WithInitiatorExpect100Threshold(threshold int64) ClientOption
WithInitiatorExpect100Threshold configures the client to use the Expect: Continue header if the request body is larger than the threshold.
Use of the Expect: Continue header is disabled by default due to concerns about the correct implementation by some servers.
func WithInitiatorLogFunc ¶
func WithInitiatorLogFunc(logFunc func(format string, args ...interface{})) ClientOption
WithInitiatorLogFunc configures the client to use a custom log function
func WithInitiatorMutual ¶
func WithInitiatorMutual() ClientOption
WithInitiatorMutual configures the client to request mutual authentication
Mutual authentication means that the client and server both authenticate each other. It causes the server to respond with a GSSAPI authentication token in the Authorization header that the client can use to complete the context establishment and verify the server's identity.
func WithInitiatorOpportunistic ¶
func WithInitiatorOpportunistic() ClientOption
WithInitiatorOpportunistic configures the client to opportunisticly authenticate
Opportunistic authentication means that the client does not wait for the server to respond with a 401 status code before sending an authentication token. This is a performance optimization that can be used to reduce the number of round trips between the client and server, at the cost of initializing the GSSAPI context and potentially exposing authentcation credentials to the server unnecessarily.
func WithInitiatorOpportunisticFunc ¶
func WithInitiatorOpportunisticFunc(opportunisticFunc OpportunisticFunc) ClientOption
WithInitiatorOpportunisticFunc configures the client to use a custom function to determine if opportunistic authentication should be used for a given URL.
func WithInitiatorRoundTripper ¶
func WithInitiatorRoundTripper(transport http.RoundTripper) ClientOption
WithInitiatorRoundTripper configures the client to use a custom round tripper
func WithInitiatorSpnFunc ¶
func WithInitiatorSpnFunc(spnFunc SpnFunc) ClientOption
WithInitiatorSpnFunc provides a custom function to provide the Service Principal Name (SPN) for a given URL.
The default uses "HTTP@" + the host name of the URL.
func WithInititorHttpLogging ¶
func WithInititorHttpLogging() ClientOption
WithInititorHttpLogging configures the client to log the HTTP requests and responses Does nothing without a log function
type DelegationPolicy ¶
type DelegationPolicy int
DelegationPolicy is the policy for delegation of credentials to the server.
const ( // DelegationPolicyNever means that credentials will not be delegated to the server. DelegationPolicyNever DelegationPolicy = iota // DelegationPolicyAlways means that credentials will be delegated to the server. DelegationPolicyAlways // DelegationPolicyIfAllowed means that credentials will be delegated to the server // if the policy (eg. Kerberos OK-as-delegate policy) allows it. DelegationPolicyIfAllowed )
var DefaultDelegationPolicy DelegationPolicy = DelegationPolicyNever
DefaultDelegationPolicy is the default delegation policy used for new clients.
type GSSAPITransport ¶
type GSSAPITransport struct {
// contains filtered or unexported fields
}
GSSAPITransport is a http.RoundTripper implementation that includes GSSAPI (HTTP Negotiate) authentication.
func NewTransport ¶
func NewTransport(provider gssapi.Provider, options ...ClientOption) (*GSSAPITransport, error)
NewTransport creates a new GSSAPI transport with the given provider and options.
The transport is a wrapper around the standard http.Transport that adds GSSAPI authentication support. By default it wraps http.DefaultTransport - this can be overridden by passing a custom round tripper with [WithRoundTripper].
func (*GSSAPITransport) RoundTrip ¶
RoundTrip implements the http.RoundTripper interface and performs one HTTP request, including potentially multiple round-trips to the server to complete the GSSAPI context establishment.
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler is a http.Handler that performs GSSAPI authentication and passes the initiator name to the next handler
func NewHandler ¶
func NewHandler(provider gssapi.Provider, next http.Handler, options ...HandlerOption) (*Handler, error)
NewHandler creates a new Handler with the given GSSAPI provider and next handler
func (*Handler) ServeHTTP ¶
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP performs the GSSAPI authentication and passes the initiator name to the next handler It doesn't seem possible to support any more than one GSSAPI round trip per request with the Go http.Server implementation without hijacking the connection.
type HandlerOption ¶
type HandlerOption func(s *Handler)
HandlerOption is a function that can be used to configure the Handler
func WithAcceptorChannelBindingDisposition ¶
func WithAcceptorChannelBindingDisposition(disposition ChannelBindingDisposition) HandlerOption
WithAcceptorChannelBindingDisposition sets the channel binding type for the Handler.
The disposition can be one of:
- ChannelBindingDispositionIgnore: ignore channel bindings
- ChannelBindingDispositionRequire: require channel bindings
- ChannelBindingDispositionIfAvailable: use channel bindings if supplied by the initiator
Note that TLS channel bindings need access to the server certifficate presented to the client. For that we need to stash the connection information in the connection's context so that it can be retreieved by the handler. This can be achieved by using ServerWithStashConn to create the http.Server.
Example ¶
package main
import (
"fmt"
"html"
"log"
"net/http"
"github.com/golang-auth/go-gssapi/v3"
ghttp "github.com/golang-auth/go-gssapi/v3/http"
)
var KeyFile = "testdata/server.key"
var CertFile = "testdata/server.crt"
func testHandler(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
}
func main() {
p, err := gssapi.NewProvider(GssProvider)
if err != nil {
log.Fatalf("Failed to create provider: %v", err)
}
opts := []ghttp.HandlerOption{
ghttp.WithAcceptorChannelBindingDisposition(ghttp.ChannelBindingDispositionRequire),
}
handler, err := ghttp.NewHandler(p, http.HandlerFunc(testHandler), opts...)
if err != nil {
log.Fatalf("Failed to create handler: %v", err)
}
server := ghttp.ServerWithStashConn(&http.Server{
Addr: ":1234",
Handler: handler,
})
log.Fatal(server.ListenAndServeTLS(CertFile, KeyFile))
}
func WithAcceptorCredential ¶
func WithAcceptorCredential(credential gssapi.Credential) HandlerOption
WithAcceptorCredential sets the acceptor credential for the Handler
func WithDelegatedCredentialCache ¶
func WithDelegatedCredentialCache(cachePath string) HandlerOption
WithDelegatedCredentialCache sets the credential cache path template for storing delegated credentials. The cache path may include a %P placeholder which will be replaced with the client principal name. Delegated credentials will be stored using the GSSAPI cred store extensions if the provider supports them. This option requires that the provider supports the HasExtCredStore extension.
type HttpTrace ¶
HttpTrace gathers infromation during the HTTP request/response cycle.
func GetHttpTrace ¶
type InitiatorName ¶
type InitiatorName struct {
// PrincipalName is the fully qualified name of the initiator
PrincipalName string
// LocalName is the local name of the initiator if available
LocalName string
}
InitiatorName is the name of the initiator of the GSSAPI context. LocalName is set if the provider has the Localname extension
func GetInitiatorName ¶
func GetInitiatorName(r *http.Request) (*InitiatorName, bool)
GetInitiatorName returns the initiator name from the request context if available This can be used by the 'next' http handler called by Handler.ServeHTTP
type OpportunisticFunc ¶
OpportunisticFunc is a function that returns true if opportunistic authentication should be used for a given URL.