CHV: implement paginated list certificates endpoints (#26554)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
* Added the list host certificates (and list device's certificates) endpoints.
|
||||
@@ -73,15 +73,8 @@ func (ds *Datastore) UpdateHostCertificates(ctx context.Context, hostID uint, ce
|
||||
}
|
||||
|
||||
func listHostCertsDB(ctx context.Context, tx sqlx.QueryerContext, hostID uint, opts fleet.ListOptions) ([]*fleet.HostCertificateRecord, *fleet.PaginationMetadata, error) {
|
||||
// TODO: move this to the service layer and do validation of the order key?
|
||||
if opts.OrderKey == "" {
|
||||
// default sort by common name ascending
|
||||
opts.OrderKey = "common_name"
|
||||
opts.OrderDirection = fleet.OrderAscending
|
||||
}
|
||||
|
||||
stmt := `
|
||||
SELECT
|
||||
SELECT
|
||||
id,
|
||||
sha1_sum,
|
||||
host_id,
|
||||
@@ -105,8 +98,8 @@ SELECT
|
||||
issuer_org_unit,
|
||||
issuer_common_name
|
||||
FROM
|
||||
host_certificates
|
||||
WHERE
|
||||
host_certificates
|
||||
WHERE
|
||||
host_id = ?
|
||||
AND deleted_at IS NULL`
|
||||
|
||||
@@ -126,7 +119,6 @@ WHERE
|
||||
certs = certs[:len(certs)-1]
|
||||
}
|
||||
}
|
||||
|
||||
return certs, metaData, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -24,24 +24,24 @@ type HostCertificateRecord struct {
|
||||
DeletedAt *time.Time `json:"-" db:"deleted_at"`
|
||||
|
||||
// The following fields are extracted from the certificate.
|
||||
NotValidAfter time.Time `json:"-" db:"not_valid_after"`
|
||||
NotValidBefore time.Time `json:"-" db:"not_valid_before"`
|
||||
CertificateAuthority bool `json:"-" db:"certificate_authority"`
|
||||
CommonName string `json:"-" db:"common_name"`
|
||||
KeyAlgorithm string `json:"-" db:"key_algorithm"`
|
||||
KeyStrength int `json:"-" db:"key_strength"`
|
||||
KeyUsage string `json:"-" db:"key_usage"`
|
||||
Serial string `json:"-" db:"serial"`
|
||||
SigningAlgorithm string `json:"-" db:"signing_algorithm"`
|
||||
|
||||
NotValidAfter time.Time `json:"-" db:"not_valid_after"`
|
||||
NotValidBefore time.Time `json:"-" db:"not_valid_before"`
|
||||
CertificateAuthority bool `json:"-" db:"certificate_authority"`
|
||||
CommonName string `json:"-" db:"common_name"`
|
||||
KeyAlgorithm string `json:"-" db:"key_algorithm"`
|
||||
KeyStrength int `json:"-" db:"key_strength"`
|
||||
KeyUsage string `json:"-" db:"key_usage"`
|
||||
Serial string `json:"-" db:"serial"`
|
||||
SigningAlgorithm string `json:"-" db:"signing_algorithm"`
|
||||
SubjectCountry string `json:"-" db:"subject_country"`
|
||||
SubjectOrganization string `json:"-" db:"subject_org"`
|
||||
SubjectOrganizationalUnit string `json:"-" db:"subject_org_unit"`
|
||||
SubjectCommonName string `json:"-" db:"subject_common_name"`
|
||||
IssuerCountry string `json:"-" db:"issuer_country"`
|
||||
IssuerOrganization string `json:"-" db:"issuer_org"`
|
||||
IssuerOrganizationalUnit string `json:"-" db:"issuer_org_unit"`
|
||||
IssuerCommonName string `json:"-" db:"issuer_common_name"`
|
||||
SubjectCountry string `json:"-" db:"subject_country"`
|
||||
SubjectOrganization string `json:"-" db:"subject_org"`
|
||||
SubjectOrganizationalUnit string `json:"-" db:"subject_org_unit"`
|
||||
SubjectCommonName string `json:"-" db:"subject_common_name"`
|
||||
IssuerCountry string `json:"-" db:"issuer_country"`
|
||||
IssuerOrganization string `json:"-" db:"issuer_org"`
|
||||
IssuerOrganizationalUnit string `json:"-" db:"issuer_org_unit"`
|
||||
IssuerCommonName string `json:"-" db:"issuer_common_name"`
|
||||
}
|
||||
|
||||
func NewHostCertificateRecord(
|
||||
@@ -82,6 +82,54 @@ func NewHostCertificateRecord(
|
||||
}
|
||||
}
|
||||
|
||||
// ToPayload fills a HostCertificatePayload with the fields of a
|
||||
// HostCertificateRecord. The HostCertificatePayload is used in API responses.
|
||||
func (r *HostCertificateRecord) ToPayload() *HostCertificatePayload {
|
||||
subject := &HostCertificateNameDetails{
|
||||
CommonName: r.SubjectCommonName,
|
||||
Country: r.SubjectCountry,
|
||||
Organization: r.SubjectOrganization,
|
||||
OrganizationalUnit: r.SubjectOrganizationalUnit,
|
||||
}
|
||||
issuer := &HostCertificateNameDetails{
|
||||
CommonName: r.IssuerCommonName,
|
||||
Country: r.IssuerCountry,
|
||||
Organization: r.IssuerOrganization,
|
||||
OrganizationalUnit: r.IssuerOrganizationalUnit,
|
||||
}
|
||||
return &HostCertificatePayload{
|
||||
ID: r.ID,
|
||||
NotValidAfter: r.NotValidAfter,
|
||||
NotValidBefore: r.NotValidBefore,
|
||||
CertificateAuthority: r.CertificateAuthority,
|
||||
CommonName: r.CommonName,
|
||||
KeyAlgorithm: r.KeyAlgorithm,
|
||||
KeyStrength: r.KeyStrength,
|
||||
KeyUsage: r.KeyUsage,
|
||||
Serial: r.Serial,
|
||||
SigningAlgorithm: r.SigningAlgorithm,
|
||||
Subject: subject,
|
||||
Issuer: issuer,
|
||||
}
|
||||
}
|
||||
|
||||
// HostCertificatePayload is the JSON model for API endpoints that return host certificates.
|
||||
type HostCertificatePayload struct {
|
||||
ID uint `json:"id"`
|
||||
NotValidAfter time.Time `json:"not_valid_after"`
|
||||
NotValidBefore time.Time `json:"not_valid_before"`
|
||||
CertificateAuthority bool `json:"certificate_authority"`
|
||||
CommonName string `json:"common_name"`
|
||||
KeyAlgorithm string `json:"key_algorithm"`
|
||||
KeyStrength int `json:"key_strength"`
|
||||
KeyUsage string `json:"key_usage"`
|
||||
Serial string `json:"serial"`
|
||||
SigningAlgorithm string `json:"signing_algorithm"`
|
||||
|
||||
Subject *HostCertificateNameDetails `json:"subject,omitempty"`
|
||||
Issuer *HostCertificateNameDetails `json:"issuer,omitempty"`
|
||||
}
|
||||
|
||||
type HostCertificateNameDetails struct {
|
||||
CommonName string `json:"common_name"`
|
||||
Country string `json:"country"`
|
||||
|
||||
@@ -435,6 +435,9 @@ type Service interface {
|
||||
// the specified host.
|
||||
ListHostSoftware(ctx context.Context, hostID uint, opts HostSoftwareTitleListOptions) ([]*HostSoftwareWithInstaller, *PaginationMetadata, error)
|
||||
|
||||
// ListHostCertificates lists the certificates installed on the specified host.
|
||||
ListHostCertificates(ctx context.Context, hostID uint, opts ListOptions) ([]*HostCertificatePayload, *PaginationMetadata, error)
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// AppConfigService provides methods for configuring the Fleet application
|
||||
|
||||
|
||||
@@ -685,3 +685,42 @@ func getDeviceSoftwareEndpoint(ctx context.Context, request interface{}, svc fle
|
||||
}
|
||||
return getDeviceSoftwareResponse{Software: res, Meta: meta, Count: int(meta.TotalResults)}, nil //nolint:gosec // dismiss G115
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// List Current Device's Certificates
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type listDeviceCertificatesRequest struct {
|
||||
Token string `url:"token"`
|
||||
fleet.ListOptions
|
||||
}
|
||||
|
||||
func (r *listDeviceCertificatesRequest) deviceAuthToken() string {
|
||||
return r.Token
|
||||
}
|
||||
|
||||
type listDeviceCertificatesResponse struct {
|
||||
Certificates []*fleet.HostCertificatePayload `json:"certificates"`
|
||||
Meta *fleet.PaginationMetadata `json:"meta,omitempty"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r listDeviceCertificatesResponse) Error() error { return r.Err }
|
||||
|
||||
func listDeviceCertificatesEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) {
|
||||
host, ok := hostctx.FromContext(ctx)
|
||||
if !ok {
|
||||
err := ctxerr.Wrap(ctx, fleet.NewAuthRequiredError("internal error: missing host from request context"))
|
||||
return listDevicePoliciesResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
req := request.(*listDeviceCertificatesRequest)
|
||||
res, meta, err := svc.ListHostCertificates(ctx, host.ID, req.ListOptions)
|
||||
if err != nil {
|
||||
return listDeviceCertificatesResponse{Err: err}, nil
|
||||
}
|
||||
if res == nil {
|
||||
res = []*fleet.HostCertificatePayload{}
|
||||
}
|
||||
return listDeviceCertificatesResponse{Certificates: res, Meta: meta}, nil
|
||||
}
|
||||
|
||||
@@ -409,6 +409,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
|
||||
ue.POST("/api/_version_/fleet/hosts/{id:[0-9]+}/labels", addLabelsToHostEndpoint, addLabelsToHostRequest{})
|
||||
ue.DELETE("/api/_version_/fleet/hosts/{id:[0-9]+}/labels", removeLabelsFromHostEndpoint, removeLabelsFromHostRequest{})
|
||||
ue.GET("/api/_version_/fleet/hosts/{id:[0-9]+}/software", getHostSoftwareEndpoint, getHostSoftwareRequest{})
|
||||
ue.GET("/api/_version_/fleet/hosts/{id:[0-9]+}/certificates", listHostCertificatesEndpoint, listHostCertificatesRequest{})
|
||||
|
||||
ue.GET("/api/_version_/fleet/hosts/summary/mdm", getHostMDMSummary, getHostMDMSummaryRequest{})
|
||||
ue.GET("/api/_version_/fleet/hosts/{id:[0-9]+}/mdm", getHostMDM, getHostMDMRequest{})
|
||||
@@ -810,6 +811,9 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
|
||||
de.WithCustomMiddleware(
|
||||
errorLimiter.Limit("install_self_service", desktopQuota),
|
||||
).POST("/api/_version_/fleet/device/{token}/software/install/{software_title_id}", submitSelfServiceSoftwareInstall, fleetSelfServiceSoftwareInstallRequest{})
|
||||
de.WithCustomMiddleware(
|
||||
errorLimiter.Limit("get_device_certificates", desktopQuota),
|
||||
).GET("/api/_version_/fleet/device/{token}/certificates", listDeviceCertificatesEndpoint, listDeviceCertificatesRequest{})
|
||||
|
||||
// mdm-related endpoints available via device authentication
|
||||
demdm := de.WithCustomMiddleware(mdmConfiguredMiddleware.VerifyAppleMDM())
|
||||
|
||||
@@ -2715,3 +2715,70 @@ func (svc *Service) ListHostSoftware(ctx context.Context, hostID uint, opts flee
|
||||
software, meta, err := svc.ds.ListHostSoftware(ctx, host, opts)
|
||||
return software, meta, ctxerr.Wrap(ctx, err, "list host software")
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Host Certificates
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type listHostCertificatesRequest struct {
|
||||
ID uint `url:"id"`
|
||||
fleet.ListOptions
|
||||
}
|
||||
|
||||
type listHostCertificatesResponse struct {
|
||||
Certificates []*fleet.HostCertificatePayload `json:"certificates"`
|
||||
Meta *fleet.PaginationMetadata `json:"meta,omitempty"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r listHostCertificatesResponse) Error() error { return r.Err }
|
||||
|
||||
func listHostCertificatesEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) {
|
||||
req := request.(*listHostCertificatesRequest)
|
||||
res, meta, err := svc.ListHostCertificates(ctx, req.ID, req.ListOptions)
|
||||
if err != nil {
|
||||
return listHostCertificatesResponse{Err: err}, nil
|
||||
}
|
||||
if res == nil {
|
||||
res = []*fleet.HostCertificatePayload{}
|
||||
}
|
||||
return listHostCertificatesResponse{Certificates: res, Meta: meta}, nil
|
||||
}
|
||||
|
||||
var listHostCertificatesSortCols = map[string]bool{
|
||||
"common_name": true,
|
||||
"not_valid_after": true,
|
||||
}
|
||||
|
||||
func (svc *Service) ListHostCertificates(ctx context.Context, hostID uint, opts fleet.ListOptions) ([]*fleet.HostCertificatePayload, *fleet.PaginationMetadata, error) {
|
||||
if !svc.authz.IsAuthenticatedWith(ctx, authzctx.AuthnDeviceToken) {
|
||||
host, err := svc.ds.HostLite(ctx, hostID)
|
||||
if err != nil {
|
||||
svc.authz.SkipAuthorization(ctx)
|
||||
return nil, nil, ctxerr.Wrap(ctx, err, "failed to load host")
|
||||
}
|
||||
if err := svc.authz.Authorize(ctx, host, fleet.ActionRead); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// query/after not supported, always include pagination info
|
||||
opts.MatchQuery = ""
|
||||
opts.After = ""
|
||||
opts.IncludeMetadata = true
|
||||
// default sort order is common name ascending
|
||||
if opts.OrderKey == "" || !listHostCertificatesSortCols[opts.OrderKey] {
|
||||
opts.OrderKey = "common_name"
|
||||
}
|
||||
|
||||
certs, meta, err := svc.ds.ListHostCertificates(ctx, hostID, opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
payload := make([]*fleet.HostCertificatePayload, 0, len(certs))
|
||||
for _, cert := range certs {
|
||||
payload = append(payload, cert.ToPayload())
|
||||
}
|
||||
return payload, meta, nil
|
||||
}
|
||||
|
||||
@@ -665,6 +665,9 @@ func TestHostAuth(t *testing.T) {
|
||||
ds.IsHostConnectedToFleetMDMFunc = func(ctx context.Context, host *fleet.Host) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
ds.ListHostCertificatesFunc = func(ctx context.Context, hostID uint, opts fleet.ListOptions) ([]*fleet.HostCertificateRecord, *fleet.PaginationMetadata, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
@@ -812,6 +815,11 @@ func TestHostAuth(t *testing.T) {
|
||||
|
||||
_, _, err = svc.ListHostSoftware(ctx, 2, fleet.HostSoftwareTitleListOptions{})
|
||||
checkAuthErr(t, tt.shouldFailGlobalRead, err)
|
||||
|
||||
_, _, err = svc.ListHostCertificates(ctx, 1, fleet.ListOptions{})
|
||||
checkAuthErr(t, tt.shouldFailTeamRead, err)
|
||||
_, _, err = svc.ListHostCertificates(ctx, 2, fleet.ListOptions{})
|
||||
checkAuthErr(t, tt.shouldFailGlobalRead, err)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha1" // nolint: gosec
|
||||
"database/sql"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
@@ -12974,3 +12975,133 @@ func (s *integrationTestSuite) TestSecretVariables() {
|
||||
require.Len(t, secrets, 1)
|
||||
assert.Equal(t, "value", secrets[0].Value)
|
||||
}
|
||||
|
||||
func (s *integrationTestSuite) TestHostCertificates() {
|
||||
t := s.T()
|
||||
ctx := context.Background()
|
||||
|
||||
token := "good_token"
|
||||
host := createOrbitEnrolledHost(t, "linux", "host1", s.ds)
|
||||
createDeviceTokenForHost(t, s.ds, host.ID, token)
|
||||
|
||||
// no certificate at the moment
|
||||
var certResp listHostCertificatesResponse
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/certificates", host.ID), nil, http.StatusOK, &certResp)
|
||||
require.Empty(t, certResp.Certificates)
|
||||
|
||||
certResp = listHostCertificatesResponse{}
|
||||
res := s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/certificates", nil, http.StatusOK)
|
||||
err := json.NewDecoder(res.Body).Decode(&certResp)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, certResp.Certificates)
|
||||
|
||||
// create some certs for that host
|
||||
certNames := []string{"a", "b", "c", "d", "e"}
|
||||
now := time.Now()
|
||||
// sorting by not_valid_after should get us "d", "c", "e", "a", "b"
|
||||
notValidAfterTimes := []time.Time{
|
||||
now.Add(time.Minute), now.Add(time.Hour),
|
||||
now.Add(time.Second), now.Add(time.Millisecond),
|
||||
now.Add(2 * time.Second)}
|
||||
certs := make([]*fleet.HostCertificateRecord, 0, len(certNames))
|
||||
for i, name := range certNames {
|
||||
certs = append(certs, &fleet.HostCertificateRecord{
|
||||
HostID: host.ID,
|
||||
CommonName: name,
|
||||
SHA1Sum: sha1.New().Sum([]byte(name)), // nolint: gosec
|
||||
SubjectCountry: "s" + name,
|
||||
IssuerCountry: "i" + name,
|
||||
NotValidAfter: notValidAfterTimes[i],
|
||||
})
|
||||
}
|
||||
require.NoError(t, s.ds.UpdateHostCertificates(ctx, host.ID, certs))
|
||||
|
||||
// list all certs
|
||||
certResp = listHostCertificatesResponse{}
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/certificates", host.ID), nil, http.StatusOK, &certResp)
|
||||
require.Len(t, certResp.Certificates, len(certNames))
|
||||
for i, cert := range certResp.Certificates {
|
||||
want := certNames[i]
|
||||
require.Equal(t, want, cert.CommonName)
|
||||
require.NotNil(t, cert.Subject)
|
||||
require.Equal(t, "s"+want, cert.Subject.Country)
|
||||
require.NotNil(t, cert.Issuer)
|
||||
require.Equal(t, "i"+want, cert.Issuer.Country)
|
||||
}
|
||||
|
||||
certResp = listHostCertificatesResponse{}
|
||||
res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/certificates", nil, http.StatusOK)
|
||||
err = json.NewDecoder(res.Body).Decode(&certResp)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, certResp.Certificates, len(certNames))
|
||||
for i, cert := range certResp.Certificates {
|
||||
want := certNames[i]
|
||||
require.Equal(t, want, cert.CommonName)
|
||||
require.NotNil(t, cert.Subject)
|
||||
require.Equal(t, "s"+want, cert.Subject.Country)
|
||||
require.NotNil(t, cert.Issuer)
|
||||
require.Equal(t, "i"+want, cert.Issuer.Country)
|
||||
}
|
||||
|
||||
// non-existing host
|
||||
certResp = listHostCertificatesResponse{}
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/certificates", host.ID+1000), nil, http.StatusNotFound, &certResp)
|
||||
// for the device endpoint, the token is the authentication so if it doesn't
|
||||
// exist, the endpoint is unauthorized.
|
||||
certResp = listHostCertificatesResponse{}
|
||||
s.DoRawNoAuth("GET", "/api/latest/fleet/device/NO-SUCH-TOKEN/certificates", nil, http.StatusUnauthorized)
|
||||
|
||||
pluckCertNames := func(certs []*fleet.HostCertificatePayload) []string {
|
||||
names := make([]string, 0, len(certs))
|
||||
for _, cert := range certs {
|
||||
names = append(names, cert.CommonName)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// invalid sort column silently defaults to "common_name"
|
||||
certResp = listHostCertificatesResponse{}
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/certificates", host.ID), nil, http.StatusOK, &certResp, "order_key", "no-such-column")
|
||||
require.Len(t, certResp.Certificates, len(certNames))
|
||||
require.Equal(t, certNames, pluckCertNames(certResp.Certificates))
|
||||
|
||||
certResp = listHostCertificatesResponse{}
|
||||
res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/certificates", nil, http.StatusOK, "order_key", "no-such-column")
|
||||
err = json.NewDecoder(res.Body).Decode(&certResp)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, certResp.Certificates, len(certNames))
|
||||
require.Equal(t, certNames, pluckCertNames(certResp.Certificates))
|
||||
|
||||
// test the pagination options
|
||||
cases := []struct {
|
||||
queryParams []string
|
||||
wantNames []string
|
||||
wantMeta fleet.PaginationMetadata
|
||||
}{
|
||||
{queryParams: []string{"page", "0", "per_page", "2"}, wantNames: []string{"a", "b"}, wantMeta: fleet.PaginationMetadata{HasNextResults: true}},
|
||||
{queryParams: []string{"page", "1", "per_page", "2"}, wantNames: []string{"c", "d"}, wantMeta: fleet.PaginationMetadata{HasNextResults: true, HasPreviousResults: true}},
|
||||
{queryParams: []string{"page", "2", "per_page", "2"}, wantNames: []string{"e"}, wantMeta: fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: true}},
|
||||
{queryParams: []string{"page", "3", "per_page", "2"}, wantNames: []string{}, wantMeta: fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: true}},
|
||||
{queryParams: []string{"page", "0", "per_page", "4", "order_direction", "desc"}, wantNames: []string{"e", "d", "c", "b"}, wantMeta: fleet.PaginationMetadata{HasNextResults: true}},
|
||||
{queryParams: []string{"page", "1", "per_page", "4", "order_direction", "desc"}, wantNames: []string{"a"}, wantMeta: fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: true}},
|
||||
{queryParams: []string{"page", "0", "per_page", "3", "order_key", "not_valid_after"}, wantNames: []string{"d", "c", "e"}, wantMeta: fleet.PaginationMetadata{HasNextResults: true}},
|
||||
{queryParams: []string{"page", "1", "per_page", "3", "order_key", "not_valid_after"}, wantNames: []string{"a", "b"}, wantMeta: fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: true}},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(strings.Join(c.queryParams, "_"), func(t *testing.T) {
|
||||
certResp = listHostCertificatesResponse{}
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/certificates", host.ID), nil, http.StatusOK, &certResp, c.queryParams...)
|
||||
require.Len(t, certResp.Certificates, len(c.wantNames))
|
||||
require.Equal(t, c.wantNames, pluckCertNames(certResp.Certificates))
|
||||
require.Equal(t, c.wantMeta, *certResp.Meta)
|
||||
|
||||
certResp = listHostCertificatesResponse{}
|
||||
res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/certificates", nil, http.StatusOK, c.queryParams...)
|
||||
err = json.NewDecoder(res.Body).Decode(&certResp)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, certResp.Certificates, len(c.wantNames))
|
||||
require.Equal(t, c.wantNames, pluckCertNames(certResp.Certificates))
|
||||
require.Equal(t, c.wantMeta, *certResp.Meta)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,8 +293,8 @@ func (ts *withServer) DoRaw(verb string, path string, rawBytes []byte, expectedS
|
||||
}, queryParams...)
|
||||
}
|
||||
|
||||
func (ts *withServer) DoRawNoAuth(verb string, path string, rawBytes []byte, expectedStatusCode int) *http.Response {
|
||||
return ts.DoRawWithHeaders(verb, path, rawBytes, expectedStatusCode, nil)
|
||||
func (ts *withServer) DoRawNoAuth(verb string, path string, rawBytes []byte, expectedStatusCode int, queryParams ...string) *http.Response {
|
||||
return ts.DoRawWithHeaders(verb, path, rawBytes, expectedStatusCode, nil, queryParams...)
|
||||
}
|
||||
|
||||
func (ts *withServer) DoJSON(verb, path string, params interface{}, expectedStatusCode int, v interface{}, queryParams ...string) {
|
||||
|
||||
Reference in New Issue
Block a user