tests/access_policy_selector_test.go
Rama master · réplica de lectura del repositorio original.
package main
import (
"context"
"net/http"
"regexp"
"testing"
"github.com/shellhub-io/shellhub/pkg/api/authorizer"
"github.com/shellhub-io/shellhub/pkg/api/requests"
"github.com/shellhub-io/shellhub/pkg/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestAccessPolicySelectorTags covers who may change which devices an access policy selects: a tag
// a policy's filter holds is moved on or off a device only by a role that may edit the policy.
func TestAccessPolicySelectorTags(t *testing.T) {
t.Parallel()
ctx := context.Background()
compose := newSSHEnvironment(t, ctx, newNamespace)
_, device := startAcceptedAgent(t, ctx, compose)
sshid := deviceSSHID(device)
t.Run("an operator does not attach a tag an allow policy selects by", func(t *testing.T) {
member, signer := memberWithKey(t, compose, "attacher", authorizer.RoleOperator)
createTag(t, compose, "selected")
t.Cleanup(func() { deleteTag(t, compose, "selected") })
grant(t, compose, &requests.AccessPolicyCreate{
Name: "selected",
Subject: userSubject(member.ID),
Filter: requests.AccessPolicyFilter{Tags: []string{"selected"}},
Logins: []string{"*"},
})
resp, err := asBearer(t, compose, member.Token).Post("/api/devices/" + device.UID + "/tags/selected")
require.NoError(t, err)
t.Cleanup(func() {
_, _ = compose.R(context.WithoutCancel(t.Context())).Delete("/api/devices/" + device.UID + "/tags/selected")
})
assert.Equal(t, http.StatusForbidden, resp.StatusCode(), resp.String())
mark := compose.ServerLogMark(t)
requireRefusedAtAuth(t, compose, sshid, signer)
compose.AwaitServerLogLine(t, mark, "reason="+string(models.ReasonNoGrant), "user="+member.ID)
})
t.Run("an operator does not detach a tag a deny policy selects by", func(t *testing.T) {
member, signer := memberWithKey(t, compose, "detacher", authorizer.RoleOperator)
createTag(t, compose, "fenced")
t.Cleanup(func() { deleteTag(t, compose, "fenced") })
tagDevice(t, compose, device.UID, "fenced")
grant(t, compose, &requests.AccessPolicyCreate{
Name: "operators",
Subject: roleSubject(authorizer.RoleOperator),
Filter: requests.AccessPolicyFilter{All: true},
Logins: []string{"*"},
})
grant(t, compose, &requests.AccessPolicyCreate{
Name: "fenced",
Subject: userSubject(member.ID),
Filter: requests.AccessPolicyFilter{Tags: []string{"fenced"}},
Logins: []string{"*"},
Action: string(models.PolicyActionDeny),
})
resp, err := asBearer(t, compose, member.Token).Delete("/api/devices/" + device.UID + "/tags/fenced")
require.NoError(t, err)
t.Cleanup(func() {
_, _ = compose.R(context.WithoutCancel(t.Context())).Post("/api/devices/" + device.UID + "/tags/fenced")
})
assert.Equal(t, http.StatusForbidden, resp.StatusCode(), resp.String())
mark := compose.ServerLogMark(t)
requireRefusedAtAuth(t, compose, sshid, signer)
compose.AwaitServerLogLine(t, mark, "reason="+string(models.ReasonDeniedByPolicy), "user="+member.ID)
})
t.Run("an operator attaches and detaches a tag no rule selects by", func(t *testing.T) {
member := newMember(t, compose, "labeller", authorizer.RoleOperator)
createTag(t, compose, "label")
t.Cleanup(func() { deleteTag(t, compose, "label") })
resp, err := asBearer(t, compose, member.Token).Post("/api/devices/" + device.UID + "/tags/label")
require.NoError(t, err)
require.Equal(t, http.StatusNoContent, resp.StatusCode(), resp.String())
requireDeviceTags(t, compose, device.UID, "label")
resp, err = asBearer(t, compose, member.Token).Delete("/api/devices/" + device.UID + "/tags/label")
require.NoError(t, err)
require.Equal(t, http.StatusNoContent, resp.StatusCode(), resp.String())
requireDeviceTags(t, compose, device.UID)
})
}
// TestPublicKeySelectorHostname covers who may rename a device a public key selects by name: a
// rename that moves the device out of the key's pattern needs a role that may edit the key, and
// one between two names the pattern matches does not.
func TestPublicKeySelectorHostname(t *testing.T) {
t.Parallel()
ctx := context.Background()
compose := newSSHEnvironment(t, ctx, legacyNamespace)
_, device := startAcceptedAgent(t, ctx, compose)
kept := device.Name + "-kept"
pattern := regexp.QuoteMeta(device.Name) + "|" + regexp.QuoteMeta(kept)
signer := registerPublicKey(t, compose, ".*", requests.PublicKeyFilter{Hostname: pattern})
member := newMember(t, compose, "renamer", authorizer.RoleOperator)
requireDeviceName := func(t *testing.T, name string) {
t.Helper()
current, resp, err := compose.GetDevice(t.Context(), device.UID)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode(), resp.String())
assert.Equal(t, name, current.Name)
}
rename := func(t *testing.T, name string) int {
t.Helper()
resp, err := asBearer(t, compose, member.Token).SetBody(map[string]string{"name": name}).Put("/api/devices/" + device.UID)
require.NoError(t, err)
return resp.StatusCode()
}
t.Run("an operator does not rename a device out of a key's name pattern", func(t *testing.T) {
t.Cleanup(func() {
_, _ = compose.R(context.WithoutCancel(t.Context())).SetBody(map[string]string{"name": device.Name}).Put("/api/devices/" + device.UID)
})
assert.Equal(t, http.StatusForbidden, rename(t, "renamed-away"))
requireDeviceName(t, device.Name)
requireKeyLogsIn(t, compose, deviceSSHID(device), signer)
})
t.Run("an operator renames a device within a key's name pattern", func(t *testing.T) {
t.Cleanup(func() {
_, _ = compose.R(context.WithoutCancel(t.Context())).SetBody(map[string]string{"name": device.Name}).Put("/api/devices/" + device.UID)
})
require.Equal(t, http.StatusOK, rename(t, kept))
requireDeviceName(t, kept)
})
}