Skip to content

Commit 68f8139

Browse files
committed
Check for filesystem permissions on startup
1 parent f88b46e commit 68f8139

5 files changed

Lines changed: 63 additions & 4 deletions

File tree

src/main/kotlin/eu/openanalytics/shinyproxyoperator/FileManager.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@
2121
package eu.openanalytics.shinyproxyoperator
2222

2323
import kotlinx.coroutines.Dispatchers
24+
import kotlinx.coroutines.IO
2425
import kotlinx.coroutines.withContext
2526
import java.nio.file.Files
2627
import java.nio.file.Path
2728
import java.nio.file.attribute.PosixFilePermission
2829
import kotlin.io.path.ExperimentalPathApi
2930
import kotlin.io.path.deleteRecursively
3031
import kotlin.io.path.exists
32+
import java.nio.file.AccessDeniedException
3133

3234
class FileManager {
3335

@@ -69,8 +71,12 @@ class FileManager {
6971
}
7072

7173
fun createDirectories(path: Path) {
72-
if (!Files.exists(path)) {
73-
Files.createDirectories(path)
74+
try {
75+
if (!Files.exists(path)) {
76+
Files.createDirectories(path)
77+
}
78+
} catch (_: AccessDeniedException) {
79+
throw InternalException("Missing read/write permission for directory '$path'!");
7480
}
7581
}
7682

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* ShinyProxy-Operator
3+
*
4+
* Copyright (C) 2021-2025 Open Analytics
5+
*
6+
* ===========================================================================
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the Apache License as published by
10+
* The Apache Software Foundation, either version 2 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* Apache License for more details.
17+
*
18+
* You should have received a copy of the Apache License
19+
* along with this program. If not, see <http://www.apache.org/licenses/>
20+
*/
21+
package eu.openanalytics.shinyproxyoperator
22+
23+
// exception for which no stack trace should be printed
24+
class InternalException(msg: String) : Exception(msg)

src/main/kotlin/eu/openanalytics/shinyproxyoperator/impl/docker/DockerOrchestrator.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import eu.openanalytics.shinyproxyoperator.Config
3131
import eu.openanalytics.shinyproxyoperator.FileManager
3232
import eu.openanalytics.shinyproxyoperator.IOrchestrator
3333
import eu.openanalytics.shinyproxyoperator.IShinyProxySource
34+
import eu.openanalytics.shinyproxyoperator.InternalException
3435
import eu.openanalytics.shinyproxyoperator.LabelFactory
3536
import eu.openanalytics.shinyproxyoperator.event.ShinyProxyEvent
3637
import eu.openanalytics.shinyproxyoperator.impl.docker.monitoring.MonitoringConfig
@@ -48,6 +49,7 @@ import kotlinx.coroutines.withContext
4849
import org.apache.commons.lang3.RandomStringUtils
4950
import org.mandas.docker.client.DockerClient
5051
import org.mandas.docker.client.builder.jersey.JerseyDockerClientBuilder
52+
import org.mandas.docker.client.exceptions.DockerException
5153
import org.mandas.docker.client.messages.ContainerConfig
5254
import org.mandas.docker.client.messages.HostConfig
5355
import org.mandas.docker.client.messages.LogConfig
@@ -93,13 +95,32 @@ class DockerOrchestrator(channel: Channel<ShinyProxyEvent>,
9395
private val logFilesCleaner: LogFilesCleaner
9496

9597
init {
98+
if (!Files.exists(dataDir)) {
99+
throw InternalException("The data directory doesn't exist: '$dataDir'!")
100+
}
101+
if (!Files.isReadable(dataDir)) {
102+
throw InternalException("Missing read permission for the data directory: '$dataDir'!");
103+
}
104+
if (!Files.isWritable(dataDir)) {
105+
throw InternalException("Missing write permission for the data directory: '$dataDir'!");
106+
}
96107
objectMapper.registerKotlinModule()
97108
objectMapper.propertyNamingStrategy = PropertyNamingStrategies.KEBAB_CASE
98109
dockerClient = JerseyDockerClientBuilder()
99110
.fromEnv()
100111
.uri("unix://" + dockerSocket)
101112
.readTimeoutMillis(0) // no timeout, needed for startContainer and logs, #32606
102113
.build()
114+
// test Docker permissions
115+
try {
116+
dockerClient.listImages()
117+
} catch (e: DockerException) {
118+
if (e.message?.contains("permission denied", ignoreCase = true) == true) {
119+
throw InternalException("No permission to access Docker daemon, check the mount of the socket and the 'SPO_DOCKER_GID' environment variable.");
120+
}
121+
throw e
122+
}
123+
103124
dataDirUid = getDatadirUId()
104125
caddyConfig = CaddyConfig(dockerClient, dataDir, config)
105126
dockerActions = DockerActions(dockerClient, config)
@@ -525,7 +546,7 @@ class DockerOrchestrator(channel: Channel<ShinyProxyEvent>,
525546
logger.info { "Owner of data dir is '$owner'" }
526547
return owner
527548
} catch (e: Exception) {
528-
logger.warn(e) { "Failed to determine owner of data dir - failling back to user 1000" }
549+
logger.warn(e) { "Failed to determine owner of data dir - falling back to user 1000" }
529550
return 1000
530551
}
531552
}

src/main/kotlin/eu/openanalytics/shinyproxyoperator/impl/source/FileSource.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import com.fasterxml.jackson.module.kotlin.readValue
2828
import eu.openanalytics.shinyproxyoperator.Config
2929
import eu.openanalytics.shinyproxyoperator.IEventController
3030
import eu.openanalytics.shinyproxyoperator.IShinyProxySource
31+
import eu.openanalytics.shinyproxyoperator.InternalException
3132
import eu.openanalytics.shinyproxyoperator.event.ShinyProxyEvent
3233
import eu.openanalytics.shinyproxyoperator.event.ShinyProxyEventType
3334
import eu.openanalytics.shinyproxyoperator.impl.docker.DockerOrchestrator
@@ -39,6 +40,7 @@ import kotlinx.coroutines.runBlocking
3940
import org.apache.commons.io.FileUtils
4041
import org.apache.commons.io.filefilter.DirectoryFileFilter
4142
import org.apache.commons.io.filefilter.RegexFileFilter
43+
import java.nio.file.Files
4244
import java.nio.file.Path
4345
import java.util.*
4446
import kotlin.concurrent.timer
@@ -63,6 +65,9 @@ class FileSource(
6365

6466
override suspend fun init() {
6567
objectMapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY)
68+
if (!Files.isReadable(inputDir)) {
69+
throw InternalException("Missing read permission for the input '$inputDir' directory!");
70+
}
6671
runOnce()
6772
logger.info { "FileSource ready" }
6873
}

src/main/kotlin/eu/openanalytics/shinyproxyoperator/main.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,11 @@ suspend fun main() {
5454
logger.info { "Starting ShinyProxy Operator" }
5555
operator.run()
5656
}
57+
} catch (exception: InternalException) {
58+
logger.warn { "Exception: ${exception.message}" }
59+
exitProcess(1)
5760
} catch (exception: Exception) {
58-
logger.warn { "Exception : ${exception.message}" }
61+
logger.warn { "Exception: ${exception.message}" }
5962
exception.printStackTrace()
6063
exitProcess(1)
6164
}

0 commit comments

Comments
 (0)