diff --git a/src/main/java/land/oras/ContainerRef.java b/src/main/java/land/oras/ContainerRef.java
index 062c1fcf..65e21f13 100644
--- a/src/main/java/land/oras/ContainerRef.java
+++ b/src/main/java/land/oras/ContainerRef.java
@@ -28,6 +28,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import land.oras.exception.OrasException;
+import land.oras.policy.ContainersPolicy;
import land.oras.policy.Transport;
import land.oras.utils.Const;
import land.oras.utils.SupportedAlgorithm;
@@ -560,15 +561,25 @@ public boolean isBlocked(Registry registry) {
effectiveRef);
return true;
}
+ return !isAllowed(effectiveRef, registry.getContainersPolicy());
+ }
+ /**
+ * Check if an effective (already resolved) reference is blocked by the given policy
+ * @param effectiveRef The effective reference
+ * @param policy The policy
+ * @return True or false
+ */
+ public boolean isAllowed(ContainerRef effectiveRef, ContainersPolicy policy) {
// Check containers policy. Strip a trailing ":tag" and/or "@digest" without touching a
// "host:port" registry (the tag colon always follows the last "/").
String scope = effectiveRef.toString().replaceFirst("(:[^/@]+)?(@[^/]+)?$", "");
- boolean allowed = registry.getContainersPolicy().isAllowed(Transport.DOCKER, scope);
- if (!allowed) {
- throw new OrasException("Image '%s' rejected by containers policy".formatted(this));
+ boolean allowed = policy.isAllowed(Transport.DOCKER, scope);
+ if (allowed) {
+ LOG.debug("Access to container reference {} is allowed by policy", effectiveRef);
+ return true;
}
-
+ LOG.info("Access to container reference {} is not allowed by policy", effectiveRef);
return false;
}
diff --git a/src/main/java/land/oras/Registry.java b/src/main/java/land/oras/Registry.java
index 5bfcc30f..ddf7f1ec 100644
--- a/src/main/java/land/oras/Registry.java
+++ b/src/main/java/land/oras/Registry.java
@@ -2050,17 +2050,6 @@ public Builder withReferrerListMaxPages(int referrerListMaxPages) {
/**
* Set the containers trust policy to enforce during pull operations.
- *
- *
When set, all image manifest pulls will be evaluated against the policy before
- * being returned. The {@code insecureAcceptAnything}, {@code reject}, and
- * {@code sigstoreSigned} (keyed verification of attached Sigstore bundles) requirements are
- * enforced. The {@code signedBy} (GPG simple signing) requirement is not implemented and will
- * log a warning and accept the image without verification.
- *
- *
By default, the policy is loaded from standard locations
- * ({@code $HOME/.config/containers/policy.json} or {@code /etc/containers/policy.json}).
- * If no policy file is found, an accept-all policy is used.
- *
* @param policy the containers policy to enforce.
* @return the builder
* @see ContainersPolicy#newPolicy()
@@ -2070,19 +2059,6 @@ public Builder withPolicy(ContainersPolicy policy) {
return this;
}
- /**
- * Load and set the containers trust policy from the given path.
- *
- * @param policyPath the path to the policy.json file.
- * @return the builder
- * @throws OrasException if the file cannot be read or parsed.
- * @see ContainersPolicy#newPolicy(Path)
- */
- public Builder withPolicy(Path policyPath) {
- registry.setContainersPolicy(ContainersPolicy.newPolicy(policyPath));
- return this;
- }
-
/**
* Return a new builder
* @return The builder
diff --git a/src/main/java/land/oras/policy/ContainersPolicy.java b/src/main/java/land/oras/policy/ContainersPolicy.java
index 8c17dda8..967ff1cf 100644
--- a/src/main/java/land/oras/policy/ContainersPolicy.java
+++ b/src/main/java/land/oras/policy/ContainersPolicy.java
@@ -91,7 +91,7 @@ public static ContainersPolicy newPolicy() {
}
LOG.warn("No containers policy.json found; using insecureAcceptAnything default");
- return acceptAll();
+ return new ContainersPolicy(PolicyFile.fromJson(List.of(new PolicyRequirement.InsecureAcceptAnything()), null));
}
/**
@@ -112,27 +112,6 @@ public static ContainersPolicy newPolicy(Path path) {
}
}
- /**
- * Create a policy that accepts any image unconditionally.
- *
- * @return a permissive {@link ContainersPolicy}.
- */
- public static ContainersPolicy acceptAll() {
- PolicyFile policyFile =
- new PolicyFile(List.of(new PolicyRequirement.InsecureAcceptAnything()), Collections.emptyMap());
- return new ContainersPolicy(policyFile);
- }
-
- /**
- * Create a policy that rejects every image unconditionally.
- *
- * @return a rejecting {@link ContainersPolicy}.
- */
- public static ContainersPolicy rejectAll() {
- PolicyFile policyFile = new PolicyFile(List.of(new PolicyRequirement.Reject()), Collections.emptyMap());
- return new ContainersPolicy(policyFile);
- }
-
/**
* Determine whether an image is allowed under this policy using the lightweight, content-free
* scope gate.
@@ -295,11 +274,6 @@ private boolean wildcardMatches(String scope, String pattern) {
}
private static List defaultPolicyPaths() {
- String home = System.getenv("HOME");
- if (home != null) {
- return List.of(
- Path.of(home, ".config", "containers", "policy.json"), Path.of("/etc/containers/policy.json"));
- }
return List.of(Path.of("/etc/containers/policy.json"));
}
diff --git a/src/test/java/land/oras/JFrogArtifactoryITCase.java b/src/test/java/land/oras/JFrogArtifactoryITCase.java
index 9cf63f9e..df1ab00f 100644
--- a/src/test/java/land/oras/JFrogArtifactoryITCase.java
+++ b/src/test/java/land/oras/JFrogArtifactoryITCase.java
@@ -24,6 +24,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.nio.file.Path;
+import land.oras.policy.ContainersPolicy;
import land.oras.utils.ZotUnsecureContainer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@@ -42,6 +43,18 @@ class JFrogArtifactoryITCase {
@Container
private final ZotUnsecureContainer unsecureRegistry = new ZotUnsecureContainer().withStartupAttempts(3);
+ @Test
+ void shouldPullWithSignature() {
+ Registry registry = Registry.builder()
+ .defaults()
+ .withPolicy(ContainersPolicy.newPolicy())
+ .build();
+ ContainerRef containerRef1 =
+ ContainerRef.parse("prj-elca-ai-oci.artifactory.svc.elca.ch/skills/jenkins-pipeline:0.0.17");
+ Manifest manifest = registry.getManifest(containerRef1);
+ assertNotNull(manifest);
+ }
+
@Test
void shouldPull() {
Registry registry = Registry.builder().build();
diff --git a/src/test/java/land/oras/RegistryTest.java b/src/test/java/land/oras/RegistryTest.java
index af102f12..f63bbdf3 100644
--- a/src/test/java/land/oras/RegistryTest.java
+++ b/src/test/java/land/oras/RegistryTest.java
@@ -2439,19 +2439,24 @@ void shouldPushArtifactWithChunkedOptionsAndAnnotations() throws IOException {
}
@Test
- void shouldBlockPullWithRejectPolicy() throws IOException {
+ void shouldBlockPullWithRejectPolicy(@TempDir Path policyDir) throws IOException {
+
+ // Create 2 policies
+ ContainersPolicy acceptALl = ContainersPolicy.newPolicy(createAcceptAllPolicy(policyDir));
+ ContainersPolicy rejectAll = ContainersPolicy.newPolicy(createRejectPolicy(policyDir));
+
// Create a registry with reject-all policy
Registry registryWithPolicy = Registry.Builder.builder()
.defaults("myuser", "mypass")
.withInsecure(true)
- .withPolicy(ContainersPolicy.rejectAll())
+ .withPolicy(rejectAll)
.build();
// First push an artifact without policy enforcement
Registry registryNormal = Registry.Builder.builder()
.defaults("myuser", "mypass")
.withInsecure(true)
- .withPolicy(ContainersPolicy.acceptAll())
+ .withPolicy(acceptALl)
.build();
ContainerRef containerRef =
@@ -2467,8 +2472,8 @@ void shouldBlockPullWithRejectPolicy() throws IOException {
// Pull fails with reject-all policy - verify exact error message
OrasException ex = assertThrows(OrasException.class, () -> registryWithPolicy.getManifest(containerRef));
assertTrue(
- ex.getMessage().contains("rejected by containers policy"),
- "Expected error message to contain 'rejected by containers policy', got: " + ex.getMessage());
+ ex.getMessage().contains("is blocked by registry configuration"),
+ "Expected error message to contain 'is blocked by registry configuration', got: " + ex.getMessage());
assertTrue(
ex.getMessage().contains(containerRef.toString()),
"Expected error message to contain container ref, got: " + ex.getMessage());
@@ -2486,14 +2491,13 @@ void shouldBlockPullWithPolicyFromPath() throws IOException {
Registry registryWithPolicy = Registry.Builder.builder()
.defaults("myuser", "mypass")
.withInsecure(true)
- .withPolicy(policyFile)
+ .withPolicy(ContainersPolicy.newPolicy(policyFile))
.build();
// Push an artifact first (with accept-all policy)
Registry registryNormal = Registry.Builder.builder()
.defaults("myuser", "mypass")
.withInsecure(true)
- .withPolicy(ContainersPolicy.acceptAll())
.build();
ContainerRef containerRef =
@@ -2508,9 +2512,36 @@ void shouldBlockPullWithPolicyFromPath() throws IOException {
// Pull fails with policy from file - verify exact error message format
OrasException ex = assertThrows(OrasException.class, () -> registryWithPolicy.getManifest(containerRef));
- String expectedMessage = "Image '%s' rejected by containers policy".formatted(containerRef);
+ String expectedMessage =
+ "Access to container reference %s is blocked by registry configuration".formatted(containerRef);
assertEquals(expectedMessage, ex.getMessage());
Files.deleteIfExists(policyFile);
}
+
+ private Path createRejectPolicy(Path tempDir) throws IOException {
+ Path output = Path.of(tempDir.toString(), "reject.json");
+ Files.writeString(
+ output,
+ // language=json
+ """
+ {
+ "default": [{"type": "reject"}]
+ }
+ """);
+ return output;
+ }
+
+ private Path createAcceptAllPolicy(Path tempDir) throws IOException {
+ Path output = Path.of(tempDir.toString(), "accept.json");
+ Files.writeString(
+ output,
+ // language=json
+ """
+ {
+ "default": [{"type": "insecureAcceptAnything"}]
+ }
+ """);
+ return output;
+ }
}
diff --git a/src/test/java/land/oras/policy/ContainersPolicyTest.java b/src/test/java/land/oras/policy/ContainersPolicyTest.java
index 8c1fb36e..3a32376f 100644
--- a/src/test/java/land/oras/policy/ContainersPolicyTest.java
+++ b/src/test/java/land/oras/policy/ContainersPolicyTest.java
@@ -41,21 +41,6 @@
@Execution(ExecutionMode.SAME_THREAD)
class ContainersPolicyTest {
- @Test
- void acceptAllPolicyAllowsEverything() {
- ContainersPolicy policy = ContainersPolicy.acceptAll();
- assertTrue(policy.isAllowed(Transport.DOCKER, "docker.io/library/nginx"));
- assertTrue(policy.isAllowed(Transport.DOCKER, "quay.io/foo/bar"));
- assertTrue(policy.isAllowed(Transport.UNKNOWN, ""));
- }
-
- @Test
- void rejectAllPolicyDeniesEverything() {
- ContainersPolicy policy = ContainersPolicy.rejectAll();
- assertFalse(policy.isAllowed(Transport.DOCKER, "docker.io/library/nginx"));
- assertFalse(policy.isAllowed(Transport.DOCKER, "quay.io/foo/bar"));
- }
-
@Test
void loadAcceptAllFromFile() {
Path path = resourcePath("policy/accept-all.json");
@@ -191,23 +176,6 @@ void loadsUserPolicyFromHome(@TempDir Path homeDir) throws Exception {
});
}
- @Test
- void userPolicyTakesPrecedenceOverSystemPolicy(@TempDir Path homeDir) throws Exception {
- // language=json
- String userPolicyJson = """
- {"default": [{"type": "reject"}]}
- """;
- writePolicyFile(homeDir, userPolicyJson);
-
- TestUtils.withHome(homeDir, () -> {
- ContainersPolicy policy = ContainersPolicy.newPolicy();
- assertNotNull(policy);
- // User's reject-all should win (we can't know what system policy says, but we know
- // the user policy was loaded since docker.io is rejected)
- assertFalse(policy.isAllowed(Transport.DOCKER, "docker.io/library/nginx"));
- });
- }
-
@Test
@Execution(ExecutionMode.SAME_THREAD)
void fallsBackToAcceptAllWhenNoPolicyFileFound(@TempDir Path emptyHome) throws Exception {