Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/main/java/land/oras/ContainerRef.java
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ public String getEffectiveRegistry(Registry target) {
? target.getRegistry()
: determineFirstUnqualifiedSearchRegistry(target);
}
// The effective registry can we rewrotten by the registry configuration.
// The effective registry can be rewritten by the registry configuration.
// Ensure to return it
ContainerRef rewrite = target.getRegistriesConf().rewrite(this);
return rewrite.getRegistry();
Expand All @@ -506,14 +506,15 @@ public ContainerRef forRegistry(String registry) {
public boolean isInsecure(Registry registry) {
String effectiveRegistry = getEffectiveRegistry(registry);
ContainerRef effectiveRef = forRegistry(effectiveRegistry);
if (registry.getRegistriesConf().isInsecure(effectiveRef)) {
// Configuration is authoritative over the current registry
if (registry.getRegistriesConf().isInsecure(registry, effectiveRef)) {
LOG.debug(
"Access to container reference {} is insecure by location configuration for registry {}",
this,
effectiveRegistry);
return true;
}
return registry.isInsecure();
return false;
}

/**
Expand Down
172 changes: 165 additions & 7 deletions src/main/java/land/oras/Registry.java

Large diffs are not rendered by default.

89 changes: 86 additions & 3 deletions src/main/java/land/oras/auth/RegistriesConf.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.Optional;
import land.oras.ContainerRef;
import land.oras.OrasModel;
import land.oras.Registry;
import land.oras.exception.OrasException;
import land.oras.utils.Const;
import land.oras.utils.TomlUtils;
Expand Down Expand Up @@ -99,23 +100,50 @@ public static RegistriesConf newConf() {
return newConf(paths);
}

/**
* The model of a mirror entry within a [[registry]] table.
* @param location The mirror registry location (host[:port][/path]).
* @param insecure Whether the mirror is insecure.
*/
@OrasModel
public record MirrorConfig(
@Nullable @JsonProperty("location") String location, @Nullable @JsonProperty("insecure") Boolean insecure) {
/**
* Return true if this mirror should be accessed over plain HTTP or with unverified TLS.
* @return true if insecure
*/
public boolean isInsecure() {
return insecure != null && insecure;
}
}

/**
* The model of the registry configuration
* @param prefix The prefix to match against container references.
* @param location The registry location
* @param blocked Whether the registry is blocked. If true, the registry is blocked and cannot be used for pulling or pushing images.
* @param insecure Whether the registry is insecure. If true, the registry is considered insecure and may allow connections over HTTP or with invalid TLS certificates.
* @param mirrors Ordered list of mirror entries to try before the registry location.
*/
@OrasModel
record RegistryConfig(
@Nullable @JsonProperty("prefix") String prefix,
@Nullable @JsonProperty("location") String location,
@Nullable @JsonProperty("blocked") Boolean blocked,
@Nullable @JsonProperty("insecure") Boolean insecure) {
@Nullable @JsonProperty("insecure") Boolean insecure,
@Nullable @JsonProperty("mirror") List<MirrorConfig> mirrors) {
/**
* Return true if this registry is blocked and cannot be used for pulling or pushing images.
* @return true if blocked
*/
public boolean isBlocked() {
return blocked != null && blocked;
}

/**
* Return true if this registry should be accessed over plain HTTP or with unverified TLS.
* @return true if insecure
*/
public boolean isInsecure() {
return insecure != null && insecure;
}
Expand Down Expand Up @@ -254,11 +282,14 @@ public boolean isBlocked(ContainerRef location) {

/**
* Check if the given registry is marked as insecure in the configuration.
* If no entry found, we fall back to Registry configuration
* If the entry is found we use the insecure flag (or default true)
* @param registry The registry object
* @param location the registry location to check for insecurity.
* @return true if the registry is marked as insecure, false otherwise.
*/
public boolean isInsecure(ContainerRef location) {
return selectMatchingTable(location).map(RegistryConfig::isInsecure).orElse(false);
public boolean isInsecure(Registry registry, ContainerRef location) {
return selectMatchingTable(location).map(RegistryConfig::isInsecure).orElse(registry.isInsecure());
}

/**
Expand Down Expand Up @@ -333,6 +364,58 @@ public ContainerRef rewrite(ContainerRef ref) {
return ContainerRef.parse(rewrittenRefString);
}

/**
* Return the ordered list of mirrors configured for the registry that matches the given reference.
* @param ref the container reference to look up mirrors for.
* @return an unmodifiable list of mirror configs (may be empty).
*/
public List<MirrorConfig> getMirrors(ContainerRef ref) {
Optional<RegistryConfig> matchingConfig = selectMatchingTable(ref);
if (matchingConfig.isEmpty() || matchingConfig.get().mirrors() == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(matchingConfig.get().mirrors());
}

/**
* Rewrite the given container reference to use the mirror's location, replacing the registry host.
* @param ref the original container reference.
* @param mirror the mirror configuration to apply.
* @return the rewritten reference pointing at the mirror.
*/
public ContainerRef rewriteForMirror(ContainerRef ref, MirrorConfig mirror) {
String mirrorLocation = mirror.location();
if (mirrorLocation == null || mirrorLocation.isBlank()) {
return ref;
}
// Strip trailing slashes to prevent double-slash segments in the rewritten ref
mirrorLocation = mirrorLocation.replaceAll("/+$", "");
if (mirrorLocation.isBlank()) {
return ref;
}
// Always build from components to correctly handle:
// - unqualified refs (toString() omits the registry)
// - digest-only refs (tag is null, toString() would produce ":null")
String namespace = ref.getNamespace();
String repository = ref.getRepository();
String tag = ref.getTag();
String digest = ref.getDigest();
StringBuilder sb = new StringBuilder(mirrorLocation);
if (namespace != null && !namespace.isEmpty()) {
sb.append("/").append(namespace);
}
sb.append("/").append(repository);
if (tag != null && !tag.isEmpty()) {
sb.append(":").append(tag);
}
if (digest != null && !digest.isEmpty()) {
sb.append("@").append(digest);
}
String rewrittenRefString = sb.toString();
LOG.debug("Rewriting '{}' to mirror '{}'", ref, rewrittenRefString);
return ContainerRef.parse(rewrittenRefString);
}

/**
* Select the matching registry configuration table for the container reference.
* @param ref the container reference to find the matching registry configuration for.
Expand Down
4 changes: 2 additions & 2 deletions src/test/java/land/oras/ClassAnnotationsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ void shouldHaveAnnotationOnModel() {
.loadClasses());

// Check number of classes
assertEquals(26, modelClasses.size());
assertEquals(27, modelClasses.size());

// Check classes
assertTrue(modelClasses.contains(Annotations.class));
Expand Down Expand Up @@ -83,7 +83,7 @@ void shouldHaveAnnotationOnAuthPackage() {
.loadClasses());

// Check number of classes
assertEquals(8, modelClasses.size());
assertEquals(9, modelClasses.size());
}
}
}
Loading
Loading