mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
Issue 2456 policies yaml (#2512)
* wip * Add policy specs support * Add documentation * Make policy apply idempotent * Fold in code * Improve tests and simplify auth checks * Lint and fix test
This commit is contained in:
parent
a6e8f22d83
commit
d3a0d62902
1
changes/issue-2456-policies-yaml
Normal file
1
changes/issue-2456-policies-yaml
Normal file
@ -0,0 +1 @@
|
||||
* fleetctl supports applying policy yaml specs
|
@ -23,10 +23,11 @@ type specMetadata struct {
|
||||
}
|
||||
|
||||
type specGroup struct {
|
||||
Queries []*fleet.QuerySpec
|
||||
Teams []*fleet.TeamSpec
|
||||
Packs []*fleet.PackSpec
|
||||
Labels []*fleet.LabelSpec
|
||||
Queries []*fleet.QuerySpec
|
||||
Teams []*fleet.TeamSpec
|
||||
Packs []*fleet.PackSpec
|
||||
Labels []*fleet.LabelSpec
|
||||
Policies []*fleet.PolicySpec
|
||||
// This needs to be interface{} to allow for the patch logic. Otherwise we send a request that looks to the
|
||||
// server like the user explicitly set the zero values.
|
||||
AppConfig interface{}
|
||||
@ -40,9 +41,10 @@ type TeamSpec struct {
|
||||
|
||||
func specGroupFromBytes(b []byte) (*specGroup, error) {
|
||||
specs := &specGroup{
|
||||
Queries: []*fleet.QuerySpec{},
|
||||
Packs: []*fleet.PackSpec{},
|
||||
Labels: []*fleet.LabelSpec{},
|
||||
Queries: []*fleet.QuerySpec{},
|
||||
Packs: []*fleet.PackSpec{},
|
||||
Labels: []*fleet.LabelSpec{},
|
||||
Policies: []*fleet.PolicySpec{},
|
||||
}
|
||||
|
||||
for _, spec := range splitYaml(string(b)) {
|
||||
@ -115,6 +117,13 @@ func specGroupFromBytes(b []byte) (*specGroup, error) {
|
||||
}
|
||||
specs.Teams = append(specs.Teams, teamSpec.Team)
|
||||
|
||||
case fleet.PolicyKind:
|
||||
var policySpec *fleet.PolicySpec
|
||||
if err := yaml.Unmarshal(s.Spec, &policySpec); err != nil {
|
||||
return nil, errors.Wrap(err, "unmarshaling "+kind+" spec")
|
||||
}
|
||||
specs.Policies = append(specs.Policies, policySpec)
|
||||
|
||||
default:
|
||||
return nil, errors.Errorf("unknown kind %q", s.Kind)
|
||||
}
|
||||
@ -201,7 +210,7 @@ func applyCommand() *cli.Command {
|
||||
|
||||
if len(specs.Teams) > 0 {
|
||||
if err := fleetClient.ApplyTeams(specs.Teams); err != nil {
|
||||
return errors.Wrap(err, "applying queries")
|
||||
return errors.Wrap(err, "applying teams")
|
||||
}
|
||||
logf(c, "[+] applied %d teams\n", len(specs.Teams))
|
||||
}
|
||||
@ -213,6 +222,13 @@ func applyCommand() *cli.Command {
|
||||
log(c, "[+] applied user roles\n")
|
||||
}
|
||||
|
||||
if len(specs.Policies) > 0 {
|
||||
if err := fleetClient.ApplyPolicies(specs.Policies); err != nil {
|
||||
return errors.Wrap(err, "applying policies")
|
||||
}
|
||||
logf(c, "[+] applied %d policies\n", len(specs.Policies))
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
@ -234,3 +234,44 @@ spec:
|
||||
assert.True(t, savedAppConfig.HostSettings.EnableHostUsers)
|
||||
assert.True(t, savedAppConfig.HostSettings.EnableSoftwareInventory)
|
||||
}
|
||||
|
||||
func TestApplyPolicySpecs(t *testing.T) {
|
||||
_, ds := runServerWithMockedDS(t)
|
||||
|
||||
var gotPolicies []*fleet.PolicySpec
|
||||
|
||||
ds.TeamByNameFunc = func(ctx context.Context, name string) (*fleet.Team, error) {
|
||||
assert.Equal(t, "team1", name)
|
||||
return &fleet.Team{ID: 123, Name: "team1"}, nil
|
||||
}
|
||||
ds.ApplyPolicySpecsFunc = func(ctx context.Context, specs []*fleet.PolicySpec) error {
|
||||
gotPolicies = specs
|
||||
return nil
|
||||
}
|
||||
|
||||
name := writeTmpYml(t, `---
|
||||
apiVersion: v1
|
||||
kind: policy
|
||||
spec:
|
||||
query: some query
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: policy
|
||||
spec:
|
||||
query: some other query
|
||||
team: team1
|
||||
resolution: something something
|
||||
`)
|
||||
|
||||
assert.Equal(t, "[+] applied 2 policies\n", runAppForTest(t, []string{"apply", "-f", name}))
|
||||
assert.Equal(t, []*fleet.PolicySpec{
|
||||
{
|
||||
QueryName: "some query",
|
||||
},
|
||||
{
|
||||
QueryName: "some other query",
|
||||
Team: "team1",
|
||||
Resolution: "something something",
|
||||
},
|
||||
}, gotPolicies)
|
||||
}
|
||||
|
@ -4366,6 +4366,7 @@ Returns the spec for the specified pack by pack name.
|
||||
- [Get policy by ID](#get-policy-by-id)
|
||||
- [Add policy](#add-policy)
|
||||
- [Remove policies](#remove-policies)
|
||||
- [Apply policy specs](#apply-policy-specs)
|
||||
|
||||
`In Fleet 4.3.0, the Policies feature was introduced.`
|
||||
|
||||
@ -4396,15 +4397,16 @@ Hosts that do not return results for a policy's query are "Failing."
|
||||
"id": 1,
|
||||
"query_id": 2,
|
||||
"query_name": "Gatekeeper enabled",
|
||||
"resolution": "Resolution steps",
|
||||
"passing_host_count": 2000,
|
||||
"failing_host_count": 300,
|
||||
"failing_host_count": 300
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"query_id": 3,
|
||||
"query_name": "Primary disk encrypted",
|
||||
"passing_host_count": 2300,
|
||||
"failing_host_count": 0,
|
||||
"failing_host_count": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -4418,7 +4420,7 @@ Hosts that do not return results for a policy's query are "Failing."
|
||||
|
||||
| Name | Type | In | Description |
|
||||
| ------------------ | ------- | ---- | ------------------------------------------------------------------------------------------------------------- |
|
||||
| id | integer | path | **Required.** The policy's ID. |
|
||||
| id | integer | path | **Required.** The policy's ID. |
|
||||
|
||||
#### Example
|
||||
|
||||
@ -4434,8 +4436,9 @@ Hosts that do not return results for a policy's query are "Failing."
|
||||
"id": 1,
|
||||
"query_id": 2,
|
||||
"query_name": "Gatekeeper enabled",
|
||||
"resolution": "Resolution steps",
|
||||
"passing_host_count": 2000,
|
||||
"failing_host_count": 300,
|
||||
"failing_host_count": 300
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -4446,9 +4449,10 @@ Hosts that do not return results for a policy's query are "Failing."
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | In | Description |
|
||||
| -------- | ------- | ---- | ------------------------------ |
|
||||
| query_id | integer | body | **Required.** The query's ID. |
|
||||
| Name | Type | In | Description |
|
||||
| ---------- | ------- | ---- | ------------------------------------- |
|
||||
| query_id | integer | body | **Required.** The query's ID. |
|
||||
| resolution | string | body | The resolution steps for the policy. |
|
||||
|
||||
#### Example
|
||||
|
||||
@ -4472,9 +4476,10 @@ Hosts that do not return results for a policy's query are "Failing."
|
||||
"id": 2,
|
||||
"query_id": 2,
|
||||
"query_name": "Primary disk encrypted",
|
||||
"resolution": "Some resolution steps",
|
||||
"passing_host_count": 0,
|
||||
"failing_host_count": 0,
|
||||
},
|
||||
"failing_host_count": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -4510,6 +4515,47 @@ Hosts that do not return results for a policy's query are "Failing."
|
||||
}
|
||||
```
|
||||
|
||||
### Apply policy specs
|
||||
|
||||
Applies the supplied policy specs to Fleet. Each policy requires a `query` property, and optionally a `resolution` detail
|
||||
to explain how to resolve the failure of the policy, and a `team` if the policy is at the specified team level.
|
||||
|
||||
`POST /api/v1/fleet/spec/policies`
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | In | Description |
|
||||
| ----- | ---- | ---- | ------------------------------------------------------------------------------------------------------------- |
|
||||
| specs | list | body | A list of the policy to apply. Each policy requires a `query` and optionally `team` and `resolution`. |
|
||||
|
||||
#### Example
|
||||
|
||||
`POST /api/v1/fleet/spec/policies`
|
||||
|
||||
##### Request body
|
||||
|
||||
```json
|
||||
{
|
||||
"specs": [
|
||||
{
|
||||
"query": "query name"
|
||||
},
|
||||
{
|
||||
"query": "some other query name",
|
||||
"team": "team1"
|
||||
},
|
||||
{
|
||||
"query": "query3",
|
||||
"resolution": "Add something to your config"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
##### Default response
|
||||
|
||||
`Status: 200`
|
||||
|
||||
---
|
||||
|
||||
## Team Policies
|
||||
|
1
go.sum
1
go.sum
@ -867,6 +867,7 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
github.com/zwass/kit v0.0.0-20210625184505-ec5b5c5cce9c h1:TWQ2UvXPkhPxI2KmApKBOCaV6yD2N4mlvqFQ/DlPtpQ=
|
||||
github.com/zwass/kit v0.0.0-20210625184505-ec5b5c5cce9c/go.mod h1:OYYulo9tUqRadRLwB0+LE914sa1ui2yL7OrcU3Q/1XY=
|
||||
|
@ -414,11 +414,12 @@ allow {
|
||||
|
||||
# Global Admin and Maintainer users can read and write policies
|
||||
allow {
|
||||
object.type == ["policy","team_policy"][_]
|
||||
object.type == "policy"
|
||||
subject.global_role == admin
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
# Global maintainer can read and write global policies
|
||||
allow {
|
||||
is_null(object.team_id)
|
||||
object.type == "policy"
|
||||
@ -426,20 +427,14 @@ allow {
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
# Global Maintainer and Observer users can read any policies
|
||||
allow {
|
||||
object.type == "policy"
|
||||
subject.global_role == maintainer
|
||||
action == [read][_]
|
||||
subject.global_role == [maintainer,observer][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
# Global Observer users can read policies
|
||||
allow {
|
||||
object.type == "policy"
|
||||
subject.global_role == observer
|
||||
action == [read][_]
|
||||
}
|
||||
|
||||
# Team admin and maintainers can read and write policies
|
||||
# Team admin and maintainers can read and write policies for their teams
|
||||
allow {
|
||||
not is_null(object.team_id)
|
||||
object.team_id == subject.teams[_].id
|
||||
@ -449,7 +444,6 @@ allow {
|
||||
}
|
||||
|
||||
# Team admin and maintainers can read global policies
|
||||
|
||||
allow {
|
||||
is_null(object.team_id)
|
||||
object.type == "policy"
|
||||
|
@ -458,6 +458,45 @@ func TestAuthorizeCarves(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthorizePolicies(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
policy := &fleet.Policy{}
|
||||
teamPolicy := &fleet.Policy{TeamID: ptr.Uint(1)}
|
||||
runTestCases(t, []authTestCase{
|
||||
{user: test.UserNoRoles, object: policy, action: write, allow: false},
|
||||
|
||||
{user: test.UserAdmin, object: policy, action: write, allow: true},
|
||||
{user: test.UserAdmin, object: policy, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: policy, action: write, allow: true},
|
||||
{user: test.UserMaintainer, object: policy, action: read, allow: true},
|
||||
{user: test.UserObserver, object: policy, action: write, allow: false},
|
||||
{user: test.UserObserver, object: policy, action: read, allow: true},
|
||||
|
||||
{user: test.UserAdmin, object: teamPolicy, action: write, allow: true},
|
||||
{user: test.UserAdmin, object: teamPolicy, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: teamPolicy, action: write, allow: false},
|
||||
{user: test.UserMaintainer, object: teamPolicy, action: read, allow: true},
|
||||
{user: test.UserObserver, object: teamPolicy, action: write, allow: false},
|
||||
{user: test.UserObserver, object: teamPolicy, action: read, allow: true},
|
||||
|
||||
{user: test.UserTeamAdminTeam1, object: teamPolicy, action: write, allow: true},
|
||||
{user: test.UserTeamAdminTeam1, object: teamPolicy, action: read, allow: true},
|
||||
{user: test.UserTeamAdminTeam2, object: teamPolicy, action: write, allow: false},
|
||||
{user: test.UserTeamAdminTeam2, object: teamPolicy, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamMaintainerTeam1, object: teamPolicy, action: write, allow: true},
|
||||
{user: test.UserTeamMaintainerTeam1, object: teamPolicy, action: read, allow: true},
|
||||
{user: test.UserTeamMaintainerTeam2, object: teamPolicy, action: write, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam2, object: teamPolicy, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverTeam1, object: teamPolicy, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: teamPolicy, action: read, allow: true},
|
||||
{user: test.UserTeamObserverTeam2, object: teamPolicy, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam2, object: teamPolicy, action: read, allow: false},
|
||||
})
|
||||
}
|
||||
|
||||
func assertAuthorized(t *testing.T, user *fleet.User, object, action interface{}) {
|
||||
t.Helper()
|
||||
|
||||
|
@ -1482,7 +1482,7 @@ func testHostsListByPolicy(t *testing.T, ds *Datastore) {
|
||||
filter := fleet.TeamFilter{User: test.UserAdmin}
|
||||
|
||||
q := test.NewQuery(t, ds, "query1", "select 1", 0, true)
|
||||
p, err := ds.NewGlobalPolicy(context.Background(), q.ID)
|
||||
p, err := ds.NewGlobalPolicy(context.Background(), q.ID, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
// When policy response is null, we list all hosts that haven't reported at all for the policy, or errored out
|
||||
|
@ -0,0 +1,26 @@
|
||||
package tables
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
MigrationClient.AddMigration(Up_20211013133706, Down_20211013133706)
|
||||
}
|
||||
|
||||
func Up_20211013133706(tx *sql.Tx) error {
|
||||
if columnExists(tx, "policies", "resolution") {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`ALTER TABLE policies ADD COLUMN resolution TEXT`); err != nil {
|
||||
return errors.Wrap(err, "add column resolution to policies")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Down_20211013133706(tx *sql.Tx) error {
|
||||
return nil
|
||||
}
|
@ -2,6 +2,7 @@ package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
@ -12,8 +13,8 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (ds *Datastore) NewGlobalPolicy(ctx context.Context, queryID uint) (*fleet.Policy, error) {
|
||||
res, err := ds.writer.ExecContext(ctx, `INSERT INTO policies (query_id) VALUES (?)`, queryID)
|
||||
func (ds *Datastore) NewGlobalPolicy(ctx context.Context, queryID uint, resolution string) (*fleet.Policy, error) {
|
||||
res, err := ds.writer.ExecContext(ctx, `INSERT INTO policies (query_id, resolution) VALUES (?, ?)`, queryID, resolution)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "inserting new policy")
|
||||
}
|
||||
@ -184,8 +185,8 @@ func (ds *Datastore) PolicyQueriesForHost(ctx context.Context, host *fleet.Host)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) NewTeamPolicy(ctx context.Context, teamID uint, queryID uint) (*fleet.Policy, error) {
|
||||
res, err := ds.writer.ExecContext(ctx, `INSERT INTO policies (query_id, team_id) VALUES (?, ?)`, queryID, teamID)
|
||||
func (ds *Datastore) NewTeamPolicy(ctx context.Context, teamID uint, queryID uint, resolution string) (*fleet.Policy, error) {
|
||||
res, err := ds.writer.ExecContext(ctx, `INSERT INTO policies (query_id, team_id, resolution) VALUES (?, ?, ?)`, queryID, teamID, resolution)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "inserting new team policy")
|
||||
}
|
||||
@ -208,3 +209,48 @@ func (ds *Datastore) DeleteTeamPolicies(ctx context.Context, teamID uint, ids []
|
||||
func (ds *Datastore) TeamPolicy(ctx context.Context, teamID uint, policyID uint) (*fleet.Policy, error) {
|
||||
return policyDB(ctx, ds.reader, policyID, &teamID)
|
||||
}
|
||||
|
||||
func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, specs []*fleet.PolicySpec) error {
|
||||
return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
|
||||
for _, spec := range specs {
|
||||
if spec.QueryName == "" {
|
||||
return errors.New("query name must not be empty")
|
||||
}
|
||||
|
||||
// We update by hand because team_id can be null and that means compound index wont work
|
||||
|
||||
teamCheck := `team_id is NULL`
|
||||
args := []interface{}{spec.QueryName}
|
||||
if spec.Team != "" {
|
||||
teamCheck = `team_id=(SELECT id FROM teams WHERE name=?)`
|
||||
args = append(args, spec.Team)
|
||||
}
|
||||
row := tx.QueryRowxContext(ctx,
|
||||
fmt.Sprintf(`SELECT 1 FROM policies WHERE query_id=(SELECT id FROM queries WHERE name=?) AND %s`, teamCheck),
|
||||
args...,
|
||||
)
|
||||
var exists int
|
||||
err := row.Scan(&exists)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return errors.Wrap(err, "checking policy existence")
|
||||
}
|
||||
if exists > 0 {
|
||||
_, err = tx.ExecContext(ctx,
|
||||
fmt.Sprintf(`UPDATE policies SET resolution=? WHERE query_id=(SELECT id FROM queries WHERE name=?) AND %s`, teamCheck),
|
||||
append([]interface{}{spec.Resolution}, args...)...,
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "exec ApplyPolicySpecs update")
|
||||
}
|
||||
} else {
|
||||
_, err = tx.ExecContext(ctx,
|
||||
`INSERT INTO policies (query_id, team_id, resolution) VALUES ((SELECT id FROM queries WHERE name=?), (SELECT id FROM teams WHERE name=?),?)`,
|
||||
spec.QueryName, spec.Team, spec.Resolution)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "exec ApplyPolicySpecs insert")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ func TestPolicies(t *testing.T) {
|
||||
{"TeamPolicy", testTeamPolicy},
|
||||
{"PolicyQueriesForHost", testPolicyQueriesForHost},
|
||||
{"TeamPolicyTransfer", testTeamPolicyTransfer},
|
||||
{"ApplyPolicySpec", testApplyPolicySpec},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
@ -41,7 +42,7 @@ func testPoliciesNewGlobalPolicy(t *testing.T, ds *Datastore) {
|
||||
Saved: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
p, err := ds.NewGlobalPolicy(context.Background(), q.ID)
|
||||
p, err := ds.NewGlobalPolicy(context.Background(), q.ID, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "query1", p.QueryName)
|
||||
@ -53,7 +54,7 @@ func testPoliciesNewGlobalPolicy(t *testing.T, ds *Datastore) {
|
||||
Saved: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = ds.NewGlobalPolicy(context.Background(), q2.ID)
|
||||
_, err = ds.NewGlobalPolicy(context.Background(), q2.ID, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
policies, err := ds.ListGlobalPolicies(context.Background())
|
||||
@ -108,7 +109,7 @@ func testPoliciesMembershipView(t *testing.T, ds *Datastore) {
|
||||
Saved: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
p, err := ds.NewGlobalPolicy(context.Background(), q.ID)
|
||||
p, err := ds.NewGlobalPolicy(context.Background(), q.ID, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
q2, err := ds.NewQuery(context.Background(), &fleet.Query{
|
||||
@ -118,7 +119,7 @@ func testPoliciesMembershipView(t *testing.T, ds *Datastore) {
|
||||
Saved: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
p2, err := ds.NewGlobalPolicy(context.Background(), q2.ID)
|
||||
p2, err := ds.NewGlobalPolicy(context.Background(), q2.ID, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "query1", p.QueryName)
|
||||
@ -192,19 +193,21 @@ func testTeamPolicy(t *testing.T, ds *Datastore) {
|
||||
prevPolicies, err := ds.ListGlobalPolicies(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ds.NewTeamPolicy(context.Background(), 99999999, q.ID)
|
||||
_, err = ds.NewTeamPolicy(context.Background(), 99999999, q.ID, "")
|
||||
require.Error(t, err)
|
||||
|
||||
p, err := ds.NewTeamPolicy(context.Background(), team1.ID, q.ID)
|
||||
p, err := ds.NewTeamPolicy(context.Background(), team1.ID, q.ID, "some resolution")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "query1", p.QueryName)
|
||||
require.NotNil(t, p.Resolution)
|
||||
assert.Equal(t, "some resolution", *p.Resolution)
|
||||
|
||||
globalPolicies, err := ds.ListGlobalPolicies(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, globalPolicies, len(prevPolicies))
|
||||
|
||||
_, err = ds.NewTeamPolicy(context.Background(), team2.ID, q2.ID)
|
||||
_, err = ds.NewTeamPolicy(context.Background(), team2.ID, q2.ID, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
teamPolicies, err := ds.ListTeamPolicies(context.Background(), team1.ID)
|
||||
@ -264,7 +267,7 @@ func testPolicyQueriesForHost(t *testing.T, ds *Datastore) {
|
||||
Saved: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
gp, err := ds.NewGlobalPolicy(context.Background(), q.ID)
|
||||
gp, err := ds.NewGlobalPolicy(context.Background(), q.ID, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
q2, err := ds.NewQuery(context.Background(), &fleet.Query{
|
||||
@ -274,7 +277,7 @@ func testPolicyQueriesForHost(t *testing.T, ds *Datastore) {
|
||||
Saved: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
tp, err := ds.NewTeamPolicy(context.Background(), team1.ID, q2.ID)
|
||||
tp, err := ds.NewTeamPolicy(context.Background(), team1.ID, q2.ID, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
queries, err := ds.PolicyQueriesForHost(context.Background(), host1)
|
||||
@ -337,10 +340,10 @@ func testTeamPolicyTransfer(t *testing.T, ds *Datastore) {
|
||||
Saved: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
teamPolicy, err := ds.NewTeamPolicy(context.Background(), team1.ID, q.ID)
|
||||
teamPolicy, err := ds.NewTeamPolicy(context.Background(), team1.ID, q.ID, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
globalPolicy, err := ds.NewGlobalPolicy(context.Background(), q.ID)
|
||||
globalPolicy, err := ds.NewGlobalPolicy(context.Background(), q.ID, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, ds.RecordPolicyQueryExecutions(
|
||||
@ -371,3 +374,98 @@ func testTeamPolicyTransfer(t *testing.T, ds *Datastore) {
|
||||
|
||||
checkPassingCount(0)
|
||||
}
|
||||
|
||||
func testApplyPolicySpec(t *testing.T, ds *Datastore) {
|
||||
team1, err := ds.NewTeam(context.Background(), &fleet.Team{Name: "team1"})
|
||||
require.NoError(t, err)
|
||||
|
||||
q, err := ds.NewQuery(context.Background(), &fleet.Query{
|
||||
Name: "query1",
|
||||
Description: "query1 desc",
|
||||
Query: "select 1;",
|
||||
Saved: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
q2, err := ds.NewQuery(context.Background(), &fleet.Query{
|
||||
Name: "query2",
|
||||
Description: "query2 desc",
|
||||
Query: "select 1;",
|
||||
Saved: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, ds.ApplyPolicySpecs(context.Background(), []*fleet.PolicySpec{
|
||||
{
|
||||
QueryName: "query1",
|
||||
Resolution: "some resolution",
|
||||
},
|
||||
{
|
||||
QueryName: "query2",
|
||||
Resolution: "some other resolution",
|
||||
Team: "team1",
|
||||
},
|
||||
{
|
||||
QueryName: "query1",
|
||||
Team: "team1",
|
||||
},
|
||||
}))
|
||||
|
||||
policies, err := ds.ListGlobalPolicies(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, policies, 1)
|
||||
assert.Equal(t, q.ID, policies[0].QueryID)
|
||||
require.NotNil(t, policies[0].Resolution)
|
||||
assert.Equal(t, "some resolution", *policies[0].Resolution)
|
||||
|
||||
teamPolicies, err := ds.ListTeamPolicies(context.Background(), team1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, teamPolicies, 2)
|
||||
assert.Equal(t, q2.ID, teamPolicies[0].QueryID)
|
||||
require.NotNil(t, teamPolicies[0].Resolution)
|
||||
assert.Equal(t, "some other resolution", *teamPolicies[0].Resolution)
|
||||
|
||||
assert.Equal(t, q.ID, teamPolicies[1].QueryID)
|
||||
require.NotNil(t, teamPolicies[1].Resolution)
|
||||
assert.Equal(t, "", *teamPolicies[1].Resolution)
|
||||
|
||||
require.Error(t, ds.ApplyPolicySpecs(context.Background(), []*fleet.PolicySpec{
|
||||
{
|
||||
QueryName: "query13",
|
||||
},
|
||||
}))
|
||||
|
||||
require.Error(t, ds.ApplyPolicySpecs(context.Background(), []*fleet.PolicySpec{
|
||||
{
|
||||
Team: "team1",
|
||||
},
|
||||
}))
|
||||
|
||||
require.Error(t, ds.ApplyPolicySpecs(context.Background(), []*fleet.PolicySpec{
|
||||
{
|
||||
QueryName: "query123",
|
||||
Team: "team1",
|
||||
},
|
||||
}))
|
||||
|
||||
// Make sure apply is idempotent
|
||||
require.NoError(t, ds.ApplyPolicySpecs(context.Background(), []*fleet.PolicySpec{
|
||||
{
|
||||
QueryName: "query1",
|
||||
Resolution: "some resolution",
|
||||
},
|
||||
{
|
||||
QueryName: "query2",
|
||||
Resolution: "some other resolution",
|
||||
Team: "team1",
|
||||
},
|
||||
{
|
||||
QueryName: "query1",
|
||||
Team: "team1",
|
||||
},
|
||||
}))
|
||||
|
||||
policies, err = ds.ListGlobalPolicies(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, policies, 1)
|
||||
}
|
||||
|
@ -309,9 +309,9 @@ CREATE TABLE `migration_status_tables` (
|
||||
`tstamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `id` (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=106 DEFAULT CHARSET=utf8mb4;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=107 DEFAULT CHARSET=utf8mb4;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01');
|
||||
INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01');
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `network_interfaces` (
|
||||
@ -400,6 +400,7 @@ CREATE TABLE `policies` (
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`team_id` int(10) unsigned DEFAULT NULL,
|
||||
`resolution` text,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `fk_policies_query_id` (`query_id`),
|
||||
KEY `fk_policies_team_id` (`team_id`),
|
||||
|
@ -342,7 +342,7 @@ type Datastore interface {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// GlobalPoliciesStore
|
||||
|
||||
NewGlobalPolicy(ctx context.Context, queryID uint) (*Policy, error)
|
||||
NewGlobalPolicy(ctx context.Context, queryID uint, resolution string) (*Policy, error)
|
||||
Policy(ctx context.Context, id uint) (*Policy, error)
|
||||
RecordPolicyQueryExecutions(ctx context.Context, host *Host, results map[uint]*bool, updated time.Time) error
|
||||
|
||||
@ -350,6 +350,7 @@ type Datastore interface {
|
||||
DeleteGlobalPolicies(ctx context.Context, ids []uint) ([]uint, error)
|
||||
|
||||
PolicyQueriesForHost(ctx context.Context, host *Host) (map[string]string, error)
|
||||
ApplyPolicySpecs(ctx context.Context, specs []*PolicySpec) error
|
||||
|
||||
// MigrateTables creates and migrates the table schemas
|
||||
MigrateTables(ctx context.Context) error
|
||||
@ -363,7 +364,7 @@ type Datastore interface {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Team Policies
|
||||
|
||||
NewTeamPolicy(ctx context.Context, teamID uint, queryID uint) (*Policy, error)
|
||||
NewTeamPolicy(ctx context.Context, teamID uint, queryID uint, resolution string) (*Policy, error)
|
||||
ListTeamPolicies(ctx context.Context, teamID uint) ([]*Policy, error)
|
||||
DeleteTeamPolicies(ctx context.Context, teamID uint, ids []uint) ([]uint, error)
|
||||
TeamPolicy(ctx context.Context, teamID uint, policyID uint) (*Policy, error)
|
||||
|
@ -1,23 +0,0 @@
|
||||
package fleet
|
||||
|
||||
type Policy struct {
|
||||
ID uint `json:"id"`
|
||||
QueryID uint `json:"query_id" db:"query_id"`
|
||||
QueryName string `json:"query_name" db:"query_name"`
|
||||
PassingHostCount uint `json:"passing_host_count" db:"passing_host_count"`
|
||||
FailingHostCount uint `json:"failing_host_count" db:"failing_host_count"`
|
||||
TeamID *uint `json:"team_id" db:"team_id"`
|
||||
|
||||
UpdateCreateTimestamps
|
||||
}
|
||||
|
||||
func (p Policy) AuthzType() string {
|
||||
return "policy"
|
||||
}
|
||||
|
||||
type HostPolicy struct {
|
||||
ID uint `json:"id" db:"id"`
|
||||
QueryID uint `json:"query_id" db:"query_id"`
|
||||
QueryName string `json:"query_name" db:"query_name"`
|
||||
Response string `json:"response" db:"response"`
|
||||
}
|
34
server/fleet/policies.go
Normal file
34
server/fleet/policies.go
Normal file
@ -0,0 +1,34 @@
|
||||
package fleet
|
||||
|
||||
type Policy struct {
|
||||
ID uint `json:"id"`
|
||||
QueryID uint `json:"query_id" db:"query_id"`
|
||||
QueryName string `json:"query_name" db:"query_name"`
|
||||
PassingHostCount uint `json:"passing_host_count" db:"passing_host_count"`
|
||||
FailingHostCount uint `json:"failing_host_count" db:"failing_host_count"`
|
||||
TeamID *uint `json:"team_id" db:"team_id"`
|
||||
Resolution *string `json:"resolution,omitempty" db:"resolution"`
|
||||
|
||||
UpdateCreateTimestamps
|
||||
}
|
||||
|
||||
func (p Policy) AuthzType() string {
|
||||
return "policy"
|
||||
}
|
||||
|
||||
const (
|
||||
PolicyKind = "policy"
|
||||
)
|
||||
|
||||
type HostPolicy struct {
|
||||
ID uint `json:"id" db:"id"`
|
||||
QueryID uint `json:"query_id" db:"query_id"`
|
||||
QueryName string `json:"query_name" db:"query_name"`
|
||||
Response string `json:"response" db:"response"`
|
||||
}
|
||||
|
||||
type PolicySpec struct {
|
||||
QueryName string `json:"query"`
|
||||
Resolution string `json:"resolution,omitempty"`
|
||||
Team string `json:"team,omitempty"`
|
||||
}
|
@ -399,10 +399,11 @@ type Service interface {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// GlobalPolicyService
|
||||
|
||||
NewGlobalPolicy(ctx context.Context, queryID uint) (*Policy, error)
|
||||
NewGlobalPolicy(ctx context.Context, queryID uint, resolution string) (*Policy, error)
|
||||
ListGlobalPolicies(ctx context.Context) ([]*Policy, error)
|
||||
DeleteGlobalPolicies(ctx context.Context, ids []uint) ([]uint, error)
|
||||
GetPolicyByIDQueries(ctx context.Context, policyID uint) (*Policy, error)
|
||||
ApplyPolicySpecs(ctx context.Context, policies []*PolicySpec) error
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Software
|
||||
@ -413,7 +414,7 @@ type Service interface {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Team Policies
|
||||
|
||||
NewTeamPolicy(ctx context.Context, teamID uint, queryID uint) (*Policy, error)
|
||||
NewTeamPolicy(ctx context.Context, teamID uint, queryID uint, resolution string) (*Policy, error)
|
||||
ListTeamPolicies(ctx context.Context, teamID uint) ([]*Policy, error)
|
||||
DeleteTeamPolicies(ctx context.Context, teamID uint, ids []uint) ([]uint, error)
|
||||
GetTeamPolicyByIDQueries(ctx context.Context, teamID uint, policyID uint) (*Policy, error)
|
||||
|
@ -281,6 +281,8 @@ type DeleteGlobalPoliciesFunc func(ctx context.Context, ids []uint) ([]uint, err
|
||||
|
||||
type PolicyQueriesForHostFunc func(ctx context.Context, host *fleet.Host) (map[string]string, error)
|
||||
|
||||
type ApplyPolicySpecsFunc func(ctx context.Context, specs []*fleet.PolicySpec) error
|
||||
|
||||
type MigrateTablesFunc func(ctx context.Context) error
|
||||
|
||||
type MigrateDataFunc func(ctx context.Context) error
|
||||
@ -707,6 +709,9 @@ type DataStore struct {
|
||||
PolicyQueriesForHostFunc PolicyQueriesForHostFunc
|
||||
PolicyQueriesForHostFuncInvoked bool
|
||||
|
||||
ApplyPolicySpecsFunc ApplyPolicySpecsFunc
|
||||
ApplyPolicySpecsFuncInvoked bool
|
||||
|
||||
MigrateTablesFunc MigrateTablesFunc
|
||||
MigrateTablesFuncInvoked bool
|
||||
|
||||
@ -1383,7 +1388,7 @@ func (s *DataStore) RecordStatisticsSent(ctx context.Context) error {
|
||||
return s.RecordStatisticsSentFunc(ctx)
|
||||
}
|
||||
|
||||
func (s *DataStore) NewGlobalPolicy(ctx context.Context, queryID uint) (*fleet.Policy, error) {
|
||||
func (s *DataStore) NewGlobalPolicy(ctx context.Context, queryID uint, resolution string) (*fleet.Policy, error) {
|
||||
s.NewGlobalPolicyFuncInvoked = true
|
||||
return s.NewGlobalPolicyFunc(ctx, queryID)
|
||||
}
|
||||
@ -1413,6 +1418,11 @@ func (s *DataStore) PolicyQueriesForHost(ctx context.Context, host *fleet.Host)
|
||||
return s.PolicyQueriesForHostFunc(ctx, host)
|
||||
}
|
||||
|
||||
func (s *DataStore) ApplyPolicySpecs(ctx context.Context, specs []*fleet.PolicySpec) error {
|
||||
s.ApplyPolicySpecsFuncInvoked = true
|
||||
return s.ApplyPolicySpecsFunc(ctx, specs)
|
||||
}
|
||||
|
||||
func (s *DataStore) MigrateTables(ctx context.Context) error {
|
||||
s.MigrateTablesFuncInvoked = true
|
||||
return s.MigrateTablesFunc(ctx)
|
||||
@ -1433,7 +1443,7 @@ func (s *DataStore) ListSoftware(ctx context.Context, teamId *uint, opt fleet.Li
|
||||
return s.ListSoftwareFunc(ctx, teamId, opt)
|
||||
}
|
||||
|
||||
func (s *DataStore) NewTeamPolicy(ctx context.Context, teamID uint, queryID uint) (*fleet.Policy, error) {
|
||||
func (s *DataStore) NewTeamPolicy(ctx context.Context, teamID uint, queryID uint, resolution string) (*fleet.Policy, error) {
|
||||
s.NewTeamPolicyFuncInvoked = true
|
||||
return s.NewTeamPolicyFunc(ctx, teamID, queryID)
|
||||
}
|
||||
|
@ -23,3 +23,12 @@ func (c *Client) ApplyTeams(specs []*fleet.TeamSpec) error {
|
||||
var responseBody applyTeamSpecsResponse
|
||||
return c.authenticatedRequest(req, verb, path, &responseBody)
|
||||
}
|
||||
|
||||
// ApplyPolicies sends the list of Policies to be applied to the
|
||||
// Fleet instance.
|
||||
func (c *Client) ApplyPolicies(specs []*fleet.PolicySpec) error {
|
||||
req := applyPolicySpecsRequest{Specs: specs}
|
||||
verb, path := "POST", "/api/v1/fleet/spec/policies"
|
||||
var responseBody applyPolicySpecsResponse
|
||||
return c.authenticatedRequest(req, verb, path, &responseBody)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
@ -11,7 +12,8 @@ import (
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type globalPolicyRequest struct {
|
||||
QueryID uint `json:"query_id"`
|
||||
QueryID uint `json:"query_id"`
|
||||
Resolution string `json:"resolution"`
|
||||
}
|
||||
|
||||
type globalPolicyResponse struct {
|
||||
@ -23,19 +25,19 @@ func (r globalPolicyResponse) error() error { return r.Err }
|
||||
|
||||
func globalPolicyEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
|
||||
req := request.(*globalPolicyRequest)
|
||||
resp, err := svc.NewGlobalPolicy(ctx, req.QueryID)
|
||||
resp, err := svc.NewGlobalPolicy(ctx, req.QueryID, req.Resolution)
|
||||
if err != nil {
|
||||
return globalPolicyResponse{Err: err}, nil
|
||||
}
|
||||
return globalPolicyResponse{Policy: resp}, nil
|
||||
}
|
||||
|
||||
func (svc Service) NewGlobalPolicy(ctx context.Context, queryID uint) (*fleet.Policy, error) {
|
||||
func (svc Service) NewGlobalPolicy(ctx context.Context, queryID uint, resolution string) (*fleet.Policy, error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Policy{}, fleet.ActionWrite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return svc.ds.NewGlobalPolicy(ctx, queryID)
|
||||
return svc.ds.NewGlobalPolicy(ctx, queryID, "")
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
@ -133,3 +135,50 @@ func (svc Service) DeleteGlobalPolicies(ctx context.Context, ids []uint) ([]uint
|
||||
|
||||
return svc.ds.DeleteGlobalPolicies(ctx, ids)
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// Apply Spec
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type applyPolicySpecsRequest struct {
|
||||
Specs []*fleet.PolicySpec `json:"specs"`
|
||||
}
|
||||
|
||||
type applyPolicySpecsResponse struct {
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r applyPolicySpecsResponse) error() error { return r.Err }
|
||||
|
||||
func applyPolicySpecsEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
|
||||
req := request.(*applyPolicySpecsRequest)
|
||||
err := svc.ApplyPolicySpecs(ctx, req.Specs)
|
||||
if err != nil {
|
||||
return applyPolicySpecsResponse{Err: err}, nil
|
||||
}
|
||||
return applyPolicySpecsResponse{}, nil
|
||||
}
|
||||
|
||||
func (svc Service) ApplyPolicySpecs(ctx context.Context, policies []*fleet.PolicySpec) error {
|
||||
checkGlobalPolicyAuth := false
|
||||
for _, policy := range policies {
|
||||
if policy.Team != "" {
|
||||
team, err := svc.ds.TeamByName(ctx, policy.Team)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getting team by name")
|
||||
}
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Policy{TeamID: &team.ID}, fleet.ActionWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
checkGlobalPolicyAuth = true
|
||||
}
|
||||
if checkGlobalPolicyAuth {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Policy{}, fleet.ActionWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return svc.ds.ApplyPolicySpecs(ctx, policies)
|
||||
}
|
||||
|
@ -26,6 +26,12 @@ func TestGlobalPoliciesAuth(t *testing.T) {
|
||||
ds.DeleteGlobalPoliciesFunc = func(ctx context.Context, ids []uint) ([]uint, error) {
|
||||
return nil, nil
|
||||
}
|
||||
ds.TeamByNameFunc = func(ctx context.Context, name string) (*fleet.Team, error) {
|
||||
return &fleet.Team{ID: 1}, nil
|
||||
}
|
||||
ds.ApplyPolicySpecsFunc = func(ctx context.Context, specs []*fleet.PolicySpec) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var testCases = []struct {
|
||||
name string
|
||||
@ -74,7 +80,7 @@ func TestGlobalPoliciesAuth(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := viewer.NewContext(context.Background(), viewer.Viewer{User: tt.user})
|
||||
|
||||
_, err := svc.NewGlobalPolicy(ctx, 2)
|
||||
_, err := svc.NewGlobalPolicy(ctx, 2, "")
|
||||
checkAuthErr(t, tt.shouldFailWrite, err)
|
||||
|
||||
_, err = svc.ListGlobalPolicies(ctx)
|
||||
@ -85,6 +91,13 @@ func TestGlobalPoliciesAuth(t *testing.T) {
|
||||
|
||||
_, err = svc.DeleteGlobalPolicies(ctx, []uint{1})
|
||||
checkAuthErr(t, tt.shouldFailWrite, err)
|
||||
|
||||
err = svc.ApplyPolicySpecs(ctx, []*fleet.PolicySpec{
|
||||
{
|
||||
QueryName: "query1",
|
||||
},
|
||||
})
|
||||
checkAuthErr(t, tt.shouldFailWrite, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -709,6 +709,8 @@ func attachNewStyleFleetAPIRoutes(r *mux.Router, svc fleet.Service, opts []kitht
|
||||
e.GET("/api/v1/fleet/teams/{team_id}/policies/{policy_id}", getTeamPolicyByIDEndpoint, getTeamPolicyByIDRequest{})
|
||||
e.POST("/api/v1/fleet/teams/{team_id}/policies/delete", deleteTeamPoliciesEndpoint, deleteTeamPoliciesRequest{})
|
||||
|
||||
e.POST("/api/v1/fleet/spec/policies", applyPolicySpecsEndpoint, applyPolicySpecsRequest{})
|
||||
|
||||
e.GET("/api/v1/fleet/packs/{id:[0-9]+}", getPackEndpoint, getPackRequest{})
|
||||
|
||||
e.GET("/api/v1/fleet/software", listSoftwareEndpoint, listSoftwareRequest{})
|
||||
|
@ -12,8 +12,9 @@ import (
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type teamPolicyRequest struct {
|
||||
TeamID uint `url:"team_id"`
|
||||
QueryID uint `json:"query_id"`
|
||||
TeamID uint `url:"team_id"`
|
||||
QueryID uint `json:"query_id"`
|
||||
Resolution string `json:"resolution"`
|
||||
}
|
||||
|
||||
type teamPolicyResponse struct {
|
||||
@ -25,19 +26,19 @@ func (r teamPolicyResponse) error() error { return r.Err }
|
||||
|
||||
func teamPolicyEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
|
||||
req := request.(*teamPolicyRequest)
|
||||
resp, err := svc.NewTeamPolicy(ctx, req.TeamID, req.QueryID)
|
||||
resp, err := svc.NewTeamPolicy(ctx, req.TeamID, req.QueryID, "")
|
||||
if err != nil {
|
||||
return teamPolicyResponse{Err: err}, nil
|
||||
}
|
||||
return teamPolicyResponse{Policy: resp}, nil
|
||||
}
|
||||
|
||||
func (svc Service) NewTeamPolicy(ctx context.Context, teamID uint, queryID uint) (*fleet.Policy, error) {
|
||||
func (svc Service) NewTeamPolicy(ctx context.Context, teamID uint, queryID uint, resolution string) (*fleet.Policy, error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Policy{TeamID: ptr.Uint(teamID)}, fleet.ActionWrite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return svc.ds.NewTeamPolicy(ctx, teamID, queryID)
|
||||
return svc.ds.NewTeamPolicy(ctx, teamID, queryID, resolution)
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -28,6 +28,12 @@ func TestTeamPoliciesAuth(t *testing.T) {
|
||||
ds.DeleteTeamPoliciesFunc = func(ctx context.Context, teamID uint, ids []uint) ([]uint, error) {
|
||||
return nil, nil
|
||||
}
|
||||
ds.TeamByNameFunc = func(ctx context.Context, name string) (*fleet.Team, error) {
|
||||
return &fleet.Team{ID: 1}, nil
|
||||
}
|
||||
ds.ApplyPolicySpecsFunc = func(ctx context.Context, specs []*fleet.PolicySpec) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var testCases = []struct {
|
||||
name string
|
||||
@ -94,7 +100,7 @@ func TestTeamPoliciesAuth(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := viewer.NewContext(context.Background(), viewer.Viewer{User: tt.user})
|
||||
|
||||
_, err := svc.NewTeamPolicy(ctx, 1, 2)
|
||||
_, err := svc.NewTeamPolicy(ctx, 1, 2, "")
|
||||
checkAuthErr(t, tt.shouldFailWrite, err)
|
||||
|
||||
_, err = svc.ListTeamPolicies(ctx, 1)
|
||||
@ -105,6 +111,14 @@ func TestTeamPoliciesAuth(t *testing.T) {
|
||||
|
||||
_, err = svc.DeleteTeamPolicies(ctx, 1, []uint{1})
|
||||
checkAuthErr(t, tt.shouldFailWrite, err)
|
||||
|
||||
err = svc.ApplyPolicySpecs(ctx, []*fleet.PolicySpec{
|
||||
{
|
||||
QueryName: "query1",
|
||||
Team: "team1",
|
||||
},
|
||||
})
|
||||
checkAuthErr(t, tt.shouldFailWrite, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -21,4 +21,58 @@ var (
|
||||
ID: 4,
|
||||
GlobalRole: ptr.String(fleet.RoleObserver),
|
||||
}
|
||||
UserTeamAdminTeam1 = &fleet.User{
|
||||
ID: 5,
|
||||
Teams: []fleet.UserTeam{
|
||||
{
|
||||
Team: fleet.Team{ID: 1},
|
||||
Role: fleet.RoleAdmin,
|
||||
},
|
||||
},
|
||||
}
|
||||
UserTeamAdminTeam2 = &fleet.User{
|
||||
ID: 6,
|
||||
Teams: []fleet.UserTeam{
|
||||
{
|
||||
Team: fleet.Team{ID: 2},
|
||||
Role: fleet.RoleAdmin,
|
||||
},
|
||||
},
|
||||
}
|
||||
UserTeamMaintainerTeam1 = &fleet.User{
|
||||
ID: 7,
|
||||
Teams: []fleet.UserTeam{
|
||||
{
|
||||
Team: fleet.Team{ID: 1},
|
||||
Role: fleet.RoleMaintainer,
|
||||
},
|
||||
},
|
||||
}
|
||||
UserTeamMaintainerTeam2 = &fleet.User{
|
||||
ID: 8,
|
||||
Teams: []fleet.UserTeam{
|
||||
{
|
||||
Team: fleet.Team{ID: 2},
|
||||
Role: fleet.RoleMaintainer,
|
||||
},
|
||||
},
|
||||
}
|
||||
UserTeamObserverTeam1 = &fleet.User{
|
||||
ID: 9,
|
||||
Teams: []fleet.UserTeam{
|
||||
{
|
||||
Team: fleet.Team{ID: 1},
|
||||
Role: fleet.RoleObserver,
|
||||
},
|
||||
},
|
||||
}
|
||||
UserTeamObserverTeam2 = &fleet.User{
|
||||
ID: 10,
|
||||
Teams: []fleet.UserTeam{
|
||||
{
|
||||
Team: fleet.Team{ID: 2},
|
||||
Role: fleet.RoleObserver,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user