Trying to bypass when not allowed returns an error (#40186)
**Related issue:** Resolves #36105
This commit is contained in:
@@ -7,11 +7,24 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
func (ds *Datastore) ConditionalAccessBypassDevice(ctx context.Context, hostID uint) error {
|
||||
const stmt = `
|
||||
const checkStmt = `
|
||||
SELECT
|
||||
COUNT(*)
|
||||
FROM
|
||||
policy_membership pm
|
||||
INNER JOIN
|
||||
policies p ON pm.policy_id = p.id
|
||||
WHERE
|
||||
pm.host_id = ?
|
||||
AND p.conditional_access_bypass_enabled = 0
|
||||
AND pm.passes = 0
|
||||
`
|
||||
const insertStmt = `
|
||||
INSERT INTO
|
||||
host_conditional_access (host_id, bypassed_at)
|
||||
VALUES
|
||||
@@ -19,7 +32,17 @@ func (ds *Datastore) ConditionalAccessBypassDevice(ctx context.Context, hostID u
|
||||
ON DUPLICATE KEY UPDATE
|
||||
bypassed_at = NOW(6)`
|
||||
|
||||
if _, err := ds.writer(ctx).ExecContext(ctx, stmt, hostID); err != nil {
|
||||
var blockCount uint
|
||||
|
||||
if err := sqlx.GetContext(ctx, ds.writer(ctx), &blockCount, checkStmt, hostID); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "checking failing policy count")
|
||||
}
|
||||
|
||||
if blockCount != 0 {
|
||||
return &fleet.BadRequestError{Message: "host has failing non-bypassable policies"}
|
||||
}
|
||||
|
||||
if _, err := ds.writer(ctx).ExecContext(ctx, insertStmt, hostID); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "inserting host conditional bypass")
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
"github.com/fleetdm/fleet/v4/server/test"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -18,6 +20,7 @@ func TestConditionalAccessBypass(t *testing.T) {
|
||||
fn func(t *testing.T, ds *Datastore)
|
||||
}{
|
||||
{"ConditionalAccessBypassDevice", testConditionalAccessBypassDevice},
|
||||
{"ConditionalAccessBypassDeviceWithBlockingPolicy", testConditionalAccessBypassDeviceWithBlockingPolicy},
|
||||
{"ConditionalAccessConsumeBypass", testConditionalAccessConsumeBypass},
|
||||
{"ConditionalAccessClearBypasses", testConditionalAccessClearBypasses},
|
||||
{"ConditionalAccessBypassDeletedWithHost", testConditionalAccessBypassDeletedWithHost},
|
||||
@@ -268,3 +271,51 @@ func testConditionalAccessBypassedAt(t *testing.T, ds *Datastore) {
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, bypassedAtOther)
|
||||
}
|
||||
|
||||
func testConditionalAccessBypassDeviceWithBlockingPolicy(t *testing.T, ds *Datastore) {
|
||||
ctx := context.Background()
|
||||
|
||||
user := test.NewUser(t, ds, "Alice", "alice@example.com", true)
|
||||
|
||||
host, err := ds.NewHost(ctx, &fleet.Host{
|
||||
DetailUpdatedAt: time.Now(),
|
||||
LabelUpdatedAt: time.Now(),
|
||||
PolicyUpdatedAt: time.Now(),
|
||||
SeenTime: time.Now(),
|
||||
NodeKey: ptr.String("blocking-policy-host"),
|
||||
UUID: "blocking-policy-uuid",
|
||||
Hostname: "blocking.local",
|
||||
PrimaryIP: "192.168.1.10",
|
||||
PrimaryMac: "30-65-EC-6F-C4-70",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a global policy with conditional_access_bypass_enabled defaulting to 1 (bypassable)
|
||||
policy, err := ds.NewGlobalPolicy(ctx, &user.ID, fleet.PolicyPayload{
|
||||
Name: "non-bypassable-policy",
|
||||
Query: "select 1;",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Set conditional_access_bypass_enabled = 0 to make it non-bypassable
|
||||
ExecAdhocSQL(t, ds, func(db sqlx.ExtContext) error {
|
||||
_, err := db.ExecContext(ctx, `UPDATE policies SET conditional_access_bypass_enabled = 0 WHERE id = ?`, policy.ID)
|
||||
return err
|
||||
})
|
||||
|
||||
// Record a failing result for this policy on the host
|
||||
err = ds.RecordPolicyQueryExecutions(ctx, host, map[uint]*bool{policy.ID: ptr.Bool(false)}, time.Now(), false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Bypass should fail because the host has a failing non-bypassable policy
|
||||
err = ds.ConditionalAccessBypassDevice(ctx, host.ID)
|
||||
require.Error(t, err)
|
||||
var badReqErr *fleet.BadRequestError
|
||||
require.ErrorAs(t, err, &badReqErr)
|
||||
|
||||
// Verify no host_conditional_access row was created
|
||||
var count int
|
||||
innerErr := ds.writer(ctx).GetContext(ctx, &count, "SELECT COUNT(*) FROM host_conditional_access WHERE host_id = ?", host.ID)
|
||||
require.NoError(t, innerErr)
|
||||
require.Equal(t, 0, count)
|
||||
}
|
||||
|
||||
@@ -24176,6 +24176,39 @@ FqU+KJOed6qlzj7qy+u5l6CQeajLGdjUxFlFyw==
|
||||
require.NoError(t, getDeviceHostResp.Err)
|
||||
require.False(t, getDeviceHostResp.Host.ConditionalAccessBypassed)
|
||||
})
|
||||
|
||||
t.Run("bypass fails when host has failing non-bypassable policy", func(t *testing.T) {
|
||||
token := fmt.Sprintf("bypass-nonbypassable-%s", uuid.New().String())
|
||||
host := createHostAndDeviceToken(t, s.ds, token)
|
||||
|
||||
// Create a global policy
|
||||
adminUser := s.users["admin1@example.com"]
|
||||
policy, err := s.ds.NewGlobalPolicy(ctx, &adminUser.ID, fleet.PolicyPayload{
|
||||
Name: fmt.Sprintf("non-bypassable-%s", uuid.New().String()),
|
||||
Query: "select 1;",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Make the policy non-bypassable
|
||||
mysql.ExecAdhocSQL(t, s.ds, func(db sqlx.ExtContext) error {
|
||||
_, innerErr := db.ExecContext(ctx, `UPDATE policies SET conditional_access_bypass_enabled = 0 WHERE id = ?`, policy.ID)
|
||||
return innerErr
|
||||
})
|
||||
|
||||
// Record a failing result for this policy on the host
|
||||
err = s.ds.RecordPolicyQueryExecutions(ctx, host, map[uint]*bool{policy.ID: ptr.Bool(false)}, time.Now(), false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Bypass should fail with 400 Bad Request
|
||||
var bypassResp bypassConditionalAccessResponse
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/v1/fleet/device/%s/bypass_conditional_access", token),
|
||||
nil, http.StatusBadRequest, &bypassResp)
|
||||
|
||||
// Verify no bypass row was created
|
||||
bypassedAt, err := s.ds.ConditionalAccessBypassedAt(ctx, host.ID)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, bypassedAt)
|
||||
})
|
||||
}
|
||||
|
||||
// generateTestCertForDeviceAuth generates a test certificate for device authentication.
|
||||
|
||||
Reference in New Issue
Block a user