Complete platform management workflows
This commit is contained in:
@@ -0,0 +1,433 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"browser.local/platform/domain"
|
||||
)
|
||||
|
||||
const maxDistributionMessageLength = 256
|
||||
|
||||
func ValidateRuntimeBinding(binding domain.RuntimeBinding) error {
|
||||
var violations []string
|
||||
violations = appendRequired(violations, "id", binding.ID)
|
||||
violations = appendRequired(violations, "serverInstanceId", binding.ServerInstanceID)
|
||||
violations = appendRequired(violations, "pluginId", binding.PluginID)
|
||||
violations = appendRequired(violations, "profileKey", binding.ProfileKey)
|
||||
violations = appendRequired(violations, "mode", binding.Mode)
|
||||
if !validRuntimeBindingStatus(binding.Status) {
|
||||
violations = append(violations, "status is invalid")
|
||||
}
|
||||
for key, value := range binding.Bindings {
|
||||
if !validDistributionLogicalKey(key) {
|
||||
violations = append(violations, "bindings key is invalid")
|
||||
}
|
||||
if containsUnsafeRuntimeSecret(value) || looksLikeRawHostPath(value) || strings.Contains(strings.ToLower(value), "://") && !strings.HasPrefix(value, "secret://") {
|
||||
violations = append(violations, "bindings."+key+" must use safe logical or secret refs")
|
||||
}
|
||||
}
|
||||
for i, key := range binding.MissingKeys {
|
||||
if !validDistributionLogicalKey(key) {
|
||||
violations = append(violations, fmt.Sprintf("missingKeys[%d] is invalid", i))
|
||||
}
|
||||
}
|
||||
if binding.CreatedAt.IsZero() {
|
||||
violations = append(violations, "createdAt is required")
|
||||
}
|
||||
if binding.UpdatedAt.IsZero() {
|
||||
violations = append(violations, "updatedAt is required")
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func ValidateEncryptedComponentKey(key domain.EncryptedComponentKey) error {
|
||||
var violations []string
|
||||
violations = appendRequired(violations, "id", key.ID)
|
||||
violations = appendRequired(violations, "serverInstanceId", key.ServerInstanceID)
|
||||
violations = appendRequired(violations, "encryptedKey", key.EncryptedKey)
|
||||
violations = appendRequired(violations, "keyHash", key.KeyHash)
|
||||
violations = appendRequired(violations, "fingerprint", key.Fingerprint)
|
||||
violations = appendRequired(violations, "secretRef", key.SecretRef)
|
||||
if !validDistributionComponentKind(key.ComponentKind) {
|
||||
violations = append(violations, "componentKind is invalid")
|
||||
}
|
||||
if key.ComponentKind == domain.DistributionComponentClientManager && strings.TrimSpace(key.ComponentKey) == "" {
|
||||
violations = append(violations, "componentKey is required for client-manager keys")
|
||||
}
|
||||
if key.ComponentKey != "" && !validDistributionLogicalKey(key.ComponentKey) {
|
||||
violations = append(violations, "componentKey is invalid")
|
||||
}
|
||||
if !strings.HasPrefix(key.EncryptedKey, "enc:v1:") {
|
||||
violations = append(violations, "encryptedKey must be encrypted")
|
||||
}
|
||||
if key.KeyHash != "" && !validSHA256Checksum(key.KeyHash) {
|
||||
violations = append(violations, "keyHash must be sha256:<hex>")
|
||||
}
|
||||
if !strings.HasPrefix(key.SecretRef, "secret://runtime-keys/") {
|
||||
violations = append(violations, "secretRef must be a runtime key secret ref")
|
||||
}
|
||||
if key.Generation <= 0 {
|
||||
violations = append(violations, "generation must be positive")
|
||||
}
|
||||
if !validComponentKeyStatus(key.Status) {
|
||||
violations = append(violations, "status is invalid")
|
||||
}
|
||||
if key.CreatedAt.IsZero() {
|
||||
violations = append(violations, "createdAt is required")
|
||||
}
|
||||
if key.UpdatedAt.IsZero() {
|
||||
violations = append(violations, "updatedAt is required")
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func ValidateRunDistribution(distribution domain.RunDistribution) error {
|
||||
var violations []string
|
||||
violations = appendRequired(violations, "id", distribution.ID)
|
||||
violations = appendRequired(violations, "serverInstanceId", distribution.ServerInstanceID)
|
||||
violations = appendRequired(violations, "pluginId", distribution.PluginID)
|
||||
violations = appendRequired(violations, "runEndpointId", distribution.RunEndpointID)
|
||||
violations = appendRequired(violations, "targetOs", distribution.TargetOS)
|
||||
violations = appendRequired(violations, "targetArch", distribution.TargetArch)
|
||||
violations = appendRequired(violations, "packageFormat", distribution.PackageFormat)
|
||||
violations = appendRequired(violations, "artifactId", distribution.ArtifactID)
|
||||
violations = appendRequired(violations, "checksum", distribution.Checksum)
|
||||
violations = appendRequired(violations, "secretRef", distribution.SecretRef)
|
||||
violations = appendDistributionTargetViolations(violations, distribution.TargetOS, distribution.TargetArch)
|
||||
violations = appendDistributionStatusViolations(violations, distribution.Status)
|
||||
if distribution.Checksum != "" && !validSHA256Checksum(distribution.Checksum) {
|
||||
violations = append(violations, "checksum must be sha256:<hex>")
|
||||
}
|
||||
if distribution.KeyGeneration <= 0 {
|
||||
violations = append(violations, "keyGeneration must be positive")
|
||||
}
|
||||
if distribution.PackageFormat != "zip" && distribution.PackageFormat != "tar.gz" {
|
||||
violations = append(violations, "packageFormat is invalid")
|
||||
}
|
||||
if !strings.HasPrefix(distribution.SecretRef, "secret://runtime-keys/") {
|
||||
violations = append(violations, "secretRef must be redacted runtime key ref")
|
||||
}
|
||||
if distribution.CreatedAt.IsZero() {
|
||||
violations = append(violations, "createdAt is required")
|
||||
}
|
||||
if distribution.UpdatedAt.IsZero() {
|
||||
violations = append(violations, "updatedAt is required")
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func ValidateClientManagerDistribution(distribution domain.ClientManagerDistribution) error {
|
||||
var violations []string
|
||||
violations = appendRequired(violations, "id", distribution.ID)
|
||||
violations = appendRequired(violations, "serverInstanceId", distribution.ServerInstanceID)
|
||||
violations = appendRequired(violations, "pluginId", distribution.PluginID)
|
||||
violations = appendRequired(violations, "profileKey", distribution.ProfileKey)
|
||||
violations = appendRequired(violations, "repositoryUrl", distribution.RepositoryURL)
|
||||
violations = appendRequired(violations, "sourceRevision", distribution.SourceRevision)
|
||||
violations = appendRequired(violations, "buildJobId", distribution.BuildJobID)
|
||||
violations = appendRequired(violations, "artifactId", distribution.ArtifactID)
|
||||
violations = appendRequired(violations, "checksum", distribution.Checksum)
|
||||
violations = appendRequired(violations, "secretRef", distribution.SecretRef)
|
||||
violations = appendDistributionTargetViolations(violations, distribution.TargetOS, distribution.TargetArch)
|
||||
violations = appendDistributionStatusViolations(violations, distribution.Status)
|
||||
if !validDistributionLogicalKey(distribution.ProfileKey) {
|
||||
violations = append(violations, "profileKey is invalid")
|
||||
}
|
||||
if distribution.Checksum != "" && !validSHA256Checksum(distribution.Checksum) {
|
||||
violations = append(violations, "checksum must be sha256:<hex>")
|
||||
}
|
||||
if distribution.KeyGeneration <= 0 {
|
||||
violations = append(violations, "keyGeneration must be positive")
|
||||
}
|
||||
violations = append(violations, validateRepositoryURL("repositoryUrl", distribution.RepositoryURL)...)
|
||||
if !strings.HasPrefix(distribution.SecretRef, "secret://runtime-keys/") {
|
||||
violations = append(violations, "secretRef must be redacted runtime key ref")
|
||||
}
|
||||
if distribution.CreatedAt.IsZero() {
|
||||
violations = append(violations, "createdAt is required")
|
||||
}
|
||||
if distribution.UpdatedAt.IsZero() {
|
||||
violations = append(violations, "updatedAt is required")
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func ValidateDependencyStatus(status domain.DependencyStatus) error {
|
||||
var violations []string
|
||||
violations = appendRequired(violations, "id", status.ID)
|
||||
violations = appendRequired(violations, "serverInstanceId", status.ServerInstanceID)
|
||||
violations = appendRequired(violations, "pluginId", status.PluginID)
|
||||
violations = appendRequired(violations, "probeKey", status.ProbeKey)
|
||||
if !validDistributionLogicalKey(status.ProbeKey) {
|
||||
violations = append(violations, "probeKey is invalid")
|
||||
}
|
||||
if status.TargetOS != "" || status.TargetArch != "" {
|
||||
violations = appendDistributionTargetViolations(violations, status.TargetOS, status.TargetArch)
|
||||
}
|
||||
if !validDependencyState(status.State) {
|
||||
violations = append(violations, "state is invalid")
|
||||
}
|
||||
if status.InstallPlanKey != "" && !validDistributionLogicalKey(status.InstallPlanKey) {
|
||||
violations = append(violations, "installPlanKey is invalid")
|
||||
}
|
||||
if len(status.Message) > maxDistributionMessageLength || containsUnsafeRuntimeSecret(status.Message) || looksLikeRawHostPath(status.Message) {
|
||||
violations = append(violations, "message is unsafe or too long")
|
||||
}
|
||||
if status.CheckedAt.IsZero() {
|
||||
violations = append(violations, "checkedAt is required")
|
||||
}
|
||||
if status.UpdatedAt.IsZero() {
|
||||
violations = append(violations, "updatedAt is required")
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func ValidateClientManagerBuildJob(job domain.ClientManagerBuildJob) error {
|
||||
var violations []string
|
||||
violations = appendRequired(violations, "id", job.ID)
|
||||
violations = appendRequired(violations, "serverInstanceId", job.ServerInstanceID)
|
||||
violations = appendRequired(violations, "pluginId", job.PluginID)
|
||||
violations = appendRequired(violations, "profileKey", job.ProfileKey)
|
||||
violations = appendRequired(violations, "repositoryUrl", job.RepositoryURL)
|
||||
violations = appendRequired(violations, "sourceRevision", job.SourceRevision)
|
||||
violations = appendDistributionTargetViolations(violations, job.TargetOS, job.TargetArch)
|
||||
if !validDistributionLogicalKey(job.ProfileKey) {
|
||||
violations = append(violations, "profileKey is invalid")
|
||||
}
|
||||
if job.Checksum != "" && !validSHA256Checksum(job.Checksum) {
|
||||
violations = append(violations, "checksum must be sha256:<hex>")
|
||||
}
|
||||
if job.LogsRef != "" && !validScopedInputRef(job.LogsRef) {
|
||||
violations = append(violations, "logsRef is not allowed")
|
||||
}
|
||||
if job.KeyGeneration < 0 {
|
||||
violations = append(violations, "keyGeneration must not be negative")
|
||||
}
|
||||
if !validDistributionJobStatus(job.Status) {
|
||||
violations = append(violations, "status is invalid")
|
||||
}
|
||||
violations = append(violations, validateRepositoryURL("repositoryUrl", job.RepositoryURL)...)
|
||||
if job.CreatedAt.IsZero() {
|
||||
violations = append(violations, "createdAt is required")
|
||||
}
|
||||
if job.UpdatedAt.IsZero() {
|
||||
violations = append(violations, "updatedAt is required")
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func ValidateRunUpdateJob(job domain.RunUpdateJob) error {
|
||||
var violations []string
|
||||
violations = appendRequired(violations, "id", job.ID)
|
||||
violations = appendRequired(violations, "serverInstanceId", job.ServerInstanceID)
|
||||
violations = appendRequired(violations, "runEndpointId", job.RunEndpointID)
|
||||
violations = appendRequired(violations, "artifactId", job.ArtifactID)
|
||||
violations = appendRequired(violations, "checksum", job.Checksum)
|
||||
violations = appendRequired(violations, "idempotencyKey", job.IdempotencyKey)
|
||||
if job.Checksum != "" && !validSHA256Checksum(job.Checksum) {
|
||||
violations = append(violations, "checksum must be sha256:<hex>")
|
||||
}
|
||||
if !validDistributionJobStatus(job.Status) {
|
||||
violations = append(violations, "status is invalid")
|
||||
}
|
||||
if containsUnsafeRuntimeSecret(job.IdempotencyKey) || looksLikeRawHostPath(job.IdempotencyKey) {
|
||||
violations = append(violations, "idempotencyKey is unsafe")
|
||||
}
|
||||
if job.CreatedAt.IsZero() {
|
||||
violations = append(violations, "createdAt is required")
|
||||
}
|
||||
if job.UpdatedAt.IsZero() {
|
||||
violations = append(violations, "updatedAt is required")
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func ValidateRunDistributionGenerateRequest(request domain.RunDistributionGenerateRequest) error {
|
||||
var violations []string
|
||||
violations = appendRequired(violations, "serverInstanceId", request.ServerInstanceID)
|
||||
violations = appendRequired(violations, "targetOs", request.TargetOS)
|
||||
violations = appendRequired(violations, "targetArch", request.TargetArch)
|
||||
violations = appendRequired(violations, "idempotencyKey", request.IdempotencyKey)
|
||||
violations = appendDistributionTargetViolations(violations, request.TargetOS, request.TargetArch)
|
||||
if containsUnsafeRuntimeSecret(request.IdempotencyKey) || looksLikeRawHostPath(request.IdempotencyKey) {
|
||||
violations = append(violations, "idempotencyKey is unsafe")
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func ValidateClientManagerBuildRequest(request domain.ClientManagerBuildRequest) error {
|
||||
var violations []string
|
||||
violations = appendRequired(violations, "serverInstanceId", request.ServerInstanceID)
|
||||
violations = appendRequired(violations, "profileKey", request.ProfileKey)
|
||||
violations = appendRequired(violations, "targetOs", request.TargetOS)
|
||||
violations = appendRequired(violations, "targetArch", request.TargetArch)
|
||||
violations = appendRequired(violations, "repositoryUrl", request.RepositoryURL)
|
||||
violations = appendRequired(violations, "idempotencyKey", request.IdempotencyKey)
|
||||
violations = appendDistributionTargetViolations(violations, request.TargetOS, request.TargetArch)
|
||||
if !validDistributionLogicalKey(request.ProfileKey) {
|
||||
violations = append(violations, "profileKey is invalid")
|
||||
}
|
||||
if request.SourceRevision != "" && !validDistributionLogicalKey(request.SourceRevision) {
|
||||
violations = append(violations, "sourceRevision is invalid")
|
||||
}
|
||||
if containsUnsafeRuntimeSecret(request.IdempotencyKey) || looksLikeRawHostPath(request.IdempotencyKey) {
|
||||
violations = append(violations, "idempotencyKey is unsafe")
|
||||
}
|
||||
violations = append(violations, validateRepositoryURL("repositoryUrl", request.RepositoryURL)...)
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func ValidateComponentKeyResetRequest(request domain.ComponentKeyResetRequest) error {
|
||||
var violations []string
|
||||
violations = appendRequired(violations, "serverInstanceId", request.ServerInstanceID)
|
||||
if !validDistributionComponentKind(request.ComponentKind) {
|
||||
violations = append(violations, "componentKind is invalid")
|
||||
}
|
||||
if request.ComponentKind == domain.DistributionComponentClientManager && strings.TrimSpace(request.ComponentKey) == "" {
|
||||
violations = append(violations, "componentKey is required for client-manager")
|
||||
}
|
||||
if request.ComponentKey != "" && !validDistributionLogicalKey(request.ComponentKey) {
|
||||
violations = append(violations, "componentKey is invalid")
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func ValidateComponentAuthenticationRequest(request domain.ComponentAuthenticationRequest) error {
|
||||
var violations []string
|
||||
violations = appendRequired(violations, "serverInstanceId", request.ServerInstanceID)
|
||||
violations = appendRequired(violations, "key", request.Key)
|
||||
if !validDistributionComponentKind(request.ComponentKind) {
|
||||
violations = append(violations, "componentKind is invalid")
|
||||
}
|
||||
if request.ComponentKind == domain.DistributionComponentClientManager && strings.TrimSpace(request.ComponentKey) == "" {
|
||||
violations = append(violations, "componentKey is required for client-manager")
|
||||
}
|
||||
if request.ComponentKey != "" && !validDistributionLogicalKey(request.ComponentKey) {
|
||||
violations = append(violations, "componentKey is invalid")
|
||||
}
|
||||
if request.Generation <= 0 {
|
||||
violations = append(violations, "generation must be positive")
|
||||
}
|
||||
if len(request.Key) > 256 || looksLikeRawHostPath(request.Key) || strings.Contains(strings.ToLower(request.Key), "://") {
|
||||
violations = append(violations, "key is unsafe")
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func appendDistributionTargetViolations(violations []string, targetOS string, targetArch string) []string {
|
||||
if !validDistributionTargetOS(targetOS) {
|
||||
violations = append(violations, "targetOs is invalid")
|
||||
}
|
||||
if !validDistributionTargetArch(targetArch) {
|
||||
violations = append(violations, "targetArch is invalid")
|
||||
}
|
||||
return violations
|
||||
}
|
||||
|
||||
func appendDistributionStatusViolations(violations []string, status domain.DistributionStatus) []string {
|
||||
if !validDistributionStatus(status) {
|
||||
return append(violations, "status is invalid")
|
||||
}
|
||||
return violations
|
||||
}
|
||||
|
||||
func validateRepositoryURL(field string, value string) []string {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return nil
|
||||
}
|
||||
lowered := strings.ToLower(strings.TrimSpace(value))
|
||||
if !strings.HasPrefix(lowered, "https://") || !strings.HasSuffix(lowered, ".git") {
|
||||
return []string{field + " must be an HTTPS git repository URL"}
|
||||
}
|
||||
for _, reason := range unsafePluginStringReasons(value) {
|
||||
return []string{field + ": " + reason}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validDistributionComponentKind(kind domain.DistributionComponentKind) bool {
|
||||
switch kind {
|
||||
case domain.DistributionComponentRun, domain.DistributionComponentClientManager:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validComponentKeyStatus(status domain.ComponentKeyStatus) bool {
|
||||
switch status {
|
||||
case domain.ComponentKeyStatusActive, domain.ComponentKeyStatusRevoked:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validDistributionStatus(status domain.DistributionStatus) bool {
|
||||
switch status {
|
||||
case domain.DistributionStatusAvailable, domain.DistributionStatusRevoked, domain.DistributionStatusBuilding, domain.DistributionStatusFailed:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validRuntimeBindingStatus(status domain.RuntimeBindingStatus) bool {
|
||||
switch status {
|
||||
case domain.RuntimeBindingStatusComplete, domain.RuntimeBindingStatusIncomplete:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validDependencyState(state domain.DependencyState) bool {
|
||||
switch state {
|
||||
case domain.DependencyStateUnknown, domain.DependencyStatePresent, domain.DependencyStateMissing, domain.DependencyStateInstalling, domain.DependencyStateFailed:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validDistributionJobStatus(status domain.DistributionJobStatus) bool {
|
||||
switch status {
|
||||
case domain.DistributionJobStatusQueued, domain.DistributionJobStatusRunning, domain.DistributionJobStatusSucceeded, domain.DistributionJobStatusFailed, domain.DistributionJobStatusDenied:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validDistributionTargetOS(targetOS string) bool {
|
||||
switch targetOS {
|
||||
case "linux", "windows", "darwin":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validDistributionTargetArch(targetArch string) bool {
|
||||
switch targetArch {
|
||||
case "amd64", "arm64":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validDistributionLogicalKey(value string) bool {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed == "" || trimmed != value || len([]rune(value)) > 96 {
|
||||
return false
|
||||
}
|
||||
for _, char := range value {
|
||||
if (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') || char == '_' || char == '-' || char == '.' || char == '/' {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return !strings.Contains(value, "..") && !strings.Contains(value, "://") && !looksLikeRawHostPath(value) && !containsUnsafeRuntimeSecret(value)
|
||||
}
|
||||
Reference in New Issue
Block a user