diff --git a/spinifex/admin/admin_test.go b/spinifex/admin/admin_test.go
index e43668417..14c99fc51 100644
--- a/spinifex/admin/admin_test.go
+++ b/spinifex/admin/admin_test.go
@@ -621,7 +621,7 @@ func TestGenerateCertificatesIfNeeded(t *testing.T) {
dir := t.TempDir()
// First call generates all certs
- caCertPath := GenerateCertificatesIfNeeded(dir, false, "", "us-east-1", "mulga.internal")
+ caCertPath := GenerateCertificatesIfNeeded(dir, false, "", "us-east-1", "spinifex.internal")
assert.Equal(t, filepath.Join(dir, "ca.pem"), caCertPath)
assert.True(t, FileExists(filepath.Join(dir, "ca.pem")))
assert.True(t, FileExists(filepath.Join(dir, "ca.key")))
@@ -632,7 +632,7 @@ func TestGenerateCertificatesIfNeeded(t *testing.T) {
caInfo, _ := os.Stat(filepath.Join(dir, "ca.pem"))
origModTime := caInfo.ModTime()
- GenerateCertificatesIfNeeded(dir, false, "", "us-east-1", "mulga.internal")
+ GenerateCertificatesIfNeeded(dir, false, "", "us-east-1", "spinifex.internal")
caInfo2, _ := os.Stat(filepath.Join(dir, "ca.pem"))
assert.Equal(t, origModTime, caInfo2.ModTime())
})
@@ -640,7 +640,7 @@ func TestGenerateCertificatesIfNeeded(t *testing.T) {
t.Run("ForceRegenerates", func(t *testing.T) {
origCA, _ := os.ReadFile(filepath.Join(dir, "ca.pem"))
- GenerateCertificatesIfNeeded(dir, true, "", "us-east-1", "mulga.internal")
+ GenerateCertificatesIfNeeded(dir, true, "", "us-east-1", "spinifex.internal")
newCA, _ := os.ReadFile(filepath.Join(dir, "ca.pem"))
assert.NotEqual(t, origCA, newCA)
})
@@ -660,7 +660,7 @@ func TestGenerateServerCertOnly(t *testing.T) {
require.NoError(t, os.WriteFile(filepath.Join(dir, "ca.pem"), caCert, 0600))
require.NoError(t, os.WriteFile(filepath.Join(dir, "ca.key"), caKey, 0600))
- err := GenerateServerCertOnly(dir, "10.0.0.5", "us-east-1", "mulga.internal")
+ err := GenerateServerCertOnly(dir, "10.0.0.5", "us-east-1", "spinifex.internal")
require.NoError(t, err)
assert.True(t, FileExists(filepath.Join(dir, "server.pem")))
assert.True(t, FileExists(filepath.Join(dir, "server.key")))
@@ -678,13 +678,13 @@ func TestGenerateServerCertOnly(t *testing.T) {
assert.True(t, hasBindIP)
// AWS-parity ECR SANs must be present.
- assert.Contains(t, cert.DNSNames, "ecr.us-east-1.mulga.internal")
- assert.Contains(t, cert.DNSNames, "*.dkr.ecr.us-east-1.mulga.internal")
+ assert.Contains(t, cert.DNSNames, "ecr.us-east-1.spinifex.internal")
+ assert.Contains(t, cert.DNSNames, "*.dkr.ecr.us-east-1.spinifex.internal")
})
t.Run("MissingCA", func(t *testing.T) {
dir := t.TempDir()
- err := GenerateServerCertOnly(dir, "10.0.0.5", "us-east-1", "mulga.internal")
+ err := GenerateServerCertOnly(dir, "10.0.0.5", "us-east-1", "spinifex.internal")
assert.Error(t, err)
assert.Contains(t, err.Error(), "CA files not found")
})
@@ -693,13 +693,13 @@ func TestGenerateServerCertOnly(t *testing.T) {
func TestAWSGWServiceDNSNames(t *testing.T) {
t.Parallel()
- got := AWSGWServiceDNSNames("us-east-1", "mulga.internal")
+ got := AWSGWServiceDNSNames("us-east-1", "spinifex.internal")
assert.Equal(t, []string{
- "ecr.us-east-1.mulga.internal",
- "*.dkr.ecr.us-east-1.mulga.internal",
+ "ecr.us-east-1.spinifex.internal",
+ "*.dkr.ecr.us-east-1.spinifex.internal",
}, got)
- assert.Nil(t, AWSGWServiceDNSNames("", "mulga.internal"))
+ assert.Nil(t, AWSGWServiceDNSNames("", "spinifex.internal"))
assert.Nil(t, AWSGWServiceDNSNames("us-east-1", ""))
assert.Nil(t, AWSGWServiceDNSNames("", ""))
}
diff --git a/spinifex/awserrors/awserrors.go b/spinifex/awserrors/awserrors.go
index fd635ad49..b3144b040 100644
--- a/spinifex/awserrors/awserrors.go
+++ b/spinifex/awserrors/awserrors.go
@@ -466,6 +466,7 @@ var (
ErrorLayersNotFound = "LayersNotFoundException"
ErrorReferencedImagesNotFound = "ReferencedImagesNotFoundException"
ErrorTooManyTags = "TooManyTagsException"
+ ErrorOperationNotSupported = "OperationNotSupportedException"
// ELBv2-specific error codes
ErrorELBv2LoadBalancerNotFound = "LoadBalancerNotFound"
@@ -984,6 +985,7 @@ var ErrorLookup = map[string]ErrorMessage{
ErrorLayersNotFound: {HTTPCode: 400, Message: "The specified layers could not be found, or the specified layer is not valid for this repository."},
ErrorReferencedImagesNotFound: {HTTPCode: 400, Message: "The manifest list is referencing an image that does not exist."},
ErrorTooManyTags: {HTTPCode: 400, Message: "The list of tags on the repository is over the limit. The maximum number of tags that can be applied to a repository is 50."},
+ ErrorOperationNotSupported: {HTTPCode: 400, Message: "The specified operation is not supported in this registry."},
// ELBv2 error codes
ErrorELBv2LoadBalancerNotFound: {HTTPCode: 400, Message: "One or more load balancers not found."},
diff --git a/spinifex/config/config.go b/spinifex/config/config.go
index ed20b4e9a..0b9b03517 100644
--- a/spinifex/config/config.go
+++ b/spinifex/config/config.go
@@ -24,7 +24,7 @@ type ClusterConfig struct {
// Defaults for cluster-wide AWS-parity settings.
const (
DefaultAWSRegion = "us-east-1"
- DefaultAWSInternalSuffix = "mulga.internal"
+ DefaultAWSInternalSuffix = "spinifex.internal"
)
// AWSConfig holds cluster-wide AWS-parity settings shared across services.
diff --git a/spinifex/gateway/ecrapi/handler.go b/spinifex/gateway/ecrapi/handler.go
index b7deafd0a..4155352dd 100644
--- a/spinifex/gateway/ecrapi/handler.go
+++ b/spinifex/gateway/ecrapi/handler.go
@@ -36,6 +36,14 @@ func NotImplemented(_ *nats.Conn, _ string, _ []byte) (any, error) {
return nil, errors.New(awserrors.ErrorNotImplemented)
}
+// ScanningNotSupported is the handler for the ECR image-scanning surface. mulga
+// runs no vulnerability scanner, so these actions return the AWS
+// OperationNotSupportedException (400) rather than NotImplemented (501): clients
+// and the console read it as a deliberately-off feature, not a server fault.
+func ScanningNotSupported(_ *nats.Conn, _ string, _ []byte) (any, error) {
+ return nil, errors.New(awserrors.ErrorOperationNotSupported)
+}
+
// Actions is the authoritative ECR control-plane action namespace. Real
// implementations replace the NotImplemented value as each action lands; the
// key set is the v1 API contract.
@@ -70,21 +78,27 @@ var Actions = map[string]Handler{
"PutRegistryPolicy": NotImplemented,
"DescribeRegistry": NotImplemented,
- // Deferred-feature surface (image scan, lifecycle, replication, tagging).
- "PutImageScanningConfiguration": NotImplemented,
- "GetImageScanningConfiguration": NotImplemented,
- "StartImageScan": NotImplemented,
- "DescribeImageScanFindings": NotImplemented,
- "PutLifecyclePolicy": PutLifecyclePolicy,
- "GetLifecyclePolicy": GetLifecyclePolicy,
- "DeleteLifecyclePolicy": DeleteLifecyclePolicy,
- "StartLifecyclePolicyPreview": NotImplemented,
- "GetLifecyclePolicyPreview": NotImplemented,
- "PutReplicationConfiguration": NotImplemented,
- "ReplicateImage": NotImplemented,
- "TagResource": NotImplemented,
- "UntagResource": NotImplemented,
- "ListTagsForResource": NotImplemented,
+ // Image scanning is unsupported (no scanner backend): the whole scan surface
+ // returns OperationNotSupportedException rather than NotImplemented.
+ "PutImageScanningConfiguration": ScanningNotSupported,
+ "GetImageScanningConfiguration": ScanningNotSupported,
+ "StartImageScan": ScanningNotSupported,
+ "DescribeImageScanFindings": ScanningNotSupported,
+ "GetRegistryScanningConfiguration": ScanningNotSupported,
+ "PutRegistryScanningConfiguration": ScanningNotSupported,
+ "BatchGetRepositoryScanningConfiguration": ScanningNotSupported,
+
+ // Deferred-feature surface (lifecycle, replication, tagging).
+ "PutLifecyclePolicy": PutLifecyclePolicy,
+ "GetLifecyclePolicy": GetLifecyclePolicy,
+ "DeleteLifecyclePolicy": DeleteLifecyclePolicy,
+ "StartLifecyclePolicyPreview": NotImplemented,
+ "GetLifecyclePolicyPreview": NotImplemented,
+ "PutReplicationConfiguration": NotImplemented,
+ "ReplicateImage": NotImplemented,
+ "TagResource": NotImplemented,
+ "UntagResource": NotImplemented,
+ "ListTagsForResource": NotImplemented,
}
// WriteJSONResponse serialises obj as a 200 AWS JSON 1.1 response using the
diff --git a/spinifex/gateway/ecrapi/handler_test.go b/spinifex/gateway/ecrapi/handler_test.go
index cf55bb348..e52d9c25c 100644
--- a/spinifex/gateway/ecrapi/handler_test.go
+++ b/spinifex/gateway/ecrapi/handler_test.go
@@ -34,6 +34,29 @@ func TestActions_CoreRegisteredAsStub(t *testing.T) {
}
}
+func TestScanningNotSupported(t *testing.T) {
+ out, err := ScanningNotSupported(nil, "123456789012", []byte("{}"))
+ assert.Nil(t, out)
+ require.Error(t, err)
+ assert.Equal(t, awserrors.ErrorOperationNotSupported, err.Error())
+}
+
+func TestActions_ScanSurfaceUnsupported(t *testing.T) {
+ scan := []string{
+ "PutImageScanningConfiguration", "GetImageScanningConfiguration",
+ "StartImageScan", "DescribeImageScanFindings",
+ "GetRegistryScanningConfiguration", "PutRegistryScanningConfiguration",
+ "BatchGetRepositoryScanningConfiguration",
+ }
+ for _, action := range scan {
+ h, ok := Actions[action]
+ require.True(t, ok, "action %q should be registered", action)
+ _, err := h(nil, "123456789012", nil)
+ assert.Equal(t, awserrors.ErrorOperationNotSupported, err.Error(),
+ "action %q should resolve to the OperationNotSupported stub", action)
+ }
+}
+
func TestWriteJSONResponse(t *testing.T) {
type repo struct {
RepositoryName *string `locationName:"repositoryName" type:"string"`
diff --git a/spinifex/gateway/ecrauth/ecrauth_test.go b/spinifex/gateway/ecrauth/ecrauth_test.go
index c21e78217..d34a0aa4c 100644
--- a/spinifex/gateway/ecrauth/ecrauth_test.go
+++ b/spinifex/gateway/ecrauth/ecrauth_test.go
@@ -13,7 +13,7 @@ import (
"github.com/stretchr/testify/require"
)
-const testAudience = "ecr.ap-southeast-2.mulga.internal"
+const testAudience = "ecr.ap-southeast-2.spinifex.internal"
var testMasterKey = func() []byte {
k := make([]byte, 32)
@@ -102,7 +102,7 @@ func TestVerifier_RejectsWrongAudience(t *testing.T) {
tok, _, err := NewIssuer(key, testAudience).Mint(samplePrincipal())
require.NoError(t, err)
- _, err = NewVerifier(verify, "ecr.us-east-1.mulga.internal").Verify(tok)
+ _, err = NewVerifier(verify, "ecr.us-east-1.spinifex.internal").Verify(tok)
require.Error(t, err)
}
diff --git a/spinifex/gateway/ecrauth_middleware_test.go b/spinifex/gateway/ecrauth_middleware_test.go
index ca15ad3d1..baa445dc3 100644
--- a/spinifex/gateway/ecrauth_middleware_test.go
+++ b/spinifex/gateway/ecrauth_middleware_test.go
@@ -16,7 +16,7 @@ import (
const (
ecrTestRegion = "ap-southeast-2"
- ecrTestSuffix = "mulga.internal"
+ ecrTestSuffix = "spinifex.internal"
ecrTestAudience = "ecr." + ecrTestRegion + "." + ecrTestSuffix
ecrTestAccount = "000000000001"
)
diff --git a/spinifex/gateway/gateway.go b/spinifex/gateway/gateway.go
index d6abadb9b..50f484da0 100644
--- a/spinifex/gateway/gateway.go
+++ b/spinifex/gateway/gateway.go
@@ -76,7 +76,7 @@ type GatewayConfig struct {
Config string // Shared AWS Gateway config for S3 auth
ExpectedNodes int // Number of expected spinifex nodes for multi-node operations
Region string // Region this gateway is running in
- InternalSuffix string // Internal DNS suffix for AWS-parity endpoints (e.g. mulga.internal)
+ InternalSuffix string // Internal DNS suffix for AWS-parity endpoints (e.g. spinifex.internal)
AZ string // Availability zone this gateway is running in
IAMService handlers_iam.IAMService
STSService handlers_sts.STSService
diff --git a/spinifex/gateway/hostmatch_test.go b/spinifex/gateway/hostmatch_test.go
index c9fee8cbf..afa46f357 100644
--- a/spinifex/gateway/hostmatch_test.go
+++ b/spinifex/gateway/hostmatch_test.go
@@ -19,16 +19,16 @@ func TestParseRegistryHost(t *testing.T) {
}{
{
name: "match with suffix",
- host: "123456789012.dkr.ecr.us-east-1.mulga.internal",
- suffix: "mulga.internal",
+ host: "123456789012.dkr.ecr.us-east-1.spinifex.internal",
+ suffix: "spinifex.internal",
wantAcct: "123456789012",
wantRegn: "us-east-1",
wantOK: true,
},
{
name: "match strips port",
- host: "123456789012.dkr.ecr.us-east-1.mulga.internal:9999",
- suffix: "mulga.internal",
+ host: "123456789012.dkr.ecr.us-east-1.spinifex.internal:9999",
+ suffix: "spinifex.internal",
wantAcct: "123456789012",
wantRegn: "us-east-1",
wantOK: true,
@@ -43,26 +43,26 @@ func TestParseRegistryHost(t *testing.T) {
},
{
name: "control plane host is not a registry host",
- host: "ecr.us-east-1.mulga.internal",
- suffix: "mulga.internal",
+ host: "ecr.us-east-1.spinifex.internal",
+ suffix: "spinifex.internal",
wantOK: false,
},
{
name: "wrong suffix does not match",
host: "123456789012.dkr.ecr.us-east-1.other.internal",
- suffix: "mulga.internal",
+ suffix: "spinifex.internal",
wantOK: false,
},
{
name: "missing dkr.ecr labels",
- host: "123456789012.foo.bar.us-east-1.mulga.internal",
- suffix: "mulga.internal",
+ host: "123456789012.foo.bar.us-east-1.spinifex.internal",
+ suffix: "spinifex.internal",
wantOK: false,
},
{
name: "empty host",
host: "",
- suffix: "mulga.internal",
+ suffix: "spinifex.internal",
wantOK: false,
},
{
@@ -95,7 +95,7 @@ func TestParseRegistryHost(t *testing.T) {
func TestHostMatch_PopulatesContextOnRegistryHost(t *testing.T) {
t.Parallel()
- gw := &GatewayConfig{InternalSuffix: "mulga.internal"}
+ gw := &GatewayConfig{InternalSuffix: "spinifex.internal"}
var gotAcct, gotRegn string
var acctOK, regnOK bool
@@ -105,7 +105,7 @@ func TestHostMatch_PopulatesContextOnRegistryHost(t *testing.T) {
})
req := httptest.NewRequest(http.MethodGet, "/v2/", nil)
- req.Host = "123456789012.dkr.ecr.us-east-1.mulga.internal"
+ req.Host = "123456789012.dkr.ecr.us-east-1.spinifex.internal"
gw.hostMatch(next).ServeHTTP(httptest.NewRecorder(), req)
if !acctOK || gotAcct != "123456789012" {
@@ -119,7 +119,7 @@ func TestHostMatch_PopulatesContextOnRegistryHost(t *testing.T) {
func TestHostMatch_PassesThroughNonRegistryHost(t *testing.T) {
t.Parallel()
- gw := &GatewayConfig{InternalSuffix: "mulga.internal"}
+ gw := &GatewayConfig{InternalSuffix: "spinifex.internal"}
var hadAccount bool
next := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
@@ -127,7 +127,7 @@ func TestHostMatch_PassesThroughNonRegistryHost(t *testing.T) {
})
req := httptest.NewRequest(http.MethodGet, "/v2/", nil)
- req.Host = "ecr.us-east-1.mulga.internal"
+ req.Host = "ecr.us-east-1.spinifex.internal"
gw.hostMatch(next).ServeHTTP(httptest.NewRecorder(), req)
if hadAccount {
diff --git a/spinifex/services/spinifexui/frontend/src/routes/_auth/ecr/(repositories)/-components/repository-detail-page.test.tsx b/spinifex/services/spinifexui/frontend/src/routes/_auth/ecr/(repositories)/-components/repository-detail-page.test.tsx
index d55e8cf45..4f47343c4 100644
--- a/spinifex/services/spinifexui/frontend/src/routes/_auth/ecr/(repositories)/-components/repository-detail-page.test.tsx
+++ b/spinifex/services/spinifexui/frontend/src/routes/_auth/ecr/(repositories)/-components/repository-detail-page.test.tsx
@@ -78,6 +78,14 @@ describe("RepositoryDetailPage", () => {
expect(screen.getByText("No images pushed yet.")).toBeInTheDocument()
})
+ it("shows scanning is unsupported on the Scan tab", () => {
+ renderWithClient(
+ Vulnerability scanning is not supported by this registry. Pushed images + are stored and served without scan findings. +
+