Browse Source

Improve null-safety of buildpack/spring-boot-buildpack-platform

See gh-46926
pull/46973/head
Moritz Halbritter 7 months ago
parent
commit
ca7e025dfd
  1. 8
      buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildOwner.java
  2. 6
      buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackCoordinates.java
  3. 8
      buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java
  4. 3
      buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/TotalProgressListener.java
  5. 11
      buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadata.java
  6. 8
      buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ssl/PemPrivateKeyParser.java
  7. 9
      buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageConfig.java
  8. 8
      core/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemPrivateKeyParser.java

8
buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildOwner.java

@ -18,6 +18,8 @@ package org.springframework.boot.buildpack.platform.build;
import java.util.Map; import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.io.Owner;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -38,7 +40,7 @@ class BuildOwner implements Owner {
private final long gid; private final long gid;
BuildOwner(Map<String, String> env) { BuildOwner(Map<String, @Nullable String> env) {
this.uid = getValue(env, USER_PROPERTY_NAME); this.uid = getValue(env, USER_PROPERTY_NAME);
this.gid = getValue(env, GROUP_PROPERTY_NAME); this.gid = getValue(env, GROUP_PROPERTY_NAME);
} }
@ -48,7 +50,7 @@ class BuildOwner implements Owner {
this.gid = gid; this.gid = gid;
} }
private long getValue(Map<String, String> env, String name) { private long getValue(Map<String, @Nullable String> env, String name) {
String value = env.get(name); String value = env.get(name);
Assert.state(StringUtils.hasText(value), Assert.state(StringUtils.hasText(value),
() -> "Missing '" + name + "' value from the builder environment '" + env + "'"); () -> "Missing '" + name + "' value from the builder environment '" + env + "'");
@ -83,7 +85,7 @@ class BuildOwner implements Owner {
* @return a {@link BuildOwner} instance extracted from the env * @return a {@link BuildOwner} instance extracted from the env
* @throws IllegalStateException if the env does not contain the correct CNB variables * @throws IllegalStateException if the env does not contain the correct CNB variables
*/ */
static BuildOwner fromEnv(Map<String, String> env) { static BuildOwner fromEnv(Map<String, @Nullable String> env) {
Assert.notNull(env, "'env' must not be null"); Assert.notNull(env, "'env' must not be null");
return new BuildOwner(env); return new BuildOwner(env);
} }

6
buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackCoordinates.java

@ -106,15 +106,15 @@ final class BuildpackCoordinates {
private static BuildpackCoordinates fromToml(TomlParseResult toml, Path path) { private static BuildpackCoordinates fromToml(TomlParseResult toml, Path path) {
Assert.isTrue(!toml.isEmpty(), Assert.isTrue(!toml.isEmpty(),
() -> "Buildpack descriptor 'buildpack.toml' is required in buildpack '" + path + "'"); () -> "Buildpack descriptor 'buildpack.toml' is required in buildpack '" + path + "'");
Assert.hasText(toml.getString("buildpack.id"), String buildpackId = toml.getString("buildpack.id");
() -> "Buildpack descriptor must contain ID in buildpack '" + path + "'"); Assert.hasText(buildpackId, () -> "Buildpack descriptor must contain ID in buildpack '" + path + "'");
Assert.hasText(toml.getString("buildpack.version"), Assert.hasText(toml.getString("buildpack.version"),
() -> "Buildpack descriptor must contain version in buildpack '" + path + "'"); () -> "Buildpack descriptor must contain version in buildpack '" + path + "'");
Assert.isTrue(toml.contains("stacks") || toml.contains("order"), Assert.isTrue(toml.contains("stacks") || toml.contains("order"),
() -> "Buildpack descriptor must contain either 'stacks' or 'order' in buildpack '" + path + "'"); () -> "Buildpack descriptor must contain either 'stacks' or 'order' in buildpack '" + path + "'");
Assert.isTrue(!(toml.contains("stacks") && toml.contains("order")), Assert.isTrue(!(toml.contains("stacks") && toml.contains("order")),
() -> "Buildpack descriptor must not contain both 'stacks' and 'order' in buildpack '" + path + "'"); () -> "Buildpack descriptor must not contain both 'stacks' and 'order' in buildpack '" + path + "'");
return new BuildpackCoordinates(toml.getString("buildpack.id"), toml.getString("buildpack.version")); return new BuildpackCoordinates(buildpackId, toml.getString("buildpack.version"));
} }
/** /**

8
buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java

@ -160,10 +160,12 @@ public class DockerApi {
} }
private ApiVersion getApiVersion() { private ApiVersion getApiVersion() {
if (this.apiVersion == null) { ApiVersion apiVersion = this.apiVersion;
this.apiVersion = this.system.getApiVersion(); if (apiVersion == null) {
apiVersion = this.system.getApiVersion();
this.apiVersion = apiVersion;
} }
return this.apiVersion; return apiVersion;
} }
/** /**

3
buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/TotalProgressListener.java

@ -105,6 +105,9 @@ public abstract class TotalProgressListener<E extends ImageProgressUpdateEvent>
void update(ImageProgressUpdateEvent event) { void update(ImageProgressUpdateEvent event) {
String status = event.getStatus(); String status = event.getStatus();
if (status == null) {
return;
}
if (event.getProgressDetail() != null && this.progressByStatus.containsKey(status)) { if (event.getProgressDetail() != null && this.progressByStatus.containsKey(status)) {
int current = this.progressByStatus.get(status); int current = this.progressByStatus.get(status);
this.progressByStatus.put(status, updateProgress(current, event.getProgressDetail())); this.progressByStatus.put(status, updateProgress(current, event.getProgressDetail()));

11
buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadata.java

@ -89,7 +89,7 @@ final class DockerConfigurationMetadata {
return this.context; return this.context;
} }
DockerContext forContext(String context) { DockerContext forContext(@Nullable String context) {
return createDockerContext(this.configLocation, context); return createDockerContext(this.configLocation, context);
} }
@ -129,8 +129,9 @@ final class DockerConfigurationMetadata {
if (currentContext == null || DEFAULT_CONTEXT.equals(currentContext)) { if (currentContext == null || DEFAULT_CONTEXT.equals(currentContext)) {
return DockerContext.empty(); return DockerContext.empty();
} }
Path metaPath = Path.of(configLocation, CONTEXTS_DIR, META_DIR, asHash(currentContext), CONTEXT_FILE_NAME); String hash = asHash(currentContext);
Path tlsPath = Path.of(configLocation, CONTEXTS_DIR, TLS_DIR, asHash(currentContext), DOCKER_ENDPOINT); Path metaPath = Path.of(configLocation, CONTEXTS_DIR, META_DIR, hash, CONTEXT_FILE_NAME);
Path tlsPath = Path.of(configLocation, CONTEXTS_DIR, TLS_DIR, hash, DOCKER_ENDPOINT);
if (!metaPath.toFile().exists()) { if (!metaPath.toFile().exists()) {
throw new IllegalArgumentException("Docker context '" + currentContext + "' does not exist"); throw new IllegalArgumentException("Docker context '" + currentContext + "' does not exist");
} }
@ -146,14 +147,14 @@ final class DockerConfigurationMetadata {
} }
} }
private static @Nullable String asHash(String currentContext) { private static String asHash(String currentContext) {
try { try {
MessageDigest digest = MessageDigest.getInstance("SHA-256"); MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(currentContext.getBytes(StandardCharsets.UTF_8)); byte[] hash = digest.digest(currentContext.getBytes(StandardCharsets.UTF_8));
return HexFormat.of().formatHex(hash); return HexFormat.of().formatHex(hash);
} }
catch (NoSuchAlgorithmException ex) { catch (NoSuchAlgorithmException ex) {
return null; throw new IllegalStateException("SHA-256 is not available", ex);
} }
} }

8
buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ssl/PemPrivateKeyParser.java

@ -115,11 +115,11 @@ final class PemPrivateKeyParser {
private PemPrivateKeyParser() { private PemPrivateKeyParser() {
} }
private static PKCS8EncodedKeySpec createKeySpecForPkcs1Rsa(byte[] bytes, String password) { private static PKCS8EncodedKeySpec createKeySpecForPkcs1Rsa(byte[] bytes, @Nullable String password) {
return createKeySpecForAlgorithm(bytes, RSA_ALGORITHM, null); return createKeySpecForAlgorithm(bytes, RSA_ALGORITHM, null);
} }
private static PKCS8EncodedKeySpec createKeySpecForSec1Ec(byte[] bytes, String password) { private static PKCS8EncodedKeySpec createKeySpecForSec1Ec(byte[] bytes, @Nullable String password) {
DerElement ecPrivateKey = DerElement.of(bytes); DerElement ecPrivateKey = DerElement.of(bytes);
Assert.state(ecPrivateKey != null, "Unable to find private key"); Assert.state(ecPrivateKey != null, "Unable to find private key");
Assert.state(ecPrivateKey.isType(ValueType.ENCODED, TagType.SEQUENCE), Assert.state(ecPrivateKey.isType(ValueType.ENCODED, TagType.SEQUENCE),
@ -164,7 +164,7 @@ final class PemPrivateKeyParser {
} }
} }
private static PKCS8EncodedKeySpec createKeySpecForPkcs8(byte[] bytes, String password) { private static PKCS8EncodedKeySpec createKeySpecForPkcs8(byte[] bytes, @Nullable String password) {
DerElement ecPrivateKey = DerElement.of(bytes); DerElement ecPrivateKey = DerElement.of(bytes);
Assert.state(ecPrivateKey != null, "Unable to find private key"); Assert.state(ecPrivateKey != null, "Unable to find private key");
Assert.state(ecPrivateKey.isType(ValueType.ENCODED, TagType.SEQUENCE), Assert.state(ecPrivateKey.isType(ValueType.ENCODED, TagType.SEQUENCE),
@ -182,7 +182,7 @@ final class PemPrivateKeyParser {
return (algorithmName != null) ? new PKCS8EncodedKeySpec(bytes, algorithmName) : new PKCS8EncodedKeySpec(bytes); return (algorithmName != null) ? new PKCS8EncodedKeySpec(bytes, algorithmName) : new PKCS8EncodedKeySpec(bytes);
} }
private static PKCS8EncodedKeySpec createKeySpecForPkcs8Encrypted(byte[] bytes, String password) { private static PKCS8EncodedKeySpec createKeySpecForPkcs8Encrypted(byte[] bytes, @Nullable String password) {
return Pkcs8PrivateKeyDecryptor.decrypt(bytes, password); return Pkcs8PrivateKeyDecryptor.decrypt(bytes, password);
} }

9
buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageConfig.java

@ -24,6 +24,7 @@ import java.util.function.Consumer;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.MappedObject;
@ -38,7 +39,7 @@ public class ImageConfig extends MappedObject {
private final Map<String, String> labels; private final Map<String, String> labels;
private final Map<String, String> configEnv; private final Map<String, @Nullable String> configEnv;
ImageConfig(JsonNode node) { ImageConfig(JsonNode node) {
super(node, MethodHandles.lookup()); super(node, MethodHandles.lookup());
@ -55,12 +56,12 @@ public class ImageConfig extends MappedObject {
return labels; return labels;
} }
private Map<String, String> parseConfigEnv() { private Map<String, @Nullable String> parseConfigEnv() {
String[] entries = valueAt("/Env", String[].class); String[] entries = valueAt("/Env", String[].class);
if (entries == null) { if (entries == null) {
return Collections.emptyMap(); return Collections.emptyMap();
} }
Map<String, String> env = new LinkedHashMap<>(); Map<String, @Nullable String> env = new LinkedHashMap<>();
for (String entry : entries) { for (String entry : entries) {
int i = entry.indexOf('='); int i = entry.indexOf('=');
String name = (i != -1) ? entry.substring(0, i) : entry; String name = (i != -1) ? entry.substring(0, i) : entry;
@ -88,7 +89,7 @@ public class ImageConfig extends MappedObject {
* an empty {@code Map} is returned. * an empty {@code Map} is returned.
* @return the env, never {@code null} * @return the env, never {@code null}
*/ */
public Map<String, String> getEnv() { public Map<String, @Nullable String> getEnv() {
return this.configEnv; return this.configEnv;
} }

8
core/spring-boot/src/main/java/org/springframework/boot/ssl/pem/PemPrivateKeyParser.java

@ -115,11 +115,11 @@ final class PemPrivateKeyParser {
private PemPrivateKeyParser() { private PemPrivateKeyParser() {
} }
private static PKCS8EncodedKeySpec createKeySpecForPkcs1Rsa(byte[] bytes, String password) { private static PKCS8EncodedKeySpec createKeySpecForPkcs1Rsa(byte[] bytes, @Nullable String password) {
return createKeySpecForAlgorithm(bytes, RSA_ALGORITHM, null); return createKeySpecForAlgorithm(bytes, RSA_ALGORITHM, null);
} }
private static PKCS8EncodedKeySpec createKeySpecForSec1Ec(byte[] bytes, String password) { private static PKCS8EncodedKeySpec createKeySpecForSec1Ec(byte[] bytes, @Nullable String password) {
DerElement ecPrivateKey = DerElement.of(bytes); DerElement ecPrivateKey = DerElement.of(bytes);
Assert.state(ecPrivateKey != null, "Unable to find private key"); Assert.state(ecPrivateKey != null, "Unable to find private key");
Assert.state(ecPrivateKey.isType(ValueType.ENCODED, TagType.SEQUENCE), Assert.state(ecPrivateKey.isType(ValueType.ENCODED, TagType.SEQUENCE),
@ -164,7 +164,7 @@ final class PemPrivateKeyParser {
} }
} }
private static PKCS8EncodedKeySpec createKeySpecForPkcs8(byte[] bytes, String password) { private static PKCS8EncodedKeySpec createKeySpecForPkcs8(byte[] bytes, @Nullable String password) {
DerElement ecPrivateKey = DerElement.of(bytes); DerElement ecPrivateKey = DerElement.of(bytes);
Assert.state(ecPrivateKey != null, "Unable to find private key"); Assert.state(ecPrivateKey != null, "Unable to find private key");
Assert.state(ecPrivateKey.isType(ValueType.ENCODED, TagType.SEQUENCE), Assert.state(ecPrivateKey.isType(ValueType.ENCODED, TagType.SEQUENCE),
@ -182,7 +182,7 @@ final class PemPrivateKeyParser {
return (algorithmName != null) ? new PKCS8EncodedKeySpec(bytes, algorithmName) : new PKCS8EncodedKeySpec(bytes); return (algorithmName != null) ? new PKCS8EncodedKeySpec(bytes, algorithmName) : new PKCS8EncodedKeySpec(bytes);
} }
private static PKCS8EncodedKeySpec createKeySpecForPkcs8Encrypted(byte[] bytes, String password) { private static PKCS8EncodedKeySpec createKeySpecForPkcs8Encrypted(byte[] bytes, @Nullable String password) {
return Pkcs8PrivateKeyDecryptor.decrypt(bytes, password); return Pkcs8PrivateKeyDecryptor.decrypt(bytes, password);
} }

Loading…
Cancel
Save