Skip to content

Commit a2af702

Browse files
SNOW-1660409 JDBC Connector - GET file from stage weak file permissions (#2066)
1 parent 6d5d616 commit a2af702

File tree

6 files changed

+102
-4
lines changed

6 files changed

+102
-4
lines changed

src/main/java/net/snowflake/client/jdbc/SnowflakeFileTransferAgent.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package net.snowflake.client.jdbc;
66

77
import static net.snowflake.client.core.Constants.NO_SPACE_LEFT_ON_DEVICE_ERR;
8+
import static net.snowflake.client.jdbc.SnowflakeUtil.createOwnerOnlyPermissionDir;
89
import static net.snowflake.client.jdbc.SnowflakeUtil.systemGetProperty;
910

1011
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -1565,8 +1566,10 @@ public boolean execute() throws SQLException {
15651566
if (commandType == CommandType.DOWNLOAD) {
15661567
File dir = new File(localLocation);
15671568
if (!dir.exists()) {
1568-
boolean created = dir.mkdirs();
1569-
1569+
boolean created =
1570+
SnowflakeUtil.isWindows()
1571+
? dir.mkdirs()
1572+
: createOwnerOnlyPermissionDir(localLocation);
15701573
if (created) {
15711574
logger.debug("Directory created: {}", localLocation);
15721575
} else {
@@ -2507,7 +2510,7 @@ private static void pullFileFromRemoteStore(
25072510
RemoteStoreFileEncryptionMaterial encMat,
25082511
String presignedUrl,
25092512
String queryId)
2510-
throws SQLException {
2513+
throws SQLException, IOException {
25112514
remoteLocation remoteLocation = extractLocationAndPath(stage.getLocation());
25122515

25132516
String stageFilePath = filePath;

src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java

+54
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,18 @@
1313
import com.fasterxml.jackson.databind.node.ArrayNode;
1414
import com.google.common.base.Strings;
1515
import java.io.BufferedReader;
16+
import java.io.File;
1617
import java.io.IOException;
1718
import java.io.InputStreamReader;
1819
import java.io.PrintWriter;
1920
import java.io.StringWriter;
2021
import java.lang.reflect.Field;
2122
import java.lang.reflect.Method;
23+
import java.nio.file.Files;
24+
import java.nio.file.Path;
25+
import java.nio.file.Paths;
26+
import java.nio.file.attribute.PosixFilePermission;
27+
import java.nio.file.attribute.PosixFilePermissions;
2228
import java.sql.SQLException;
2329
import java.sql.Time;
2430
import java.sql.Types;
@@ -33,6 +39,7 @@
3339
import java.util.Optional;
3440
import java.util.Properties;
3541
import java.util.Random;
42+
import java.util.Set;
3643
import java.util.TreeMap;
3744
import java.util.concurrent.Executors;
3845
import java.util.concurrent.ThreadFactory;
@@ -66,6 +73,9 @@ public class SnowflakeUtil {
6673
private static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakeUtil.class);
6774
private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper();
6875

76+
private static final Set<PosixFilePermission> ownerOnlyPermission =
77+
PosixFilePermissions.fromString("rw-------");
78+
6979
/** Additional data types not covered by standard JDBC */
7080
public static final int EXTRA_TYPES_TIMESTAMP_LTZ = 50000;
7181

@@ -902,6 +912,50 @@ public static Map<String, String> createCaseInsensitiveMap(Header[] headers) {
902912
}
903913
}
904914

915+
/** create a directory with Owner only permission (0600) */
916+
@SnowflakeJdbcInternalApi
917+
public static boolean createOwnerOnlyPermissionDir(String location) {
918+
if (isWindows()) {
919+
return false;
920+
}
921+
922+
boolean isDirCreated = true;
923+
Path dir = Paths.get(location);
924+
try {
925+
Files.createDirectory(dir, PosixFilePermissions.asFileAttribute(ownerOnlyPermission));
926+
} catch (IOException e) {
927+
logger.error(
928+
"Failed to set OwnerOnly permission for {}. This may cause the file download to fail ",
929+
location);
930+
isDirCreated = false;
931+
}
932+
return isDirCreated;
933+
}
934+
935+
@SnowflakeJdbcInternalApi
936+
public static void assureOnlyUserAccessibleFilePermissions(File file) throws IOException {
937+
if (isWindows()) {
938+
return;
939+
}
940+
boolean disableUserPermissions =
941+
file.setReadable(false, false)
942+
&& file.setWritable(false, false)
943+
&& file.setExecutable(false, false);
944+
boolean setOwnerPermissionsOnly = file.setReadable(true, true) && file.setWritable(true, true);
945+
946+
if (disableUserPermissions && setOwnerPermissionsOnly) {
947+
logger.info("Successfuly set OwnerOnly permission for {}. ", file.getAbsolutePath());
948+
} else {
949+
file.delete();
950+
logger.error(
951+
"Failed to set OwnerOnly permission for {}. Failed to download", file.getAbsolutePath());
952+
throw new IOException(
953+
String.format(
954+
"Failed to set OwnerOnly permission for %s. Failed to download",
955+
file.getAbsolutePath()));
956+
}
957+
}
958+
905959
/**
906960
* Check whether the OS is Windows
907961
*

src/main/java/net/snowflake/client/jdbc/cloud/storage/SnowflakeAzureClient.java

+1
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ public void download(
343343
transferOptions.setConcurrentRequestCount(parallelism);
344344

345345
blob.downloadToFile(localFilePath, null, transferOptions, opContext);
346+
SnowflakeUtil.assureOnlyUserAccessibleFilePermissions(localFile);
346347
stopwatch.stop();
347348
long downloadMillis = stopwatch.elapsedMillis();
348349

src/main/java/net/snowflake/client/jdbc/cloud/storage/SnowflakeGCSClient.java

+2
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ public void download(
314314
outStream.flush();
315315
outStream.close();
316316
bodyStream.close();
317+
SnowflakeUtil.assureOnlyUserAccessibleFilePermissions(localFile);
317318
if (isEncrypting()) {
318319
Map<String, String> userDefinedHeaders =
319320
createCaseInsensitiveMap(response.getAllHeaders());
@@ -351,6 +352,7 @@ public void download(
351352
logger.debug("Starting download without presigned URL", false);
352353
blob.downloadTo(
353354
localFile.toPath(), Blob.BlobSourceOption.shouldReturnRawInputStream(true));
355+
SnowflakeUtil.assureOnlyUserAccessibleFilePermissions(localFile);
354356
stopwatch.stop();
355357
downloadMillis = stopwatch.elapsedMillis();
356358
logger.debug("Download successful", false);

src/main/java/net/snowflake/client/jdbc/cloud/storage/SnowflakeS3Client.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ public ExecutorService newExecutor() {
387387
String iv = metaMap.get(AMZ_IV);
388388

389389
myDownload.waitForCompletion();
390-
390+
SnowflakeUtil.assureOnlyUserAccessibleFilePermissions(localFile);
391391
stopwatch.stop();
392392
long downloadMillis = stopwatch.elapsedMillis();
393393

src/test/java/net/snowflake/client/jdbc/SnowflakeUtilTest.java

+38
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package net.snowflake.client.jdbc;
55

66
import static net.snowflake.client.jdbc.SnowflakeUtil.createCaseInsensitiveMap;
7+
import static net.snowflake.client.jdbc.SnowflakeUtil.createOwnerOnlyPermissionDir;
78
import static net.snowflake.client.jdbc.SnowflakeUtil.extractColumnMetadata;
89
import static net.snowflake.client.jdbc.SnowflakeUtil.getSnowflakeType;
910
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -15,18 +16,28 @@
1516
import com.fasterxml.jackson.databind.ObjectMapper;
1617
import com.fasterxml.jackson.databind.node.ArrayNode;
1718
import com.fasterxml.jackson.databind.node.ObjectNode;
19+
import java.io.File;
20+
import java.io.IOException;
21+
import java.nio.file.Files;
22+
import java.nio.file.Path;
23+
import java.nio.file.attribute.PosixFileAttributes;
24+
import java.nio.file.attribute.PosixFilePermission;
25+
import java.nio.file.attribute.PosixFilePermissions;
1826
import java.sql.Types;
1927
import java.util.ArrayList;
2028
import java.util.Arrays;
2129
import java.util.HashMap;
2230
import java.util.Map;
31+
import java.util.Set;
2332
import java.util.TreeMap;
33+
import net.snowflake.client.annotations.DontRunOnWindows;
2434
import net.snowflake.client.category.TestTags;
2535
import net.snowflake.client.core.ObjectMapperFactory;
2636
import org.apache.http.Header;
2737
import org.apache.http.message.BasicHeader;
2838
import org.junit.jupiter.api.Tag;
2939
import org.junit.jupiter.api.Test;
40+
import org.junit.jupiter.api.io.TempDir;
3041

3142
@Tag(TestTags.CORE)
3243
public class SnowflakeUtilTest extends BaseJDBCTest {
@@ -117,6 +128,33 @@ public void shouldConvertHeadersCreateCaseInsensitiveMap() {
117128
assertEquals("value2", map.get("Key2"));
118129
}
119130

131+
@Test
132+
@DontRunOnWindows
133+
public void testCreateOwnerOnlyPermissionDir(@TempDir Path tempDir)
134+
throws IOException, UnsupportedOperationException {
135+
Path tmp = tempDir.resolve("folder-permission-testing");
136+
String folderPath = tmp.toString();
137+
boolean isDirCreated = createOwnerOnlyPermissionDir(folderPath);
138+
assertTrue(isDirCreated);
139+
assertTrue(tmp.toFile().isDirectory());
140+
PosixFileAttributes attributes = Files.readAttributes(tmp, PosixFileAttributes.class);
141+
Set<PosixFilePermission> permissions = attributes.permissions();
142+
assertEquals(PosixFilePermissions.toString(permissions), "rw-------");
143+
}
144+
145+
@Test
146+
@DontRunOnWindows
147+
public void testValidateFilePermission(@TempDir Path tempDir)
148+
throws IOException, UnsupportedOperationException {
149+
Path tmp = tempDir.resolve("fileTesting.txt");
150+
File file = tmp.toFile();
151+
assertTrue(file.createNewFile());
152+
SnowflakeUtil.assureOnlyUserAccessibleFilePermissions(file);
153+
PosixFileAttributes attributes = Files.readAttributes(tmp, PosixFileAttributes.class);
154+
Set<PosixFilePermission> permissions = attributes.permissions();
155+
assertEquals(PosixFilePermissions.toString(permissions), "rw-------");
156+
}
157+
120158
private static SnowflakeColumnMetadata createExpectedMetadata(
121159
JsonNode rootNode, JsonNode fieldOne, JsonNode fieldTwo) throws SnowflakeSQLLoggedException {
122160
ColumnTypeInfo columnTypeInfo =

0 commit comments

Comments
 (0)