Skip to content

Commit 867364d

Browse files
authored
Allow setting short-name-mode and set mode to enforcing by default (like podman / CRI-IO) (#560)
2 parents 0b79b88 + 8974948 commit 867364d

4 files changed

Lines changed: 150 additions & 1 deletion

File tree

src/main/java/land/oras/ContainerRef.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,7 @@ private String determineFirstUnqualifiedSearchRegistry(Registry registry) {
531531
LOG.debug(
532532
"Found registries in unqualified-search-registries: {}",
533533
registry.getRegistriesConf().getUnqualifiedRegistries());
534+
registry.getRegistriesConf().enforceShortNameMode();
534535
List<String> unqualifiedRegistries = registry.getRegistriesConf().getUnqualifiedRegistries();
535536
for (String searchRegistry : unqualifiedRegistries) {
536537
Registry targetRegistry = registry.copy(searchRegistry);

src/main/java/land/oras/auth/RegistriesConf.java

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020

2121
package land.oras.auth;
2222

23+
import com.fasterxml.jackson.annotation.JsonCreator;
2324
import com.fasterxml.jackson.annotation.JsonProperty;
25+
import com.fasterxml.jackson.annotation.JsonValue;
2426
import java.nio.file.Files;
2527
import java.nio.file.Path;
2628
import java.util.Collections;
@@ -132,13 +134,52 @@ static ParsedPrefix parse(String prefix) {
132134
}
133135
}
134136

137+
/**
138+
* The for handling short name
139+
*/
140+
enum ShortNameMode {
141+
142+
/**
143+
* Use all unqualified-search registries without any restriction
144+
*/
145+
DISABLED("disabled"),
146+
147+
/**
148+
* If only one unqualified-search registry is set, use it as there is no ambiguity.
149+
* If there is more than one registry this throw an error (default)
150+
*/
151+
ENFORCING("enforcing"),
152+
153+
/**
154+
* Same as enforcing for ORAS Java SDK
155+
*/
156+
PERMISSIVE("permissive");
157+
158+
ShortNameMode(String value) {
159+
this.value = value;
160+
}
161+
162+
@JsonCreator
163+
public static ShortNameMode fromString(String key) {
164+
return ShortNameMode.valueOf(key.toUpperCase());
165+
}
166+
167+
@JsonValue
168+
public String getKey() {
169+
return value;
170+
}
171+
172+
private String value;
173+
}
174+
135175
/**
136176
* The model of the configuration file, which contains the list of registry configurations, aliases, and unqualified registries.
137177
* @param registries The list of registry configurations, each containing the registry location, whether it is blocked, and whether it is insecure.
138178
* @param aliases The map of registry aliases, where the key is the alias and the value is the actual registry URL.
139179
* @param unqualifiedRegistries The list of unqualified registries, which are registries that can be used without specifying a registry.
140180
*/
141181
record ConfigFile(
182+
@JsonProperty("short-name-mode") @Nullable ShortNameMode shortNameMode,
142183
@JsonProperty("registry") @Nullable List<RegistryConfig> registries,
143184
@JsonProperty("aliases") @Nullable Map<String, String> aliases,
144185
@JsonProperty("unqualified-search-registries") @Nullable List<String> unqualifiedRegistries) {}
@@ -151,6 +192,23 @@ public List<String> getUnqualifiedRegistries() {
151192
return Collections.unmodifiableList(config.unqualifiedRegistries);
152193
}
153194

195+
/**
196+
* Enforce the short name mode by checking the configuration. If the short name mode is set to ENFORCING or PERMISSIVE and there are multiple unqualified registries configured, this method throws an OrasException indicating that the configuration is invalid. If the configuration is valid, this method does nothing.
197+
* @throws OrasException if the short name mode is set to ENFORCING or PERMISSIVE and there are multiple unqualified registries configured, indicating that the configuration is invalid.
198+
*/
199+
public void enforceShortNameMode() throws OrasException {
200+
if ((config.shortNameMode == ShortNameMode.ENFORCING || config.shortNameMode == ShortNameMode.PERMISSIVE)
201+
&& config.unqualifiedRegistries.size() > 1) {
202+
throw new OrasException(
203+
"Short name mode is set to ENFORCING/PERMISSION but multiple unqualified registries are configured: "
204+
+ config.unqualifiedRegistries);
205+
}
206+
LOG.debug(
207+
"Short name mode '{}' is valid with unqualified registries: {}",
208+
config.shortNameMode,
209+
config.unqualifiedRegistries);
210+
}
211+
154212
/**
155213
* Return the aliases
156214
* @return an unmodifiable map of aliases, where the key is the alias and the value is the actual registry URL.
@@ -315,6 +373,11 @@ private Config() {}
315373
this.registries.addAll(registryConfigs);
316374
}
317375

376+
/**
377+
* Default to enforcing
378+
*/
379+
private ShortNameMode shortNameMode = ShortNameMode.ENFORCING;
380+
318381
/**
319382
* List of unqualified registries.
320383
*/
@@ -351,6 +414,10 @@ public static Config load(ConfigFile configFile) throws OrasException {
351414
LOG.trace("Loading registry configurations: {}", configFile.registries);
352415
config.registries.addAll(configFile.registries);
353416
}
417+
if (configFile.shortNameMode != null) {
418+
LOG.trace("Loading short name mode: {}", configFile.shortNameMode);
419+
config.shortNameMode = configFile.shortNameMode;
420+
}
354421
return config;
355422
}
356423
}

