Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#4292] Fix mounted data path directory permissions for besu user #7575

Merged
merged 12 commits into from
Sep 18, 2024
Merged
66 changes: 66 additions & 0 deletions besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,10 @@
import java.net.URI;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.UserPrincipal;
import java.time.Clock;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -382,6 +385,11 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
arity = "1")
private final Optional<String> identityString = Optional.empty();

@Option(
names = "--print-paths-and-exit",
description = "Print the configured paths and exit without starting the node.")
private final Boolean printPathsAndExit = false;

// P2P Discovery Option Group
@CommandLine.ArgGroup(validate = false, heading = "@|bold P2P Discovery Options|@%n")
P2PDiscoveryOptionGroup p2PDiscoveryOptionGroup = new P2PDiscoveryOptionGroup();
Expand Down Expand Up @@ -1093,6 +1101,12 @@ public void run() {
try {
configureLogging(true);

if (printPathsAndExit) {
// Print configured paths requiring read/write permissions to be adjusted
checkPermissionsAndPrintPaths();
System.exit(0); // Exit before any services are started
}

// set merge config on the basis of genesis config
setMergeConfigOptions();

Expand Down Expand Up @@ -1138,6 +1152,58 @@ public void run() {
}
}

private void checkPermissionsAndPrintPaths() {
// Check permissions for the data path
checkPermissions(dataDir(), "besu", false);
pullurib marked this conversation as resolved.
Show resolved Hide resolved

// Check permissions for genesis file
try {
if (genesisFile != null) {
checkPermissions(genesisFile.toPath(), "besu", true);
}
} catch (Exception e) {
System.out.println("Error: Failed checking genesis file: Reason: " + e.getMessage());
pullurib marked this conversation as resolved.
Show resolved Hide resolved
}
}

// Helper method to check permissions on a given path
private void checkPermissions(final Path path, final String besuUser, final boolean readOnly) {
try {
// Get the permissions of the file
// check if besu user is the owner - get owner permissions if yes
// else, check if besu and owner are in the same group - if yes, check the group permission
// otherwise check permissions for others

// Get the owner of the file or directory
UserPrincipal owner = Files.getOwner(path);
boolean hasReadPermission, hasWritePermission;

// Get file permissions
Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path);

// Check if besu is the owner
if (owner.getName().equals(besuUser)) {
// Owner permissions
hasReadPermission = permissions.contains(PosixFilePermission.OWNER_READ);
hasWritePermission = permissions.contains(PosixFilePermission.OWNER_WRITE);
} else {
// Skipping group check for now as we know besu user and root don't have a common group
pullurib marked this conversation as resolved.
Show resolved Hide resolved
// Others' permissions
hasReadPermission = permissions.contains(PosixFilePermission.OTHERS_READ);
hasWritePermission = permissions.contains(PosixFilePermission.OTHERS_WRITE);
}

if (!hasReadPermission || (!readOnly && !hasWritePermission)) {
String accessType = readOnly ? "READ" : "READ_WRITE";
System.out.println("PERMISSION_CHECK_PATH:" + path + ":" + accessType);
}
} catch (Exception e) {
// Do nothing upon catching an error
System.out.println(
"Error: Failed to check permissions for path: '" + path + "'. Reason: " + e.getMessage());
}
}

