diff --git a/.gitattributes b/.gitattributes index 9cd0768..911dc25 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,6 @@ * text=auto *.sh eol=lf +**/sbin/** eol=lf **/run eol=lf */services.d/* eol=lf **/run eol=lf diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..35c483d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "files.eol": "\n" +} \ No newline at end of file diff --git a/alpine/docker-compose.yml b/alpine/docker-compose.yml index e715e71..2d88487 100644 --- a/alpine/docker-compose.yml +++ b/alpine/docker-compose.yml @@ -4,6 +4,7 @@ services: build: . image: homecentr/base:alpine-local environment: + PUID_ADDITIONAL_GROUPS: "9005:test-group" FILE__TEST_VAR: "/var-value.txt" UNRELATED: "some-var" volumes: diff --git a/alpine/fs/etc/cont-init.d/01-env.sh b/alpine/fs/etc/cont-init.d/01-env.sh index fd458c8..810aad7 100644 --- a/alpine/fs/etc/cont-init.d/01-env.sh +++ b/alpine/fs/etc/cont-init.d/01-env.sh @@ -1,5 +1,7 @@ #!/usr/bin/with-contenv ash +source homecentr_set_s6_env_var + MATCHING_FILES=$(find /var/run/s6/container_environment/ -name "FILE__*" | wc -l) if [ $MATCHING_FILES -le 0 ]; then @@ -9,14 +11,16 @@ fi for FULL_FILENAME in /var/run/s6/container_environment/FILE__*; do FILENAME="${FULL_FILENAME#'/var/run/s6/container_environment/'}" - #if [[ $FILENAME = "FILE__"* ]]; then - VAR_VALUE_FILE=$(cat ${FULL_FILENAME}) - VAR_NAME="${FILENAME#'FILE__'}" + VAR_VALUE_FILE=$(cat ${FULL_FILENAME}) + VAR_NAME="${FILENAME#'FILE__'}" + + if [ -f "$VAR_VALUE_FILE" ]; then + VAR_VALUE=$(cat "$VAR_VALUE_FILE") + + homecentr_set_s6_env_var "$VAR_NAME" "$VAR_VALUE" - if [ -f "$VAR_VALUE_FILE" ]; then - cat "$VAR_VALUE_FILE" > "/var/run/s6/container_environment/${VAR_NAME}" - echo "[env-vars] Variable $VAR_NAME set from ${VAR_VALUE_FILE}" - else - echo "[env-vars] Variable $VAR_NAME could not be set from ${VAR_VALUE_FILE}. File not found." - fi + echo "[env-vars] Variable $VAR_NAME set from ${VAR_VALUE_FILE}" + else + echo "[env-vars] Variable $VAR_NAME could not be set from ${VAR_VALUE_FILE}. File not found." + fi done \ No newline at end of file diff --git a/alpine/fs/etc/cont-init.d/10-init.sh b/alpine/fs/etc/cont-init.d/10-init.sh index 3dceb17..e04ca25 100644 --- a/alpine/fs/etc/cont-init.d/10-init.sh +++ b/alpine/fs/etc/cont-init.d/10-init.sh @@ -1,4 +1,9 @@ #!/usr/bin/with-contenv ash +source homecentr_create_group +source homecentr_print_banner +source homecentr_print_context +source homecentr_set_s6_env_var + EXEC_GROUP="root" EXEC_USER="root" @@ -15,16 +20,6 @@ then exit 2 fi -### Verify required packages are available -if [ "$PGID" != "0" ] || [ "$PUID" != "0" ] -then - if ( ! type "addgroup" > /dev/null; ) || ( ! type "adduser" > /dev/null; ) - then - >&2 echo "Cannot create user/group, please install shadow package!" - exit 1 - fi -fi - ### Create primary group if [ "$PGID" -ne "0" ] then @@ -37,16 +32,8 @@ then deluser nonroot fi - # Check if the group exists already - cat /etc/group | grep ^nonroot: > /dev/null + homecentr_create_group "nonroot" "$PGID" - if [ $? == 0 ] - then - # Group already exists, delete it - delgroup nonroot - fi - - addgroup -g $PGID nonroot EXEC_GROUP="nonroot" fi @@ -61,6 +48,13 @@ fi ### Create groups if [[ ! -z $PUID_ADDITIONAL_GROUPS ]] then + echo "$PUID_ADDITIONAL_GROUPS" | egrep '^[0-9]+\:[a-zA-Z0-9_-]+(,[0-9]+\:[a-zA-Z0-9_-]+)*$' > /dev/null + + if [ "$?" != "0" ]; then + >&2 echo "The value $PUID_ADDITIONAL_GROUPS is invalid. Valid format is :,:" + exit 2 + fi + # Expecting string in format "gid:name,gid:name" GROUPS=$(echo $PUID_ADDITIONAL_GROUPS | tr "," "\n") @@ -69,27 +63,17 @@ then GRP_ID=$(echo $GROUP | cut -d ':' -f 1) GRP_NAME=$(echo $GROUP | cut -d ':' -f 2) - # Create group - addgroup -g "$GRP_ID" "$GRP_NAME" + # Create group (delete if already exists) + homecentr_create_group "$GRP_NAME" "$GRP_ID" # Add user to the group addgroup "$EXEC_USER" "$GRP_NAME" done fi -echo ' - __ __ __ - / / / /___ ____ ___ ___ ________ ____ / /______ - / /_/ / __ \/ __ `__ \/ _ \/ ___/ _ \/ __ \/ __/ ___/ - / __ / /_/ / / / / / / __/ /__/ __/ / / / /_/ / -/_/ /_/\____/_/ /_/ /_/\___/\___/\___/_/ /_/\__/_/ -' -echo " -------------------------------------- -User uid: $(id -u $EXEC_USER) -User gid: $(id -g $EXEC_GROUP) -------------------------------------- -" - -# Write the variable so that the runas script can use it -echo "$EXEC_USER" > /var/run/s6/container_environment/EXEC_USER \ No newline at end of file +homecentr_print_banner + +# Write the variable so that other scripts can use it +homecentr_set_s6_env_var "EXEC_USER" "$EXEC_USER" + +homecentr_print_context \ No newline at end of file diff --git a/alpine/fs/usr/sbin/homecentr_create_group b/alpine/fs/usr/sbin/homecentr_create_group new file mode 100644 index 0000000..1ba281f --- /dev/null +++ b/alpine/fs/usr/sbin/homecentr_create_group @@ -0,0 +1,17 @@ +#!/usr/bin/with-contenv ash + +homecentr_create_group() { + # $1 = Group name + # $2 = Group ID + + # Check if the group exists already + cat /etc/group | grep "^$1:" > /dev/null + + if [ $? == 0 ] + then + # Group already exists, delete it + delgroup "$1" + fi + + addgroup -g "$2" "$1" +} \ No newline at end of file diff --git a/alpine/fs/usr/sbin/homecentr_print_banner b/alpine/fs/usr/sbin/homecentr_print_banner new file mode 100644 index 0000000..09ec72f --- /dev/null +++ b/alpine/fs/usr/sbin/homecentr_print_banner @@ -0,0 +1,13 @@ +#!/usr/bin/with-contenv ash + +homecentr_print_banner() { + +echo ' + __ __ __ + / / / /___ ____ ___ ___ ________ ____ / /______ + / /_/ / __ \/ __ `__ \/ _ \/ ___/ _ \/ __ \/ __/ ___/ + / __ / /_/ / / / / / / __/ /__/ __/ / / / /_/ / +/_/ /_/\____/_/ /_/ /_/\___/\___/\___/_/ /_/\__/_/ +' + +} \ No newline at end of file diff --git a/alpine/fs/usr/sbin/homecentr_print_context b/alpine/fs/usr/sbin/homecentr_print_context new file mode 100644 index 0000000..82c516d --- /dev/null +++ b/alpine/fs/usr/sbin/homecentr_print_context @@ -0,0 +1,15 @@ +#!/usr/bin/with-contenv ash + +homecentr_print_context() { + +# The gids script filters out the primary group so it matches the structure of the env vars passed to the container + +echo " +------------------------------------- +User uid: $(id -u $EXEC_USER) +User gid: $(id -g $EXEC_USER) +User additional gids: $(id -G $EXEC_USER | tr ' ' '\n' | grep -v "^$(id -g $EXEC_USER)$" | tr '\n' ',' | sed 's/,*$//g') +------------------------------------- +" + +} \ No newline at end of file diff --git a/alpine/fs/usr/sbin/homecentr_set_s6_env_var b/alpine/fs/usr/sbin/homecentr_set_s6_env_var new file mode 100644 index 0000000..d80d875 --- /dev/null +++ b/alpine/fs/usr/sbin/homecentr_set_s6_env_var @@ -0,0 +1,8 @@ +#!/usr/bin/with-contenv ash + +homecentr_set_s6_env_var() { + # $1 = Variable name + # $2 = Variable value + + echo "$2" > "/var/run/s6/container_environment/$1" +} \ No newline at end of file diff --git a/tests/src/test/java/BaseRunningWithInvalidSecondaryGroupsAsNonRootShould.java b/tests/src/test/java/BaseRunningWithInvalidSecondaryGroupsAsNonRootShould.java new file mode 100644 index 0000000..a72144f --- /dev/null +++ b/tests/src/test/java/BaseRunningWithInvalidSecondaryGroupsAsNonRootShould.java @@ -0,0 +1,65 @@ +import helpers.BaseDockerImageTagResolver; +import helpers.Image; +import io.homecentr.testcontainers.containers.GenericContainerEx; +import io.homecentr.testcontainers.containers.wait.strategy.WaitEx; +import io.homecentr.testcontainers.images.PullPolicyEx; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.ContainerLaunchException; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; + +import java.nio.file.Paths; +import java.time.Duration; + +import static io.homecentr.testcontainers.WaitLoop.waitFor; + +public class BaseRunningWithInvalidSecondaryGroupsAsNonRootShould { + private static final Logger logger = LoggerFactory.getLogger(BaseRunningAsRootShould.class); + + private static GenericContainerEx _container; + + @BeforeClass + public static void before(){ + _container = new GenericContainerEx<>(new BaseDockerImageTagResolver()) + .withEnv("PUID", "7000") + .withEnv("PGID", "8000") + .withEnv("PUID_ADDITIONAL_GROUPS", "8005=grp1") // valid delimiter is : + .withRelativeFileSystemBind(Paths.get(Image.getExamplesDir(), "loop").toString(), "/usr/sbin/loop") + .withRelativeFileSystemBind(Paths.get(Image.getExamplesDir(), "run").toString(), "/etc/services.d/env-test/run") + .withImagePullPolicy(PullPolicyEx.never()) + .waitingFor(WaitEx.forS6OverlayStart()); + + try + { + _container.start(); + } + catch (ContainerLaunchException ex) { + // Expected + } + } + + @AfterClass + public static void after() { + _container.close(); + } + + @Test + public void printErrorMessage() throws Exception { + waitFor(Duration.ofSeconds(10), () -> _container.getLogsAnalyzer().matches(".*Valid format is :,:.*")); + } + + @Test + public void exitFromInitScript() throws Exception { + // This checks the container stopped on the init script and not because of other failure + waitFor(Duration.ofSeconds(10), () -> _container.getLogsAnalyzer().matches(".*\\[cont\\-init\\.d\\] 10-init\\.sh: exited.*")); + } + + @Test + public void exit() throws Exception { + waitFor(Duration.ofSeconds(5), () -> !_container.isRunning()); + } +} diff --git a/tests/src/test/java/BaseRunningWithInvalidSecondaryGroupsAsRootShould.java b/tests/src/test/java/BaseRunningWithInvalidSecondaryGroupsAsRootShould.java new file mode 100644 index 0000000..68d38ad --- /dev/null +++ b/tests/src/test/java/BaseRunningWithInvalidSecondaryGroupsAsRootShould.java @@ -0,0 +1,63 @@ +import helpers.BaseDockerImageTagResolver; +import helpers.Image; +import io.homecentr.testcontainers.containers.GenericContainerEx; +import io.homecentr.testcontainers.containers.wait.strategy.WaitEx; +import io.homecentr.testcontainers.images.PullPolicyEx; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.ContainerLaunchException; + +import java.nio.file.Paths; +import java.time.Duration; + +import static io.homecentr.testcontainers.WaitLoop.waitFor; + +public class BaseRunningWithInvalidSecondaryGroupsAsRootShould { + private static final Logger logger = LoggerFactory.getLogger(BaseRunningAsRootShould.class); + + private static GenericContainerEx _container; + + @BeforeClass + public static void before(){ + _container = new GenericContainerEx<>(new BaseDockerImageTagResolver()) + .withEnv("PUID", "0") + .withEnv("PGID", "0") + .withEnv("PUID_ADDITIONAL_GROUPS", "8005=grp1") // valid delimiter is : + .withRelativeFileSystemBind(Paths.get(Image.getExamplesDir(), "loop").toString(), "/usr/sbin/loop") + .withRelativeFileSystemBind(Paths.get(Image.getExamplesDir(), "run").toString(), "/etc/services.d/env-test/run") + .withImagePullPolicy(PullPolicyEx.never()) + .waitingFor(WaitEx.forS6OverlayStart()); + + try + { + _container.start(); + } + catch (ContainerLaunchException ex) { + // Expected + } + } + + @AfterClass + public static void after() { + _container.close(); + } + + @Test + public void printErrorMessage() throws Exception { + waitFor(Duration.ofSeconds(10), () -> _container.getLogsAnalyzer().matches(".*Valid format is :,:.*")); + } + + @Test + public void exitFromInitScript() throws Exception { + // This checks the container stopped on the init script and not because of other failure + waitFor(Duration.ofSeconds(10), () -> _container.getLogsAnalyzer().matches(".*\\[cont\\-init\\.d\\] 10-init\\.sh: exited.*")); + } + + @Test + public void exit() throws Exception { + waitFor(Duration.ofSeconds(5), () -> !_container.isRunning()); + } +} diff --git a/tests/src/test/java/BaseRunningWithSecondaryGroupsAsNonRootShould.java b/tests/src/test/java/BaseRunningWithSecondaryGroupsAsNonRootShould.java index 5d48d2e..a8979d1 100644 --- a/tests/src/test/java/BaseRunningWithSecondaryGroupsAsNonRootShould.java +++ b/tests/src/test/java/BaseRunningWithSecondaryGroupsAsNonRootShould.java @@ -57,4 +57,10 @@ public void loadGroupsToPuidUserContext() throws Exception { waitFor(Duration.ofSeconds(10), () -> _container.getLogsAnalyzer().matches(".*ID=.*8005\\(grp1\\).*")); waitFor(Duration.ofSeconds(10), () -> _container.getLogsAnalyzer().matches(".*ID=.*8006\\(grp2\\).*")); } + + @Test + public void printGroupsToOutput() throws Exception { + // Root is automatically member of other groups + waitFor(Duration.ofSeconds(10), () -> _container.getLogsAnalyzer().matches(".*User additional gids:\\W*8005,8006.*")); + } } diff --git a/tests/src/test/java/BaseRunningWithSecondaryGroupsAsRootShould.java b/tests/src/test/java/BaseRunningWithSecondaryGroupsAsRootShould.java index bf31350..d70bad3 100644 --- a/tests/src/test/java/BaseRunningWithSecondaryGroupsAsRootShould.java +++ b/tests/src/test/java/BaseRunningWithSecondaryGroupsAsRootShould.java @@ -57,4 +57,9 @@ public void loadGroupsToPuidUserContext() throws Exception { waitFor(Duration.ofSeconds(10), () -> _container.getLogsAnalyzer().matches(".*ID=.*8005\\(grp1\\).*")); waitFor(Duration.ofSeconds(10), () -> _container.getLogsAnalyzer().matches(".*ID=.*8006\\(grp2\\).*")); } + + @Test + public void printGroupsToOutput() throws Exception { + waitFor(Duration.ofSeconds(10), () -> _container.getLogsAnalyzer().matches(".*User additional gids:\\W*.*8005,8006.*")); + } } diff --git a/tests/src/test/java/helpers/Image.java b/tests/src/test/java/helpers/Image.java index 6f54ac7..d508de0 100644 --- a/tests/src/test/java/helpers/Image.java +++ b/tests/src/test/java/helpers/Image.java @@ -20,6 +20,7 @@ public static String getShell() { } private static String getBase() { - return System.getProperty("base"); + return "alpine"; + //return System.getProperty("base"); } }