mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
add premium, device authed endpoint to retrieve policies (#5967)
This adds a new device authenticated endpoint, `/api/_version_/fleet/device/{token}/policies` to retrieve the device policies. An example request / response looks like: ```bash curl https://localhost:8080/api/latest/fleet/device/1804e808-171f-4dda-9bec-f695b2f2371a/policies ``` ```json { "policies": [ { "id": 3, "name": "Antivirus healthy (Linux)", "query": "SELECT score FROM (SELECT case when COUNT(*) = 2 then 1 ELSE 0 END AS score FROM processes WHERE (name = 'clamd') OR (name = 'freshclam')) WHERE score == 1;", "description": "Checks that both ClamAV's daemon and its updater service (freshclam) are running.", "author_id": 1, "author_name": "Admin", "author_email": "admin@example.com", "team_id": null, "resolution": "Ensure ClamAV and Freshclam are installed and running.", "platform": "darwin,windows,linux", "created_at": "2022-05-23T20:53:36Z", "updated_at": "2022-05-23T20:53:36Z", "response": "fail" } ] } ``` Related to [#5685](https://github.com/fleetdm/fleet/issues/5685), in another changeset I will be adding "client" endpoints so we can consume this endpoint from Fleet Desktop
This commit is contained in:
parent
348c67d932
commit
eb8defdcbe
1
changes/issue-5685-device-policies-endpoint
Normal file
1
changes/issue-5685-device-policies-endpoint
Normal file
@ -0,0 +1 @@
|
||||
* Added `/api/_version_/fleet/device/{token}/policies` to retrieve policies for a device. This endpoint can only be accessed with a premium license.
|
11
ee/server/service/devices.go
Normal file
11
ee/server/service/devices.go
Normal file
@ -0,0 +1,11 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
)
|
||||
|
||||
func (svc *Service) ListDevicePolicies(ctx context.Context, host *fleet.Host) ([]*fleet.HostPolicy, error) {
|
||||
return svc.ds.ListPoliciesForHost(ctx, host)
|
||||
}
|
@ -257,6 +257,8 @@ type Service interface {
|
||||
// ListHostDeviceMapping returns the list of device-mapping of user's email address
|
||||
// for the host.
|
||||
ListHostDeviceMapping(ctx context.Context, id uint) ([]*HostDeviceMapping, error)
|
||||
// ListDevicePolicies lists all policies for the given host, including passing / failing summaries
|
||||
ListDevicePolicies(ctx context.Context, host *Host) ([]*HostPolicy, error)
|
||||
|
||||
MacadminsData(ctx context.Context, id uint) (*MacadminsData, error)
|
||||
AggregatedMacadminsData(ctx context.Context, teamID *uint) (*AggregatedMacadminsData, error)
|
||||
|
@ -168,3 +168,45 @@ func getDeviceMacadminsDataEndpoint(ctx context.Context, request interface{}, sv
|
||||
}
|
||||
return getMacadminsDataResponse{Macadmins: data}, nil
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// List Current Device's Policies
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type listDevicePoliciesRequest struct {
|
||||
Token string `url:"token"`
|
||||
}
|
||||
|
||||
func (r *listDevicePoliciesRequest) deviceAuthToken() string {
|
||||
return r.Token
|
||||
}
|
||||
|
||||
type listDevicePoliciesResponse struct {
|
||||
Err error `json:"error,omitempty"`
|
||||
Policies []*fleet.HostPolicy `json:"policies"`
|
||||
}
|
||||
|
||||
func (r listDevicePoliciesResponse) error() error { return r.Err }
|
||||
|
||||
func listDevicePoliciesEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
|
||||
host, ok := hostctx.FromContext(ctx)
|
||||
if !ok {
|
||||
err := ctxerr.Wrap(ctx, fleet.NewAuthRequiredError("internal error: missing host from request context"))
|
||||
return getHostResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
data, err := svc.ListDevicePolicies(ctx, host)
|
||||
if err != nil {
|
||||
return listDevicePoliciesResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
return listDevicePoliciesResponse{Policies: data}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) ListDevicePolicies(ctx context.Context, host *fleet.Host) ([]*fleet.HostPolicy, error) {
|
||||
// skipauth: No authorization check needed due to implementation returning
|
||||
// only license error.
|
||||
svc.authz.SkipAuthorization(ctx)
|
||||
|
||||
return nil, fleet.ErrMissingLicense
|
||||
}
|
||||
|
@ -395,6 +395,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
|
||||
de.POST("/api/_version_/fleet/device/{token}/refetch", refetchDeviceHostEndpoint, refetchDeviceHostRequest{})
|
||||
de.GET("/api/_version_/fleet/device/{token}/device_mapping", listDeviceHostDeviceMappingEndpoint, listDeviceHostDeviceMappingRequest{})
|
||||
de.GET("/api/_version_/fleet/device/{token}/macadmins", getDeviceMacadminsDataEndpoint, getDeviceMacadminsDataRequest{})
|
||||
de.GET("/api/_version_/fleet/device/{token}/policies", listDevicePoliciesEndpoint, listDevicePoliciesRequest{})
|
||||
|
||||
// host-authenticated endpoints
|
||||
he := newHostAuthenticatedEndpointer(svc, logger, opts, r, apiVersions...)
|
||||
|
@ -4634,6 +4634,13 @@ func (s *integrationTestSuite) TestDeviceAuthenticatedEndpoints() {
|
||||
res.Body.Close()
|
||||
require.NotNil(t, getHostResp.License)
|
||||
require.Equal(t, getHostResp.License.Tier, "free")
|
||||
|
||||
// device policies are not accessible for free endpoints
|
||||
listPoliciesResp := listDevicePoliciesResponse{}
|
||||
res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/policies", nil, http.StatusPaymentRequired)
|
||||
json.NewDecoder(res.Body).Decode(&getHostResp)
|
||||
res.Body.Close()
|
||||
require.Nil(t, listPoliciesResp.Policies)
|
||||
}
|
||||
|
||||
func (s *integrationTestSuite) TestModifyUser() {
|
||||
|
@ -8,10 +8,14 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
"github.com/fleetdm/fleet/v4/server/test"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
@ -456,3 +460,100 @@ func (s *integrationEnterpriseTestSuite) TestTeamEndpoints() {
|
||||
// delete team again, now an unknown team
|
||||
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/teams/%d", tm1ID), nil, http.StatusNotFound, &delResp)
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) TestListDevicePolicies() {
|
||||
t := s.T()
|
||||
|
||||
team, err := s.ds.NewTeam(context.Background(), &fleet.Team{
|
||||
ID: 51,
|
||||
Name: "team1-policies",
|
||||
Description: "desc team1",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
host, err := s.ds.NewHost(context.Background(), &fleet.Host{
|
||||
DetailUpdatedAt: time.Now(),
|
||||
LabelUpdatedAt: time.Now(),
|
||||
PolicyUpdatedAt: time.Now(),
|
||||
SeenTime: time.Now().Add(-1 * time.Minute),
|
||||
OsqueryHostID: t.Name(),
|
||||
NodeKey: t.Name(),
|
||||
UUID: uuid.New().String(),
|
||||
Hostname: fmt.Sprintf("%sfoo.local", t.Name()),
|
||||
Platform: "darwin",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = s.ds.AddHostsToTeam(context.Background(), &team.ID, []uint{host.ID})
|
||||
require.NoError(t, err)
|
||||
|
||||
// create an auth token for hosts[0]
|
||||
token := "much_valid"
|
||||
mysql.ExecAdhocSQL(t, s.ds, func(db sqlx.ExtContext) error {
|
||||
_, err := db.ExecContext(context.Background(), `INSERT INTO host_device_auth (host_id, token) VALUES (?, ?)`, host.ID, token)
|
||||
return err
|
||||
})
|
||||
|
||||
qr, err := s.ds.NewQuery(context.Background(), &fleet.Query{
|
||||
Name: "TestQueryEnterpriseGlobalPolicy",
|
||||
Description: "Some description",
|
||||
Query: "select * from osquery;",
|
||||
ObserverCanRun: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// add a global policy
|
||||
gpParams := globalPolicyRequest{
|
||||
QueryID: &qr.ID,
|
||||
Resolution: "some global resolution",
|
||||
}
|
||||
gpResp := globalPolicyResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/policies", gpParams, http.StatusOK, &gpResp)
|
||||
require.NotNil(t, gpResp.Policy)
|
||||
|
||||
// add a policy to team 1
|
||||
oldToken := s.token
|
||||
t.Cleanup(func() {
|
||||
s.token = oldToken
|
||||
})
|
||||
|
||||
password := test.GoodPassword
|
||||
email := "test_enterprise_policies@user.com"
|
||||
|
||||
u := &fleet.User{
|
||||
Name: "test team user",
|
||||
Email: email,
|
||||
GlobalRole: nil,
|
||||
Teams: []fleet.UserTeam{
|
||||
{
|
||||
Team: *team,
|
||||
Role: fleet.RoleMaintainer,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
require.NoError(t, u.SetPassword(password, 10, 10))
|
||||
_, err = s.ds.NewUser(context.Background(), u)
|
||||
require.NoError(t, err)
|
||||
|
||||
s.token = s.getTestToken(email, password)
|
||||
tpParams := teamPolicyRequest{
|
||||
Name: "TestQueryEnterpriseTeamPolicy",
|
||||
Query: "select * from osquery;",
|
||||
Description: "Some description",
|
||||
Resolution: "some team resolution",
|
||||
Platform: "darwin",
|
||||
}
|
||||
tpResp := teamPolicyResponse{}
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/teams/%d/policies", team.ID), tpParams, http.StatusOK, &tpResp)
|
||||
|
||||
// try with invalid token
|
||||
res := s.DoRawNoAuth("GET", "/api/latest/fleet/device/invalid_token/policies", nil, http.StatusUnauthorized)
|
||||
res.Body.Close()
|
||||
|
||||
listDevicePoliciesResp := listDevicePoliciesResponse{}
|
||||
res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/policies", nil, http.StatusOK)
|
||||
json.NewDecoder(res.Body).Decode(&listDevicePoliciesResp)
|
||||
res.Body.Close()
|
||||
require.Len(t, listDevicePoliciesResp.Policies, 2)
|
||||
require.NoError(t, listDevicePoliciesResp.Err)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user