Skip to content

Commit a2e7cc1

Browse files
committed
feat: follow oci content digest spec
1 parent b140536 commit a2e7cc1

File tree

2 files changed

+61
-28
lines changed

2 files changed

+61
-28
lines changed

src/main/java/org/jenkinsci/plugins/docker/commons/credentials/ImageNameValidator.java

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -78,21 +78,18 @@ public static boolean skipped() {
7878
}
7979
if (digestIdx > 0) {
8080
int start = slashIdx > 0 ? slashIdx + 1 : 0;
81-
args[1] = userAndRepo.substring(start, digestIdx);
82-
tagIdx = args[1].lastIndexOf(':');
83-
if (tagIdx > 0 && tagIdx < args[1].length() - 1) {
84-
args[2] = args[1].substring(tagIdx + 1);
85-
args[1] = args[1].substring(0, tagIdx);
86-
}
87-
if (digestIdx < userAndRepo.length() - 1) {
88-
args[3] = userAndRepo.substring(digestIdx + 1);
81+
String name = userAndRepo.substring(start, digestIdx);
82+
args[1] = name;
83+
tagIdx = name.lastIndexOf(':');
84+
if (tagIdx > 0) {
85+
args[1] = name.substring(0, tagIdx);
86+
args[2] = name.substring(tagIdx);
8987
}
88+
args[3] = userAndRepo.substring(digestIdx);
9089
} else if (tagIdx > 0) {
9190
int start = slashIdx > 0 ? slashIdx + 1 : 0;
9291
args[1] = userAndRepo.substring(start, tagIdx);
93-
if (tagIdx < userAndRepo.length() - 1) {
94-
args[2] = userAndRepo.substring(tagIdx + 1);
95-
}
92+
args[2] = userAndRepo.substring(tagIdx);
9693
}
9794
}
9895
return args;
@@ -148,15 +145,28 @@ public static void checkUserAndRepo(@NonNull String userAndRepo) throws FormVali
148145
}
149146

150147
/**
151-
* A digest starts with 'sha256:' and must be valid ASCII and may contain
152-
* lowercase and digits.
153-
* A digest contains 71 additional characters.
148+
* A content digest specified by open container spec.
154149
*
155-
* @see <a href=
156-
* "https://docs.docker.com/engine/reference/commandline/images/#list-the-full-length-image-ids">docker
157-
* digests</a>
150+
* @see <a href="https://docs.docker.com/registry/spec/api/#content-digests">Content Digests</a>
151+
* <a href="https://github.com/opencontainers/image-spec/blob/v1.0.1/descriptor.md#digests">OCI Digests</a>
158152
*/
159-
public static final Pattern VALID_DIGEST = Pattern.compile("^sha256:([a-z0-9]){64}$");
153+
public static final Pattern VALID_DIGEST = Pattern.compile("^@[a-z0-9]+([+._-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$");
154+
155+
/**
156+
* A SHA-256 content digest specified by open container spec.
157+
*
158+
* @see <a href="https://docs.docker.com/registry/spec/api/#content-digests">Content Digests</a>
159+
* <a href="https://github.com/opencontainers/image-spec/blob/v1.0.1/descriptor.md#digests">OCI Digests</a>
160+
*/
161+
public static final Pattern VALID_DIGEST_SHA256 = Pattern.compile("^@sha256:[a-z0-9]{64}$");
162+
163+
/**
164+
* A SHA-512 content digest specified by open container spec.
165+
*
166+
* @see <a href="https://docs.docker.com/registry/spec/api/#content-digests">Content Digests</a>
167+
* <a href="https://github.com/opencontainers/image-spec/blob/v1.0.1/descriptor.md#digests">OCI Digests</a>
168+
*/
169+
public static final Pattern VALID_DIGEST_SHA512 = Pattern.compile("^@sha512:[a-z0-9]{128}$");
160170

161171
/**
162172
* Validates a digest is following the rules.
@@ -174,8 +184,23 @@ public static void checkUserAndRepo(@NonNull String userAndRepo) throws FormVali
174184
if (StringUtils.isEmpty(digest)) {
175185
return FormValidation.ok();
176186
}
177-
if (digest.length() != 71) {
178-
return FormValidation.error("Digest length != 71");
187+
if (digest.startsWith("@sha256")) {
188+
if (digest.length() != 72) {
189+
return FormValidation.error("Digest length != 72");
190+
}
191+
if (!VALID_DIGEST_SHA256.matcher(digest).matches()) {
192+
return FormValidation.error("Digest must follow the pattern '%s' for sha-256 algorithm", VALID_DIGEST_SHA256.pattern());
193+
}
194+
return FormValidation.ok();
195+
}
196+
if (digest.startsWith("@sha512")) {
197+
if (digest.length() != 136) {
198+
return FormValidation.error("Digest length != 136");
199+
}
200+
if (!VALID_DIGEST_SHA512.matcher(digest).matches()) {
201+
return FormValidation.error("Digest must follow the pattern '%s' for sha-512 algorithm", VALID_DIGEST_SHA512.pattern());
202+
}
203+
return FormValidation.ok();
179204
}
180205
if (VALID_DIGEST.matcher(digest).matches()) {
181206
return FormValidation.ok();
@@ -191,7 +216,7 @@ public static void checkUserAndRepo(@NonNull String userAndRepo) throws FormVali
191216
*
192217
* @see <a href="https://docs.docker.com/engine/reference/commandline/tag/">docker tag</a>
193218
*/
194-
public static final Pattern VALID_TAG = Pattern.compile("^[a-zA-Z0-9_]([a-zA-Z0-9_.-]){0,127}");
219+
public static final Pattern VALID_TAG = Pattern.compile("^:[a-zA-Z0-9_]([a-zA-Z0-9_.-]){0,127}");
195220