@VisibleForTesting
void setBesuConfiguration(final BesuConfigurationImpl pluginCommonConfiguration) {
this.pluginCommonConfiguration = pluginCommonConfiguration;
Expand Down
45 changes: 45 additions & 0 deletions besu/src/main/scripts/besu-entry.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/bash
##
## Copyright contributors to Hyperledger Besu.
##
## Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
## the License. You may obtain a copy of the License at
##
## http://www.apache.org/licenses/LICENSE-2.0
##
## Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
## an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
## specific language governing permissions and limitations under the License.
##
## SPDX-License-Identifier: Apache-2.0
##

# Run Besu first to get paths needing permission adjustment
output=$(/opt/besu/bin/besu --print-paths-and-exit "$@")

# Parse the output to find the paths and their required access types
echo "$output" | while IFS=: read -r prefix path accessType; do
if [[ "$prefix" == "PERMISSION_CHECK_PATH" ]]; then
# Change ownership to besu user and group
chown -R besu:besu $path

# Ensure read/write permissions for besu user

echo "Setting permissions for: $path with access: $accessType"

if [[ "$accessType" == "READ" ]]; then
# Set read-only permissions for besu user
chmod -R u+r $path
elif [[ "$accessType" == "READ_WRITE" ]]; then
# Set read/write permissions for besu user
chmod -R u+rw $path
fi
pullurib marked this conversation as resolved.
Show resolved Hide resolved
fi
done

# Finally, run Besu with the actual arguments passed to the container
# Construct the command as a single string
COMMAND="/opt/besu/bin/besu $@"

# Switch to the besu user and execute the command
exec su -s /bin/bash besu -c "$COMMAND"
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ public void tomlThatConfiguresEverythingExceptPermissioningToml() throws IOExcep
options.remove(spec.optionsMap().get("--config-file"));
options.remove(spec.optionsMap().get("--help"));
options.remove(spec.optionsMap().get("--version"));
options.remove(spec.optionsMap().get("--print-paths-and-exit"));

for (final String tomlKey : tomlResult.keySet()) {
final CommandLine.Model.OptionSpec optionSpec = spec.optionsMap().get("--" + tomlKey);
Expand Down
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,7 @@ task evmToolStartScripts(type: CreateStartScripts) {
doLast { tweakStartScript(evmToolStartScripts) }
}


task autocomplete(type: JavaExec) {
dependsOn compileJava
def tempAutocompleteFile = File.createTempFile("besu", ".autocomplete")
Expand Down Expand Up @@ -1097,6 +1098,8 @@ distributions {
from("build/reports/license/license-dependency.html") { into "." }
from("./docs/GettingStartedBinaries.md") { into "." }
from("./docs/DocsArchive0.8.0.html") { into "." }
from("./docs/DocsArchive0.8.0.html") { into "." }
from("./besu/src/main/scripts/besu-entry.sh") { into "./bin/" }
from(autocomplete) { into "." }
}
}
Expand Down
5 changes: 4 additions & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ ENV OTEL_RESOURCE_ATTRIBUTES="service.name=besu,service.version=$VERSION"
ENV OLDPATH="${PATH}"
ENV PATH="/opt/besu/bin:${OLDPATH}"

ENTRYPOINT ["besu"]
USER root
RUN chmod +x /opt/besu/bin/besu-entry.sh

ENTRYPOINT ["besu-entry.sh"]
HEALTHCHECK --start-period=5s --interval=5s --timeout=1s --retries=10 CMD bash -c "[ -f /tmp/pid ]"

# Build-time metadata as defined at http://label-schema.org
Expand Down
8 changes: 8 additions & 0 deletions docker/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,12 @@ bash $TEST_PATH/dgoss run --sysctl net.ipv6.conf.all.disable_ipv6=1 $DOCKER_IMAG
--graphql-http-enabled \
> ./reports/01.xml || i=`expr $i + 1`

if [[ $i != 0 ]]; then exit $i; fi

# Test for directory permissions
GOSS_FILES_PATH=$TEST_PATH/02 \
bash $TEST_PATH/dgoss run --sysctl net.ipv6.conf.all.disable_ipv6=1 -v besu-data:/var/lib/besu $DOCKER_IMAGE --data-path=/var/lib/besu \
--network=dev \
> ./reports/02.xml || i=`expr $i + 1`

exit $i
10 changes: 10 additions & 0 deletions docker/tests/02/goss.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
# runtime docker tests
file:
/var/lib/besu:
exists: true
owner: besu
mode: "0755"
process:
java:
running: true
2 changes: 1 addition & 1 deletion docker/tests/dgoss
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ GOSS_PATH="${GOSS_PATH:-$(which goss 2> /dev/null || true)}"
[[ $GOSS_PATH ]] || { error "Couldn't find goss installation, please set GOSS_PATH to it"; }
[[ ${GOSS_OPTS+x} ]] || GOSS_OPTS="--color --format documentation"
[[ ${GOSS_WAIT_OPTS+x} ]] || GOSS_WAIT_OPTS="-r 30s -s 1s > /dev/null"
GOSS_SLEEP=${GOSS_SLEEP:-0.2}
GOSS_SLEEP=${GOSS_SLEEP:-1.0}

case "$1" in
run)
Expand Down