src/test/java/land/oras/RegistryTest.java

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,81 @@ void shouldThrowIfUnableToFindOnAnyUnQualifiedSearchRegistry(@TempDir Path homeD
8787
Registry registry = Registry.builder().insecure().defaults().build();
8888
ContainerRef unqualifiedRef = ContainerRef.parse("docker/library/alpine:latest");
8989
assertTrue(unqualifiedRef.isUnqualified(), "ContainerRef must be unqualified");
90-
assertThrows(OrasException.class, () -> unqualifiedRef.getEffectiveRegistry(registry));
90+
OrasException e = assertThrows(OrasException.class, () -> unqualifiedRef.getEffectiveRegistry(registry));
91+
assertEquals("Invalid WWW-Authenticate header", e.getMessage());
92+
});
93+
}
94+
95+
@Test
96+
@Execution(ExecutionMode.SAME_THREAD)
97+
void shouldEnforceMultipleRegistriesWithDefaultEnforcingMode(@TempDir Path homeDir) throws Exception {
98+
99+
// language=toml
100+
String config = """
101+
unqualified-search-registries = ["%s", "localhost:5000"]
102+
"""
103+
.formatted(registry.getRegistry());
104+
105+
TestUtils.createRegistriesConfFile(homeDir, config);
106+
107+
TestUtils.withHome(homeDir, () -> {
108+
Registry registry = Registry.builder().insecure().defaults().build();
109+
ContainerRef unqualifiedRef = ContainerRef.parse("docker/library/alpine:latest");
110+
assertTrue(unqualifiedRef.isUnqualified(), "ContainerRef must be unqualified");
111+
OrasException e = assertThrows(OrasException.class, () -> unqualifiedRef.getEffectiveRegistry(registry));
112+
assertEquals(
113+
"Short name mode is set to ENFORCING/PERMISSION but multiple unqualified registries are configured: [%s, localhost:5000]"
114+
.formatted(this.registry.getRegistry()),
115+
e.getMessage());
116+
});
117+
}
118+
119+
@Test
120+
@Execution(ExecutionMode.SAME_THREAD)
121+
void shouldAllowMultipleRegistriesWithDisabledEnforcingMode(@TempDir Path homeDir) throws Exception {
122+
123+
// language=toml
124+
String config =
125+
"""
126+
short-name-mode = "disabled"
127+
unqualified-search-registries = ["%s", "localhost:5000"]
128+
"""
129+
.formatted(registry.getRegistry());
130+
131+
TestUtils.createRegistriesConfFile(homeDir, config);
132+
133+
TestUtils.withHome(homeDir, () -> {
134+
Registry registry = Registry.builder().insecure().defaults().build();
135+
ContainerRef unqualifiedRef = ContainerRef.parse("docker/library/alpine:latest");
136+
assertTrue(unqualifiedRef.isUnqualified(), "ContainerRef must be unqualified");
137+
OrasException e = assertThrows(OrasException.class, () -> unqualifiedRef.getEffectiveRegistry(registry));
138+
assertEquals("Invalid WWW-Authenticate header", e.getMessage());
139+
});
140+
}
141+
142+
@Test
143+
@Execution(ExecutionMode.SAME_THREAD)
144+
void shouldEnforceMultipleRegistriesWithPermissiveEnforcingMode(@TempDir Path homeDir) throws Exception {
145+
146+
// language=toml
147+
String config =
148+
"""
149+
short-name-mode = "permissive"
150+
unqualified-search-registries = ["%s", "localhost:5000"]
151+
"""
152+
.formatted(registry.getRegistry());
153+
154+
TestUtils.createRegistriesConfFile(homeDir, config);
155+
156+
TestUtils.withHome(homeDir, () -> {
157+
Registry registry = Registry.builder().insecure().defaults().build();
158+
ContainerRef unqualifiedRef = ContainerRef.parse("docker/library/alpine:latest");
159+
assertTrue(unqualifiedRef.isUnqualified(), "ContainerRef must be unqualified");
160+
OrasException e = assertThrows(OrasException.class, () -> unqualifiedRef.getEffectiveRegistry(registry));
161+
assertEquals(
162+
"Short name mode is set to ENFORCING/PERMISSION but multiple unqualified registries are configured: [%s, localhost:5000]"
163+
.formatted(this.registry.getRegistry()),
164+
e.getMessage());
91165
});
92166
}
93167