196221

197222
/**

src/test/java/org/jenkinsci/plugins/docker/commons/credentials/ImageNameValidatorTest.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,23 @@ public class ImageNameValidatorTest {
1919
{"docker:80/jenkinsci/workflow-demo", FormValidation.Kind.OK},
2020
{"jenkinsci/workflow-demo:latest", FormValidation.Kind.OK},
2121
{"docker:80/jenkinsci/workflow-demo:latest", FormValidation.Kind.OK},
22+
{"jenkinsci/workflow-demo@", FormValidation.Kind.ERROR},
2223
{"workflow-demo:latest", FormValidation.Kind.OK},
2324
{"workflow-demo", FormValidation.Kind.OK},
2425
{"workflow-demo:latest@sha256:56930391cf0e1be83108422bbef43001650cfb75f64b3429928f0c5986fdb750", FormValidation.Kind.OK},
2526
{"workflow-demo:latest@sha256:56930391cf0e1be83108422bbef43001650cfb75f64b", FormValidation.Kind.ERROR},
2627
{"workflow-demo@sha256:56930391cf0e1be83108422bbef43001650cfb75f64b3429928f0c5986fdb750", FormValidation.Kind.OK},
28+
{"workflow-demo@sha256:56930391cf0e1be83108422bbef43001650cfb75f64b3429928f0c5986fdB750", FormValidation.Kind.ERROR},
29+
{"workflow-demo:", FormValidation.Kind.ERROR},
30+
{"workflow-demo:latest@", FormValidation.Kind.ERROR},
31+
{"workflow-demo@", FormValidation.Kind.ERROR},
2732
{"jenkinsci/workflow-demo@sha256:56930391cf0e1be83108422bbef43001650cfb75f64b3429928f0c5986fdb750", FormValidation.Kind.OK},
2833
{"docker:80/jenkinsci/workflow-demo@sha256:56930391cf0e1be83108422bbef43001650cfb75f64b3429928f0c5986fdb750", FormValidation.Kind.OK},
2934
{"docker:80/jenkinsci/workflow-demo:latest@sha256:56930391cf0e1be83108422bbef43001650cfb75f64b3429928f0c5986fdb750", FormValidation.Kind.OK},
30-
{"docker:80/jenkinsci/workflow-demo:latest@sha1:0123456789abcdef", FormValidation.Kind.ERROR}, //?
31-
{"workflow-demo:latest@", FormValidation.Kind.ERROR},
32-
{"workflow-demo@", FormValidation.Kind.ERROR},
33-
{"jenkinsci/workflow-demo@", FormValidation.Kind.ERROR},
34-
{"docker:80/jenkinsci/workflow-demo@", FormValidation.Kind.ERROR},
35-
{"docker:80/jenkinsci/workflow-demo:latest@", FormValidation.Kind.ERROR},
35+
{"docker:80/jenkinsci/workflow-demo:latest@sha1:0123456789abcdef", FormValidation.Kind.OK},
36+
{"docker:80/jenkinsci/workflow-demo:latest@sha1:", FormValidation.Kind.ERROR},
37+
{"docker:80/jenkinsci/workflow-demo@", FormValidation.Kind.ERROR},
38+
{"docker:80/jenkinsci/workflow-demo:latest@", FormValidation.Kind.ERROR},
3639
{":tag", FormValidation.Kind.ERROR},
3740
{"name:tag", FormValidation.Kind.OK},
3841
{"name:.tag", FormValidation.Kind.ERROR},
@@ -52,6 +55,10 @@ public class ImageNameValidatorTest {
5255
{":", FormValidation.Kind.ERROR},
5356
{" ", FormValidation.Kind.ERROR},
5457

58+
{"a@sha512:56930391cf0e1be83108422bbef43001650cfb75f64b", FormValidation.Kind.ERROR},
59+
{"a@sha512:56930391cf0e1be83108422bbef43001650cfb75f64b3429928f0c5986fdb75056930391cf0e1be83108422bbef43001650cfb75f64b3429928f0c5986fdb750", FormValidation.Kind.OK},
60+
{"a@sha512:B6930391cf0e1be83108422bbef43001650cfb75f64b3429928f0c5986fdb75056930391cf0e1be83108422bbef43001650cfb75f64b3429928f0c5986fdb750", FormValidation.Kind.ERROR}
61+
5562
};
5663
}
5764

@@ -65,6 +72,7 @@ public ImageNameValidatorTest(final String userAndRepo, final FormValidation.Kin
6572

6673
@Test
6774
public void test() {
68-
assertSame(expected, ImageNameValidator.validateUserAndRepo(userAndRepo).kind);
75+
FormValidation res = ImageNameValidator.validateUserAndRepo(userAndRepo);
76+
assertSame(userAndRepo + " : " + res.getMessage(), expected, res.kind);
6977
}
7078
}

0 commit comments

Comments
 (0)