mirror of
https://github.com/fleetdm/fleet
synced 2026-05-12 03:28:48 +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 -->
120 lines
3.7 KiB
Go
120 lines
3.7 KiB
Go
package httpsig
|
|
|
|
import (
|
|
"context"
|
|
"crypto/elliptic"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/fleetdm/fleet/v4/ee/server/service/hostidentity/types"
|
|
"github.com/fleetdm/fleet/v4/pkg/fleethttpsig"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/go-kit/log"
|
|
"github.com/go-kit/log/level"
|
|
"github.com/remitly-oss/httpsig-go"
|
|
)
|
|
|
|
type HTTPSig struct {
|
|
ds fleet.Datastore
|
|
logger log.Logger
|
|
}
|
|
|
|
type KeySpecer struct {
|
|
hostIdentityCert types.HostIdentityCertificate
|
|
keySpec httpsig.KeySpec
|
|
}
|
|
|
|
func (k KeySpecer) KeySpec() (httpsig.KeySpec, error) {
|
|
return k.keySpec, nil
|
|
}
|
|
|
|
// _ ensures that KeySpecer implements the httpsig.KeySpecer interface.
|
|
var _ httpsig.KeySpecer = KeySpecer{}
|
|
|
|
func NewHTTPSig(ds fleet.Datastore, logger log.Logger) *HTTPSig {
|
|
return &HTTPSig{
|
|
ds: ds,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// _ ensures that HTTPSig implements the httpsig.KeyFetcher interface.
|
|
var _ httpsig.KeyFetcher = (*HTTPSig)(nil)
|
|
|
|
func (h *HTTPSig) Verifier() (*httpsig.Verifier, error) {
|
|
return fleethttpsig.Verifier(h)
|
|
}
|
|
|
|
func (h *HTTPSig) FetchByKeyID(ctx context.Context, _ http.Header, keyID string) (httpsig.KeySpecer, error) {
|
|
keyIDInt, err := strconv.ParseUint(keyID, 16, 64)
|
|
if err != nil {
|
|
err = fmt.Errorf("invalid hex key ID: %w", err)
|
|
h.logger.Log("level", "info", "msg", "FetchByKeyID error", "err", err)
|
|
return nil, err
|
|
}
|
|
identityCert, err := h.ds.GetHostIdentityCertBySerialNumber(ctx, keyIDInt)
|
|
switch {
|
|
case fleet.IsNotFound(err):
|
|
return nil, fmt.Errorf("certificate not found with keyID: %d", keyIDInt)
|
|
case err != nil:
|
|
err = fmt.Errorf("loading certificate: %w", err)
|
|
level.Error(h.logger).Log("msg", "FetchByKeyID error", "err", err)
|
|
return nil, err
|
|
}
|
|
publicKey, err := identityCert.UnmarshalPublicKey()
|
|
if err != nil {
|
|
err = fmt.Errorf("unmarshaling public key: %w", err)
|
|
level.Error(h.logger).Log("msg", "FetchByKeyID error", "err", err)
|
|
return nil, err
|
|
}
|
|
|
|
var algo httpsig.Algorithm
|
|
switch publicKey.Curve {
|
|
case elliptic.P256():
|
|
algo = httpsig.Algo_ECDSA_P256_SHA256
|
|
case elliptic.P384():
|
|
algo = httpsig.Algo_ECDSA_P384_SHA384
|
|
default:
|
|
err = fmt.Errorf("unsupported elliptic curve: %s", publicKey.Curve.Params().Name)
|
|
h.logger.Log("level", "info", "msg", "FetchByKeyID error", "err", err)
|
|
return nil, err
|
|
}
|
|
|
|
return &KeySpecer{
|
|
hostIdentityCert: *identityCert,
|
|
keySpec: httpsig.KeySpec{
|
|
KeyID: keyID,
|
|
Algo: algo,
|
|
PubKey: publicKey,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (h *HTTPSig) Fetch(_ context.Context, _ http.Header, _ httpsig.MetadataProvider) (httpsig.KeySpecer, error) {
|
|
return nil, errors.New("not implemented")
|
|
}
|
|
|
|
// VerifyHostIdentity checks that host identity certificate matches the node key and host ID.
|
|
// Host identity cert is used for TPM-backed HTTP message signatures.
|
|
// If the host has one, then all agent traffic should have HTTP message signatures unless specified otherwise.
|
|
// The host identity certificate must match the host's node key.
|
|
func VerifyHostIdentity(ctx context.Context, ds fleet.Datastore, host *fleet.Host) error {
|
|
hostIdentityCert, ok := FromContext(ctx)
|
|
if !ok {
|
|
return errors.New("authentication error: missing host identity certificate")
|
|
}
|
|
if host.OsqueryHostID == nil || *host.OsqueryHostID != hostIdentityCert.CommonName {
|
|
return errors.New("authentication error: http message signature does not match node key")
|
|
}
|
|
if hostIdentityCert.HostID == nil {
|
|
return fmt.Errorf("authentication error: found host identity certificate without host ID. "+
|
|
"This should not happen since host ID for a certificate should be set at enrollment. identifier/CN: %s host ID: %d",
|
|
hostIdentityCert.CommonName, host.ID)
|
|
}
|
|
if *hostIdentityCert.HostID != host.ID {
|
|
return errors.New("authentication error: http message signature does not match host ID")
|
|
}
|
|
return nil
|
|
}
|