src/test/java/land/oras/auth/RegistryConfTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
package land.oras.auth;
2222

23+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
2324
import static org.junit.jupiter.api.Assertions.assertEquals;
2425
import static org.junit.jupiter.api.Assertions.assertFalse;
2526
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -62,34 +63,39 @@ void shouldCheckRegistryStatusWithLocationOnly() {
6263
RegistriesConf conf = new RegistriesConf(new RegistriesConf.Config(List.of(registry)));
6364
assertFalse(conf.isBlocked(ContainerRef.parse("localhost:5000/library/test:latest")));
6465
assertFalse(conf.isBlocked(ContainerRef.parse("localhost:5001/library/test:latest")));
66+
assertDoesNotThrow(conf::enforceShortNameMode);
6567

6668
// With blocked true
6769
registry = new RegistriesConf.RegistryConfig(null, "localhost:5000", true, null);
6870
assertTrue(registry.isBlocked(), "Registry should be blocked when blocked is true");
6971
conf = new RegistriesConf(new RegistriesConf.Config(List.of(registry)));
7072
assertTrue(conf.isBlocked(ContainerRef.parse("localhost:5000/library/test:latest")));
7173
assertFalse(conf.isBlocked(ContainerRef.parse("localhost:5001/library/test:latest")));
74+
assertDoesNotThrow(conf::enforceShortNameMode);
7275

7376
// With insecure true
7477
registry = new RegistriesConf.RegistryConfig(null, "localhost:5000", null, true);
7578
assertTrue(registry.isInsecure(), "Registry should be insecure when insecure is true");
7679
conf = new RegistriesConf(new RegistriesConf.Config(List.of(registry)));
7780
assertTrue(conf.isInsecure(ContainerRef.parse("localhost:5000/library/test:latest")));
7881
assertFalse(conf.isInsecure(ContainerRef.parse("localhost:5001/library/test:latest")));
82+
assertDoesNotThrow(conf::enforceShortNameMode);
7983

8084
// With blocked false
8185
registry = new RegistriesConf.RegistryConfig(null, "localhost:5000", false, null);
8286
assertFalse(registry.isBlocked(), "Registry should not be blocked when blocked is false");
8387
conf = new RegistriesConf(new RegistriesConf.Config(List.of(registry)));
8488
assertFalse(conf.isBlocked(ContainerRef.parse("localhost:5000/library/test:latest")));
8589
assertFalse(conf.isBlocked(ContainerRef.parse("localhost:5001/library/test:latest")));
90+
assertDoesNotThrow(conf::enforceShortNameMode);
8691

8792
// With insecure false
8893
registry = new RegistriesConf.RegistryConfig(null, "localhost:5000", null, false);
8994
assertFalse(registry.isInsecure(), "Registry should be insecure when insecure is false");
9095
conf = new RegistriesConf(new RegistriesConf.Config(List.of(registry)));
9196
assertFalse(conf.isInsecure(ContainerRef.parse("localhost:5000/library/test:latest")));
9297
assertFalse(conf.isInsecure(ContainerRef.parse("localhost:5001/library/test:latest")));
98+
assertDoesNotThrow(conf::enforceShortNameMode);
9399
}
94100

95101
@Test
@@ -107,6 +113,7 @@ void shouldCheckRegistryStatusWithPrefixExactMatch() {
107113
RegistriesConf conf = new RegistriesConf(new RegistriesConf.Config(List.of(registry)));
108114
assertFalse(conf.isBlocked(ContainerRef.parse("localhost:5000/library/test:latest")));
109115
assertFalse(conf.isBlocked(ContainerRef.parse("localhost:5001/library/test:latest")));
116+
assertDoesNotThrow(conf::enforceShortNameMode);
110117

111118
// With blocked true
112119
registry = new RegistriesConf.RegistryConfig("localhost:5000", null, true, null);

0 commit comments

Comments
 (0)