tests/enrollment_keyless_test.go
Rama master · réplica de lectura del repositorio original.
package main
import (
"crypto/sha256"
"encoding/hex"
"net/http"
"testing"
"time"
"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/shellhub-io/shellhub/tests/environment"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func testKeylessEnrollment(t *testing.T, compose *environment.DockerCompose) {
t.Helper()
legacy := compose.ProvisioningKey(t, string(models.ProvisioningKeyTypeLegacy))
require.Equal(t, models.ProvisioningKeyTypeLegacy, legacy.Type)
t.Run("a tenant-only enrollment is attributed to the namespace's legacy key", func(t *testing.T) {
device := enroll(t, compose, newDeviceAuthRequest(t, "keyless", "02:00:00:00:40:01"))
assert.Equal(t, models.DeviceStatusPending, device.Status)
assert.Equal(t, legacy.ID, device.ProvisioningKeyID)
assert.NotNil(t, eventOf(compose.ProvisioningKeyHistory(t, legacy.ID), device.UID))
})
t.Run("the tenant-only registrations are read by a member who may accept devices and refused to an observer", func(t *testing.T) {
device := enroll(t, compose, newDeviceAuthRequest(t, "keyless-listed", "02:00:00:00:40:09"))
for _, tc := range []struct {
role authorizer.Role
status int
}{
{authorizer.RoleOperator, http.StatusOK},
{authorizer.RoleObserver, http.StatusForbidden},
} {
username := "registrations-" + string(tc.role)
compose.NewUser(t, username, username+"@ossystems.com.br", ShellHubPassword)
compose.NewMember(t, username, ShellHubNamespaceName, string(tc.role))
member := signInto(t, compose, username, ShellHubPassword)
events := []models.ProvisioningKeyEvent{}
resp, err := asBearer(t, compose, member.Token).SetResult(&events).Get("/api/devices/tenant-registrations")
require.NoError(t, err)
require.Equal(t, tc.status, resp.StatusCode(), "%s: %s", tc.role, resp.String())
if tc.status == http.StatusOK {
assert.NotNil(t, eventOf(events, device.UID), "the operator did not see the tenant-only registration")
}
}
resp, err := compose.R(t.Context()).SetQueryParam("sort_by", "fingerprint").Get("/api/devices/tenant-registrations")
require.NoError(t, err)
assert.Equal(t, http.StatusBadRequest, resp.StatusCode(), resp.String())
})
t.Run("an agent given only the tenant lands pending and serves once accepted", func(t *testing.T) {
startAgent(t, t.Context(), compose, NewAgentContainerWithTenantOnly(ShellHubNamespace), NewAgentContainerWithHostname("keyless-agent"))
var pending models.Device
require.EventuallyWithT(t, func(tt *assert.CollectT) {
for _, device := range compose.ListDevices(t, models.DeviceStatusPending) {
if device.Name == "keyless-agent" {
pending = device
}
}
assert.NotEmpty(tt, pending.UID, "the agent did not land pending")
}, 30*time.Second, time.Second)
assert.Equal(t, legacy.ID, pending.ProvisioningKeyID)
compose.UpdateDeviceStatus(t, pending.UID, environment.DeviceActionAccept)
compose.AwaitDeviceOnline(t, pending.UID)
})
}
func testAgentLoginWithTenant(t *testing.T, compose *environment.DockerCompose) {
t.Helper()
t.Run("an agent given the tenant lands pending and login prints a code that accepts it", func(t *testing.T) {
login := startAgentCommand(t, compose, []string{"./agent", "login"}, NewAgentContainerWithHostname("legacy-login"))
code := awaitAgentPairingCode(t, login)
preview := requireLoginCodePreview(t, compose, code)
require.Equal(t, "legacy-login", preview.Name)
require.Equal(t, models.DeviceStatusPending, preview.Status)
compose.UpdateDeviceStatus(t, preview.UID, environment.DeviceActionAccept)
assert.Zero(t, awaitAgentExit(t, login))
})
}
func testEnrollmentUID(t *testing.T, compose *environment.DockerCompose) {
t.Helper()
t.Run("a device enrolled by key keeps its uid with the tenant it persisted, with or without the key", func(t *testing.T) {
key := requireHarnessProvisioningKey(t, compose)
keyed := newKeyedDeviceAuthRequest(t, key, "uid-by-key", "02:00:00:00:40:07")
device := enroll(t, compose, keyed)
withTenant := keyed
withTenant.TenantID = ShellHubNamespace
reauthDevice(t, compose, withTenant, device.UID)
require.Equal(t, device.UID, authDevice(t, compose, withTenant).UID)
tenantOnly := withTenant
tenantOnly.ProvisioningKey = ""
reauthDevice(t, compose, tenantOnly, device.UID)
require.Equal(t, device.UID, authDevice(t, compose, tenantOnly).UID)
})
}
func testProvisioningKeyRefusedInLegacy(t *testing.T, compose *environment.DockerCompose) {
t.Helper()
t.Run("a presented provisioning key is refused with 403 and enrolls nothing", func(t *testing.T) {
req := newKeyedDeviceAuthRequest(t, unissuedProvisioningKey, "legacy-keyed", "02:00:00:00:40:05")
req.TenantID = ShellHubNamespace
requireAuthForbidden(t, compose, req)
})
}
func testPairingRefusedInLegacy(t *testing.T, compose *environment.DockerCompose) {
t.Helper()
t.Run("accepting a pairing into the namespace is refused with 403 and enrolls nothing", func(t *testing.T) {
req := newPairingRequest(t, "legacy-pairing", "02:00:00:00:40:06")
pairing := startPairing(t, compose, req)
_, resp, err := postPairingAccept(compose.R(t.Context()), pairing.Code)
require.NoError(t, err)
assert.Equal(t, http.StatusForbidden, resp.StatusCode(), resp.String())
assert.NotContains(t, deviceNames(compose.ListDevices(t, models.DeviceStatusEmpty)), req.Hostname)
})
}
func testProvisioningKeyAPIInLegacy(t *testing.T, compose *environment.DockerCompose) {
t.Helper()
t.Run("creating a key is refused", func(t *testing.T) {
resp, err := compose.R(t.Context()).
SetBody(&requests.CreateProvisioningKey{Name: "fleet", Mode: string(models.ProvisioningKeyModeManual)}).
Post("/api/namespaces/provisioning-key")
require.NoError(t, err)
assert.Equal(t, http.StatusForbidden, resp.StatusCode(), resp.String())
})
t.Run("editing a key is refused before the key is looked up", func(t *testing.T) {
resp, err := compose.PatchProvisioningKey(t.Context(), "no-such-key", map[string]any{"disabled": true})
require.NoError(t, err)
assert.Equal(t, http.StatusForbidden, resp.StatusCode(), resp.String())
})
t.Run("the system keys are still listed", func(t *testing.T) {
assert.Equal(t, models.ProvisioningKeyTypeLegacy, compose.ProvisioningKey(t, string(models.ProvisioningKeyTypeLegacy)).Type)
assert.Equal(t, models.ProvisioningKeyTypePairing, compose.ProvisioningKey(t, string(models.ProvisioningKeyTypePairing)).Type)
})
}
func testSystemKeyPresented(t *testing.T, compose *environment.DockerCompose, alongsideTenantStatus int) {
t.Helper()
cases := []struct {
keyType models.ProvisioningKeyType
plaintext string
mac string
}{
{keyType: models.ProvisioningKeyTypeLegacy, plaintext: "system:" + ShellHubNamespace, mac: "02:00:00:00:40:03"},
{keyType: models.ProvisioningKeyTypePairing, plaintext: "system:pairing:" + ShellHubNamespace, mac: "02:00:00:00:40:04"},
}
for _, tc := range cases {
t.Run("the "+string(tc.keyType)+" key", func(t *testing.T) {
digest := sha256.Sum256([]byte(tc.plaintext))
require.Equal(t, hex.EncodeToString(digest[:]), compose.ProvisioningKey(t, string(tc.keyType)).ID,
"the plaintext must be the one the system key is the digest of")
t.Run("enrolls nothing in place of a tenant id", func(t *testing.T) {
requireAuthRefused(t, compose, newKeyedDeviceAuthRequest(t, tc.plaintext, "system-"+string(tc.keyType), tc.mac))
})
t.Run("enrolls nothing alongside the tenant id", func(t *testing.T) {
req := newKeyedDeviceAuthRequest(t, tc.plaintext, "system-"+string(tc.keyType)+"-tenant", tc.mac)
req.TenantID = ShellHubNamespace
requireAuthAnswers(t, compose, req, alongsideTenantStatus)
})
})
}
}