Complete platform management workflows
This commit is contained in:
@@ -0,0 +1,398 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"browser.local/platform/domain"
|
||||
)
|
||||
|
||||
func TestCoreServiceGeneratesRunDistributionWithEncryptedSingletonKey(t *testing.T) {
|
||||
svc, session, instance := newDistributionTestFixture(t)
|
||||
|
||||
distribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{
|
||||
ServerInstanceID: instance.ID,
|
||||
TargetOS: "linux",
|
||||
TargetArch: "amd64",
|
||||
IdempotencyKey: "idem-run-generate",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("generate run distribution: %v", err)
|
||||
}
|
||||
if distribution.KeyGeneration != 1 || distribution.SecretRef == "" || distribution.Status != domain.DistributionStatusAvailable {
|
||||
t.Fatalf("unexpected run distribution: %+v", distribution)
|
||||
}
|
||||
|
||||
keys, err := svc.store.EncryptedComponentKeys().List(domain.EncryptedComponentKeyFilter{
|
||||
ServerInstanceID: instance.ID,
|
||||
ComponentKind: domain.DistributionComponentRun,
|
||||
Status: domain.ComponentKeyStatusActive,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("list component keys: %v", err)
|
||||
}
|
||||
if len(keys) != 1 || keys[0].Generation != 1 || !strings.HasPrefix(keys[0].EncryptedKey, "enc:v1:") {
|
||||
t.Fatalf("expected one active encrypted run key, got %+v", keys)
|
||||
}
|
||||
|
||||
config := readGeneratedPackageConfig(t, svc, session, distribution.ArtifactID)
|
||||
if config.AuthKey == "" || config.AuthKey == keys[0].EncryptedKey || strings.Contains(distribution.SecretRef, config.AuthKey) {
|
||||
t.Fatalf("run package key leaked through metadata or was not encrypted, config=%+v key=%+v distribution=%+v", config, keys[0], distribution)
|
||||
}
|
||||
auth, err := svc.AuthenticateComponent(domain.ComponentAuthenticationRequest{
|
||||
ServerInstanceID: instance.ID,
|
||||
ComponentKind: domain.DistributionComponentRun,
|
||||
Generation: config.KeyGeneration,
|
||||
Key: config.AuthKey,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("authenticate run: %v", err)
|
||||
}
|
||||
if !auth.Allowed {
|
||||
t.Fatalf("expected current run key to authenticate, got %+v", auth)
|
||||
}
|
||||
|
||||
second, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{
|
||||
ServerInstanceID: instance.ID,
|
||||
TargetOS: "linux",
|
||||
TargetArch: "amd64",
|
||||
IdempotencyKey: "idem-run-generate-second",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("generate second run distribution: %v", err)
|
||||
}
|
||||
if second.KeyGeneration != 1 || second.SecretRef != distribution.SecretRef {
|
||||
t.Fatalf("expected second package to reuse current singleton key, got first=%+v second=%+v", distribution, second)
|
||||
}
|
||||
keys, err = svc.store.EncryptedComponentKeys().List(domain.EncryptedComponentKeyFilter{
|
||||
ServerInstanceID: instance.ID,
|
||||
ComponentKind: domain.DistributionComponentRun,
|
||||
Status: domain.ComponentKeyStatusActive,
|
||||
})
|
||||
if err != nil || len(keys) != 1 {
|
||||
t.Fatalf("expected one active key after second generation, keys=%+v err=%v", keys, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoreServiceResetRunKeyRevokesOldPackagesAndRequiresRegeneration(t *testing.T) {
|
||||
svc, session, instance := newDistributionTestFixture(t)
|
||||
distribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{
|
||||
ServerInstanceID: instance.ID,
|
||||
TargetOS: "linux",
|
||||
TargetArch: "amd64",
|
||||
IdempotencyKey: "idem-run-before-reset",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("generate run distribution: %v", err)
|
||||
}
|
||||
oldConfig := readGeneratedPackageConfig(t, svc, session, distribution.ArtifactID)
|
||||
|
||||
reset, err := svc.ResetComponentKeyForSession(session, domain.ComponentKeyResetRequest{
|
||||
ServerInstanceID: instance.ID,
|
||||
ComponentKind: domain.DistributionComponentRun,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("reset run key: %v", err)
|
||||
}
|
||||
if reset.Generation != 2 || reset.Status != domain.ComponentKeyStatusActive {
|
||||
t.Fatalf("expected reset key generation 2, got %+v", reset)
|
||||
}
|
||||
oldDistribution, err := svc.store.RunDistributions().Get(distribution.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get old distribution: %v", err)
|
||||
}
|
||||
if oldDistribution.Status != domain.DistributionStatusRevoked {
|
||||
t.Fatalf("expected old distribution revoked, got %+v", oldDistribution)
|
||||
}
|
||||
if _, err := svc.OpenArtifactDownloadForSession(session, domain.ArtifactDownloadReferenceRequest{ArtifactID: distribution.ArtifactID}); err == nil || !strings.Contains(err.Error(), "available") {
|
||||
t.Fatalf("expected old artifact download to be unavailable, got %v", err)
|
||||
}
|
||||
|
||||
auth, err := svc.AuthenticateComponent(domain.ComponentAuthenticationRequest{
|
||||
ServerInstanceID: instance.ID,
|
||||
ComponentKind: domain.DistributionComponentRun,
|
||||
Generation: oldConfig.KeyGeneration,
|
||||
Key: oldConfig.AuthKey,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("authenticate old key: %v", err)
|
||||
}
|
||||
if auth.Allowed || !strings.Contains(auth.Reason, "generation") {
|
||||
t.Fatalf("expected old package authentication denial, got %+v", auth)
|
||||
}
|
||||
|
||||
newDistribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{
|
||||
ServerInstanceID: instance.ID,
|
||||
TargetOS: "linux",
|
||||
TargetArch: "amd64",
|
||||
IdempotencyKey: "idem-run-after-reset",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("generate run distribution after reset: %v", err)
|
||||
}
|
||||
newConfig := readGeneratedPackageConfig(t, svc, session, newDistribution.ArtifactID)
|
||||
if newDistribution.KeyGeneration != 2 || newConfig.AuthKey == oldConfig.AuthKey {
|
||||
t.Fatalf("expected regenerated package with new generation/key, old=%+v new=%+v", oldConfig, newConfig)
|
||||
}
|
||||
auth, err = svc.AuthenticateComponent(domain.ComponentAuthenticationRequest{
|
||||
ServerInstanceID: instance.ID,
|
||||
ComponentKind: domain.DistributionComponentRun,
|
||||
Generation: newConfig.KeyGeneration,
|
||||
Key: newConfig.AuthKey,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("authenticate new key: %v", err)
|
||||
}
|
||||
if !auth.Allowed {
|
||||
t.Fatalf("expected regenerated package to authenticate, got %+v", auth)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoreServiceBuildsClientManagerWithDistinctKeyAndAuditsSensitiveOperations(t *testing.T) {
|
||||
svc, session, instance := newDistributionTestFixture(t)
|
||||
runDistribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{
|
||||
ServerInstanceID: instance.ID,
|
||||
TargetOS: "linux",
|
||||
TargetArch: "amd64",
|
||||
IdempotencyKey: "idem-run-for-client",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("generate run distribution: %v", err)
|
||||
}
|
||||
if _, err := svc.OpenArtifactDownloadForSession(session, domain.ArtifactDownloadReferenceRequest{ArtifactID: runDistribution.ArtifactID}); err != nil {
|
||||
t.Fatalf("open run download: %v", err)
|
||||
}
|
||||
runConfig := readGeneratedPackageConfig(t, svc, session, runDistribution.ArtifactID)
|
||||
|
||||
clientDistribution, err := svc.GenerateClientManagerDistributionForSession(session, domain.ClientManagerBuildRequest{
|
||||
ServerInstanceID: instance.ID,
|
||||
ProfileKey: "scum-client-manager",
|
||||
TargetOS: "windows",
|
||||
TargetArch: "amd64",
|
||||
RepositoryURL: "https://github.com/F88888/scum_client.git",
|
||||
SourceRevision: "main",
|
||||
IdempotencyKey: "idem-client-manager",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("generate client-manager distribution: %v", err)
|
||||
}
|
||||
clientConfig := readGeneratedPackageConfig(t, svc, session, clientDistribution.ArtifactID)
|
||||
if clientDistribution.KeyGeneration != 1 || clientDistribution.BuildJobID == "" || clientDistribution.Status != domain.DistributionStatusAvailable {
|
||||
t.Fatalf("unexpected client-manager distribution: %+v", clientDistribution)
|
||||
}
|
||||
if clientConfig.AuthKey == runConfig.AuthKey || clientDistribution.SecretRef == runDistribution.SecretRef {
|
||||
t.Fatalf("client-manager must use a distinct key/ref, run=%+v client=%+v", runConfig, clientConfig)
|
||||
}
|
||||
build, err := svc.store.ClientManagerBuildJobs().Get(clientDistribution.BuildJobID)
|
||||
if err != nil {
|
||||
t.Fatalf("get build job: %v", err)
|
||||
}
|
||||
if build.Status != domain.DistributionJobStatusSucceeded || build.RepositoryURL != "https://github.com/F88888/scum_client.git" || build.SourceRevision != "main" {
|
||||
t.Fatalf("unexpected build job: %+v", build)
|
||||
}
|
||||
if build.LogsRef == "" || !strings.HasPrefix(build.LogsRef, "artifact://") {
|
||||
t.Fatalf("expected redacted build log artifact ref, got %+v", build)
|
||||
}
|
||||
packagePayload := readClientManagerPackage(t, svc, session, clientDistribution.ArtifactID)
|
||||
if packagePayload.Checkout.CheckoutRef != "branch/main" || packagePayload.Config.AuthKey != clientConfig.AuthKey || packagePayload.KeyFingerprint == "" {
|
||||
t.Fatalf("expected package checkout metadata and injected config, got %+v", packagePayload)
|
||||
}
|
||||
if len(packagePayload.OutputArtifacts) == 0 || packagePayload.BuildLogRef != build.LogsRef {
|
||||
t.Fatalf("expected output artifacts and build log ref, got %+v build=%+v", packagePayload, build)
|
||||
}
|
||||
buildLog := readArtifactString(t, svc, session, strings.TrimPrefix(build.LogsRef, "artifact://"))
|
||||
for _, expected := range []string{"client-manager checkout prepared", "checkoutRef=branch/main", "dependencyCheck=typed build profile accepted", "configInjection=secret ref"} {
|
||||
if !strings.Contains(buildLog, expected) {
|
||||
t.Fatalf("expected build log to contain %q, got %q", expected, buildLog)
|
||||
}
|
||||
}
|
||||
for _, forbidden := range []string{runConfig.AuthKey, clientConfig.AuthKey, "password=", "unix://", "tcp://", "/Users/", "mysql://", "sqlite://"} {
|
||||
if strings.Contains(buildLog, forbidden) {
|
||||
t.Fatalf("build log leaked forbidden fragment %q: %s", forbidden, buildLog)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = svc.GenerateClientManagerDistributionForSession(session, domain.ClientManagerBuildRequest{
|
||||
ServerInstanceID: instance.ID,
|
||||
ProfileKey: "scum-client-manager",
|
||||
TargetOS: "darwin",
|
||||
TargetArch: "amd64",
|
||||
RepositoryURL: "https://github.com/F88888/scum_client.git",
|
||||
IdempotencyKey: "idem-client-manager-denied",
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "targetOs") {
|
||||
t.Fatalf("expected unsupported target denial, got %v", err)
|
||||
}
|
||||
|
||||
if _, err := svc.ResetComponentKeyForSession(session, domain.ComponentKeyResetRequest{
|
||||
ServerInstanceID: instance.ID,
|
||||
ComponentKind: domain.DistributionComponentClientManager,
|
||||
ComponentKey: "scum-client-manager",
|
||||
}); err != nil {
|
||||
t.Fatalf("reset client-manager key: %v", err)
|
||||
}
|
||||
auth, err := svc.AuthenticateComponent(domain.ComponentAuthenticationRequest{
|
||||
ServerInstanceID: instance.ID,
|
||||
ComponentKind: domain.DistributionComponentClientManager,
|
||||
ComponentKey: "scum-client-manager",
|
||||
Generation: clientConfig.KeyGeneration,
|
||||
Key: clientConfig.AuthKey,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("authenticate old client key: %v", err)
|
||||
}
|
||||
if auth.Allowed {
|
||||
t.Fatalf("expected old client-manager key to be denied after reset, got %+v", auth)
|
||||
}
|
||||
|
||||
audits, err := svc.ListAuditEvents(domain.AuditEventFilter{ResourceID: instance.ID})
|
||||
if err != nil {
|
||||
t.Fatalf("list audits: %v", err)
|
||||
}
|
||||
actions := map[string]bool{}
|
||||
for _, audit := range audits {
|
||||
actions[audit.Action] = true
|
||||
for _, forbidden := range []string{runConfig.AuthKey, clientConfig.AuthKey, "password=", "unix://", "/Users/"} {
|
||||
if strings.Contains(audit.Summary, forbidden) {
|
||||
t.Fatalf("audit leaked forbidden fragment %q in %+v", forbidden, audit)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, action := range []string{"run.generate", "run.download", "client-manager.build", "client-manager.build.denied", "runtime-key.reset"} {
|
||||
if !actions[action] {
|
||||
t.Fatalf("expected audit action %q in %+v", action, audits)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newDistributionTestFixture(t *testing.T) (*CoreService, string, domain.ServerInstance) {
|
||||
t.Helper()
|
||||
svc := newTestCoreService()
|
||||
plugin, endpoint := createPluginAndRunEndpoint(t, svc)
|
||||
plugin.SupportedOS = []string{"linux", "windows"}
|
||||
plugin.DeclaredPermissions = append(plugin.DeclaredPermissions,
|
||||
"server.run.distribution",
|
||||
"server.client-manager.manage",
|
||||
"server.dependencies.manage",
|
||||
)
|
||||
plugin.RequiredRunCapabilities = append(plugin.RequiredRunCapabilities,
|
||||
domain.JobCapabilityRunSelfUpdate,
|
||||
domain.JobCapabilityDependenciesCheck,
|
||||
domain.JobCapabilityDependenciesInstall,
|
||||
domain.JobCapabilityLogsBackfill,
|
||||
)
|
||||
plugin.BridgeActions = append(plugin.BridgeActions,
|
||||
string(domain.PluginBridgeActionRunDistribution),
|
||||
string(domain.PluginBridgeActionClientManager),
|
||||
string(domain.PluginBridgeActionDependenciesRequest),
|
||||
string(domain.PluginBridgeActionLogsBackfillRequest),
|
||||
)
|
||||
if err := svc.store.GamePlugins().Update(plugin); err != nil {
|
||||
t.Fatalf("update plugin fixture: %v", err)
|
||||
}
|
||||
endpoint.Capabilities = append(endpoint.Capabilities,
|
||||
domain.JobCapabilityRunSelfUpdate,
|
||||
domain.JobCapabilityDependenciesCheck,
|
||||
domain.JobCapabilityDependenciesInstall,
|
||||
domain.JobCapabilityLogsBackfill,
|
||||
)
|
||||
if err := svc.store.RunEndpoints().Update(endpoint); err != nil {
|
||||
t.Fatalf("update endpoint fixture: %v", err)
|
||||
}
|
||||
session := createServiceUserAndLogin(t, svc, domain.User{
|
||||
ID: "user-distribution-owner",
|
||||
DisplayName: "Distribution Owner",
|
||||
Email: "distribution-owner@example.test",
|
||||
Roles: []string{"server-owner"},
|
||||
PasswordHash: "secret-password",
|
||||
})
|
||||
instance, err := svc.CreateServerInstanceForSession(session, domain.ServerInstance{
|
||||
ID: "server-distribution",
|
||||
PluginID: plugin.ID,
|
||||
RunEndpointID: endpoint.ID,
|
||||
Name: "Distribution Server",
|
||||
State: domain.ServerInstanceStateReady,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create distribution server: %v", err)
|
||||
}
|
||||
return svc, session, instance
|
||||
}
|
||||
|
||||
func readGeneratedPackageConfig(t *testing.T, svc *CoreService, session string, artifactID string) generatedPackageConfig {
|
||||
t.Helper()
|
||||
content, err := svc.ReadArtifactContentForSession(session, domain.ArtifactContentRequest{ArtifactID: artifactID})
|
||||
if err != nil {
|
||||
t.Fatalf("read artifact content: %v", err)
|
||||
}
|
||||
var config generatedPackageConfig
|
||||
if err := json.Unmarshal(content.Payload, &config); err != nil {
|
||||
t.Fatalf("unmarshal generated config: %v", err)
|
||||
}
|
||||
if config.AuthKey == "" {
|
||||
var packagePayload generatedClientManagerPackage
|
||||
if err := json.Unmarshal(content.Payload, &packagePayload); err != nil {
|
||||
t.Fatalf("unmarshal generated client-manager package: %v", err)
|
||||
}
|
||||
config = packagePayload.Config
|
||||
}
|
||||
if config.AuthKey == "" || config.SecretRef == "" || config.KeyGeneration <= 0 {
|
||||
t.Fatalf("generated package config is incomplete: %+v", config)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func readClientManagerPackage(t *testing.T, svc *CoreService, session string, artifactID string) generatedClientManagerPackage {
|
||||
t.Helper()
|
||||
content, err := svc.ReadArtifactContentForSession(session, domain.ArtifactContentRequest{ArtifactID: artifactID})
|
||||
if err != nil {
|
||||
t.Fatalf("read client-manager package content: %v", err)
|
||||
}
|
||||
var packagePayload generatedClientManagerPackage
|
||||
if err := json.Unmarshal(content.Payload, &packagePayload); err != nil {
|
||||
t.Fatalf("unmarshal generated client-manager package: %v", err)
|
||||
}
|
||||
return packagePayload
|
||||
}
|
||||
|
||||
func readArtifactString(t *testing.T, svc *CoreService, session string, artifactID string) string {
|
||||
t.Helper()
|
||||
content, err := svc.ReadArtifactContentForSession(session, domain.ArtifactContentRequest{ArtifactID: artifactID})
|
||||
if err != nil {
|
||||
t.Fatalf("read artifact content: %v", err)
|
||||
}
|
||||
return string(content.Payload)
|
||||
}
|
||||
|
||||
func TestCoreServiceDeniesRunDistributionWithoutPluginDeclaration(t *testing.T) {
|
||||
svc := newTestCoreService()
|
||||
plugin, endpoint := createPluginAndRunEndpoint(t, svc)
|
||||
session := createServiceUserAndLogin(t, svc, domain.User{
|
||||
ID: "user-distribution-denied",
|
||||
DisplayName: "Distribution Denied",
|
||||
Email: "distribution-denied@example.test",
|
||||
Roles: []string{"server-owner"},
|
||||
PasswordHash: "secret-password",
|
||||
})
|
||||
instance, err := svc.CreateServerInstanceForSession(session, domain.ServerInstance{
|
||||
ID: "server-distribution-denied",
|
||||
PluginID: plugin.ID,
|
||||
RunEndpointID: endpoint.ID,
|
||||
Name: "Distribution Denied Server",
|
||||
State: domain.ServerInstanceStateReady,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create denied server: %v", err)
|
||||
}
|
||||
_, err = svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{
|
||||
ServerInstanceID: instance.ID,
|
||||
TargetOS: "linux",
|
||||
TargetArch: "amd64",
|
||||
IdempotencyKey: "idem-run-denied",
|
||||
})
|
||||
if !errors.Is(err, ErrForbidden) {
|
||||
t.Fatalf("expected plugin declaration denial, got %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user