mirror of
https://github.com/fleetdm/fleet
synced 2026-05-09 10:11:03 +00:00
#30461 This PR contains the changes for the happy path. On a separate PR we will be adding tests and further fixes for edge cases. - [X] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. - [ ] Added/updated automated tests - [x] Manual QA for all new/changed functionality - For Orbit and Fleet Desktop changes: - [ ] Make sure fleetd is compatible with the latest released version of Fleet (see [Must rule](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/workflows/fleetd-development-and-release-strategy.md)). - [ ] Orbit runs on macOS, Linux and Windows. Check if the orbit feature/bugfix should only apply to one platform (`runtime.GOOS`). - [ ] Manual QA must be performed in the three main OSs, macOS, Windows and Linux. - [ ] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added support for using a TPM-backed key and SCEP-issued certificate to sign HTTP requests, enhancing security through hardware-based key management. * Introduced new CLI and environment flags to enable TPM-backed client certificates for Linux packages and Orbit. * Added a local HTTPS proxy that automatically signs requests using the TPM-backed key. * **Bug Fixes** * Improved cleanup and restart behavior when authentication fails with a host identity certificate. * **Tests** * Added comprehensive tests for SCEP client functionality and TPM integration. * **Chores** * Updated scripts and documentation to support TPM-backed client certificate packaging and configuration. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
106 lines
3.7 KiB
Go
106 lines
3.7 KiB
Go
package httpsig
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/fleetdm/fleet/v4/ee/server/service/hostidentity/types"
|
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
kitlog "github.com/go-kit/log"
|
|
"github.com/go-kit/log/level"
|
|
)
|
|
|
|
type key int
|
|
|
|
const hostIdentityKey key = 0
|
|
|
|
// NewContext creates a new context.Context with host identity cert.
|
|
func NewContext(ctx context.Context, hostIdentity types.HostIdentityCertificate) context.Context {
|
|
return context.WithValue(ctx, hostIdentityKey, hostIdentity)
|
|
}
|
|
|
|
// FromContext returns a pointer to the host identity cert.
|
|
func FromContext(ctx context.Context) (types.HostIdentityCertificate, bool) {
|
|
v, ok := ctx.Value(hostIdentityKey).(types.HostIdentityCertificate)
|
|
return v, ok
|
|
}
|
|
|
|
// MiddlewareFunc is a function which receives an http.Handler and returns another http.Handler.
|
|
// Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed
|
|
// to it, and then calls the handler passed as parameter to the MiddlewareFunc.
|
|
type MiddlewareFunc func(http.Handler) http.Handler
|
|
|
|
func Middleware(ds fleet.Datastore, requireSignature bool, logger kitlog.Logger) (MiddlewareFunc, error) {
|
|
// Initialize HTTP signature verifier
|
|
httpSig := NewHTTPSig(ds, logger)
|
|
verifier, err := httpSig.Verifier()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup httpsig verifier: %w", err)
|
|
}
|
|
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
if !strings.Contains(req.URL.Path, "/api/fleet/orbit/") && !strings.Contains(req.URL.Path, "/osquery/") {
|
|
next.ServeHTTP(w, req)
|
|
return
|
|
}
|
|
|
|
// We do not verify the "ping" endpoint since it is used to get server capabilities and does not carry any data.
|
|
// This endpoint is unauthenticated.
|
|
if strings.HasSuffix(req.URL.Path, "/api/fleet/orbit/ping") {
|
|
next.ServeHTTP(w, req)
|
|
return
|
|
}
|
|
|
|
// If the request does not have an HTTP message signature, we do not verify it AND
|
|
// we do not set the host identity cert in the context
|
|
if req.Header.Get("signature") == "" || req.Header.Get("signature-input") == "" {
|
|
if requireSignature {
|
|
handleError(req.Context(), w,
|
|
ctxerr.Errorf(req.Context(), "missing required HTTP message signature: path=%s", req.URL.Path),
|
|
http.StatusUnauthorized)
|
|
return
|
|
}
|
|
next.ServeHTTP(w, req)
|
|
return
|
|
}
|
|
|
|
// Verify signature using certificate associated with the provided serial number.
|
|
result, err := verifier.Verify(req)
|
|
if err != nil {
|
|
handleError(req.Context(), w,
|
|
ctxerr.Wrap(req.Context(), err, "failed to verify request signature", fmt.Sprintf("path=%s", req.URL.Path)),
|
|
http.StatusUnauthorized)
|
|
return
|
|
}
|
|
keySpecer, ok := result.KeySpecer.(*KeySpecer)
|
|
if !ok {
|
|
handleError(req.Context(), w,
|
|
ctxerr.New(req.Context(), fmt.Sprintf("could not extract host identity certificate key: path=%s", req.URL.Path)),
|
|
http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if !result.Verified {
|
|
handleError(req.Context(), w,
|
|
ctxerr.New(req.Context(), fmt.Sprintf("request not verified: path=%s host_uuid=%s", req.URL.Path,
|
|
keySpecer.hostIdentityCert.CommonName)),
|
|
http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
level.Debug(logger).Log("msg", "httpsig verified", "host_id", keySpecer.hostIdentityCert.HostID)
|
|
|
|
// Signature is valid, we set the identity data in the context and proceed with processing the request.
|
|
req = req.WithContext(NewContext(req.Context(), keySpecer.hostIdentityCert))
|
|
next.ServeHTTP(w, req)
|
|
})
|
|
}, nil
|
|
}
|
|
|
|
func handleError(ctx context.Context, w http.ResponseWriter, err error, code int) {
|
|
ctxerr.Handle(ctx, err)
|
|
http.Error(w, err.Error(), code)
|
|
}
|