diff --git a/internal/handlers/egress_allowlist_defaults.yaml b/internal/handlers/egress_allowlist_defaults.yaml index bcac4b2..0d37dff 100644 --- a/internal/handlers/egress_allowlist_defaults.yaml +++ b/internal/handlers/egress_allowlist_defaults.yaml @@ -173,6 +173,11 @@ shared_registry_domains: # the credential (e.g. the private ECR layer bucket, which interpolates the # region) stay in registryRedirectHosts in Go. registry_redirect_derivations: + # Wolters Kluwer's configured Artifactory npm registry redirects artifact + # downloads to its S3 storage bucket. + - credential_host: artifactory.wolterskluwer.io + derived: + - jfrog-platform.s3.eu-west-1.amazonaws.com # packagecloud.io redirects downloads to one CloudFront distribution it owns. - credential_host: packagecloud.io derived: diff --git a/internal/handlers/egress_allowlist_test.go b/internal/handlers/egress_allowlist_test.go index 6c57659..d16c529 100644 --- a/internal/handlers/egress_allowlist_test.go +++ b/internal/handlers/egress_allowlist_test.go @@ -565,6 +565,8 @@ func TestEgressAllowlist_JFrogS3BucketsAllowedButSharedS3Blocked(t *testing.T) { "https://attacker-bucket.s3.amazonaws.com/loot", "https://s3.amazonaws.com/attacker-bucket/loot", "https://s3-us-west-2.amazonaws.com/attacker-bucket/loot", + // The Artifactory redirect bucket is only allowed for matching registry credentials. + "https://jfrog-platform.s3.eu-west-1.amazonaws.com/artifactory/artifact", } { resp := egressResult(t, h, blocked) if assert.NotNil(t, resp, "shared S3 host must be blocked: "+blocked) { diff --git a/internal/handlers/egress_dynamic_hosts_test.go b/internal/handlers/egress_dynamic_hosts_test.go index 1af4171..0dc8c2b 100644 --- a/internal/handlers/egress_dynamic_hosts_test.go +++ b/internal/handlers/egress_dynamic_hosts_test.go @@ -208,6 +208,32 @@ func TestRegistryRedirectHosts_PackagecloudCDNNotAllowedWithoutCredential(t *tes "download CDN must not be allowed for a job with no packagecloud credential") } +func TestRegistryRedirectHosts_WoltersKluwerArtifactoryS3Derived(t *testing.T) { + registry := "https://artifactory.wolterskluwer.io/artifactory/api/npm/group-npm/" + artifact := "https://jfrog-platform.s3.eu-west-1.amazonaws.com/artifactory/c6/c6af054e3732c361e9ad8c44070f909666b5616f" + h := newEgressHandlerWithCreds(config.Credentials{ + {"type": "npm_registry", "registry": registry}, + }) + + assert.Nil(t, egressResult(t, h, registry), "the configured Artifactory registry must be allowed") + assert.Nil(t, egressResult(t, h, artifact), + "the S3 artifact host must be allowed for the matching Artifactory credential") + + for _, blocked := range []string{ + "https://evil.jfrog-platform.s3.eu-west-1.amazonaws.com/artifact", + "https://jfrog-platform.s3.us-east-1.amazonaws.com/artifact", + } { + assert.NotNil(t, egressResult(t, h, blocked), + "only the exact derived S3 host must be allowed: "+blocked) + } + + otherRegistry := newEgressHandlerWithCreds(config.Credentials{ + {"type": "npm_registry", "registry": "https://registry.npmjs.org"}, + }) + assert.NotNil(t, egressResult(t, otherRegistry, artifact), + "the S3 artifact host must remain blocked without the matching registry credential") +} + func TestRegistryRedirectHosts_GemfuryStorageDerived(t *testing.T) { // Gemfury endpoints redirect downloads to one Gemfury-owned bucket, // reachable on both its dualstack and plain accelerate endpoints.