Files
gost-x/auth/plugin/http.go
T
ginuerzh 0ca1e7621a feat(handler): forward mTLS client cert identity to auth plugins
Extracts the verified mTLS peer certificate (CN, SANs, SHA-256
fingerprint) from the TLS connection in the HTTP handler after the
handshake completes, stores it in the request context, and forwards
it to both gRPC and HTTP auth plugins via new AuthenticateRequest
fields (client_cn, client_san, client_cert_fingerprint).
2026-07-04 17:30:36 +08:00

113 lines
2.4 KiB
Go

package auth
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"github.com/go-gost/core/auth"
"github.com/go-gost/core/logger"
xctx "github.com/go-gost/x/ctx"
"github.com/go-gost/x/internal/plugin"
)
type httpPluginRequest struct {
Service string `json:"service"`
Username string `json:"username"`
Password string `json:"password"`
Client string `json:"client"`
ClientCn string `json:"clientCn,omitempty"`
ClientSan []string `json:"clientSan,omitempty"`
ClientCertFingerprint string `json:"clientCertFingerprint,omitempty"`
}
type httpPluginResponse struct {
OK bool `json:"ok"`
ID string `json:"id"`
}
type httpPlugin struct {
url string
client *http.Client
header http.Header
log logger.Logger
}
// NewHTTPPlugin creates an Authenticator plugin based on HTTP.
func NewHTTPPlugin(name string, url string, opts ...plugin.Option) auth.Authenticator {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
return &httpPlugin{
url: url,
client: plugin.NewHTTPClient(&options),
header: options.Header,
log: logger.Default().WithFields(map[string]any{
"kind": "auther",
"auther": name,
}),
}
}
func (p *httpPlugin) Authenticate(ctx context.Context, user, password string, opts ...auth.Option) (id string, ok bool) {
if p.client == nil {
return
}
var options auth.Options
for _, opt := range opts {
opt(&options)
}
var clientAddr string
if v := xctx.SrcAddrFromContext(ctx); v != nil {
clientAddr = v.String()
}
rb := httpPluginRequest{
Service: options.Service,
Username: user,
Password: password,
Client: clientAddr,
}
if v := xctx.PeerCertFromContext(ctx); v != nil {
rb.ClientCn = v.CN
rb.ClientSan = v.SANs
rb.ClientCertFingerprint = v.Fingerprint
}
v, err := json.Marshal(&rb)
if err != nil {
return
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.url, bytes.NewReader(v))
if err != nil {
return
}
if p.header != nil {
req.Header = p.header.Clone()
}
req.Header.Set("Content-Type", "application/json")
resp, err := p.client.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
io.Copy(io.Discard, resp.Body)
return
}
res := httpPluginResponse{}
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
return
}
return res.ID, res.OK
}