Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ private static List<File> upzipFile(File zipFile, String descDir, UnzipContext c
}

private static File resolveZipEntryFile(File baseDir, String basePath, String entryName) throws IOException {
validateZipEntryName(entryName);
File targetFile = new File(baseDir, entryName);
String targetPath = targetFile.getCanonicalPath();
if (!targetPath.equals(basePath) && !targetPath.startsWith(basePath + File.separator)) {
Expand All @@ -309,6 +310,15 @@ private static File resolveZipEntryFile(File baseDir, String basePath, String en
return targetFile;
}

private static void validateZipEntryName(String entryName) throws IOException {
if (entryName == null
|| entryName.startsWith("/")
|| entryName.startsWith("\\")
|| new File(entryName).isAbsolute()) {
throw new IOException(String.format("zip entry is outside of target dir: %s", entryName));
}
}

private static String getCanonicalDirPath(File dir) throws IOException {
makeDirs(dir);
return dir.getCanonicalPath();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,23 @@ public void testRejectZipSlipEntry() throws Exception {
Assert.assertFalse(escapedFile.exists());
}

@Test
public void testRejectAbsolutePathZipEntry() throws Exception {
File targetDir = temporaryFolder.newFolder("absolute");
File escapedFile = temporaryFolder.newFile("absolute-evil.txt");
Files.delete(escapedFile.toPath());
File zipFile = temporaryFolder.newFile("absolute.zip");
writeApacheZip(zipFile, escapedFile.getAbsolutePath(), "evil");

try {
ZipUtil.upzipFile(zipFile, targetDir.getAbsolutePath());
Assert.fail("Absolute path zip entry should be rejected");
} catch (TaierDefineException e) {
Assert.assertTrue(e.getMessage().contains("outside of target dir"));
}
Assert.assertFalse(escapedFile.exists());
}

@Test
public void testRejectTooManyEntries() throws Exception {
File zipFile = temporaryFolder.newFile("too-many.zip");
Expand Down Expand Up @@ -129,6 +146,15 @@ private static void writeZip(File zipFile, ZipItem... items) throws IOException
}
}

private static void writeApacheZip(File zipFile, String entryName, String content) throws IOException {
try (org.apache.tools.zip.ZipOutputStream zipOutputStream =
new org.apache.tools.zip.ZipOutputStream(new FileOutputStream(zipFile))) {
zipOutputStream.putNextEntry(new org.apache.tools.zip.ZipEntry(entryName));
zipOutputStream.write(content.getBytes(StandardCharsets.UTF_8));
zipOutputStream.closeEntry();
}
}

private static class ZipItem {
private final String name;
private final String content;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@
import com.dtstack.taier.scheduler.service.ComponentService;
import com.dtstack.taier.scheduler.vo.ComponentVO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
Expand All @@ -65,6 +67,7 @@ public class ConsoleClusterService {
private ComponentService componentService;

public Long addCluster(String clusterName) {
checkClusterName(clusterName);
if (clusterMapper.getByClusterName(clusterName) != null) {
throw new TaierDefineException(ErrorCode.NAME_ALREADY_EXIST.getDescription());
}
Expand All @@ -74,6 +77,17 @@ public Long addCluster(String clusterName) {
return cluster.getId();
}

private void checkClusterName(String clusterName) {
if (StringUtils.isBlank(clusterName)
|| clusterName.contains("/")
|| clusterName.contains("\\")
|| clusterName.contains("..")
|| clusterName.indexOf('\0') >= 0
|| new File(clusterName).isAbsolute()) {
throw new TaierDefineException("Invalid cluster name");
}
}

public IPage<Cluster> pageQuery(int currentPage, int pageSize) {
Page<Cluster> page = new Page<>(currentPage, pageSize);
return clusterMapper.selectPage(page, Wrappers.lambdaQuery(Cluster.class).eq(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ public class ConsoleComponentService {

private static final Logger LOGGER = LoggerFactory.getLogger(ComponentService.class);

private static final String LOCAL_KERBEROS_CLUSTER_DIR_PREFIX = "CLUSTER_";

@Autowired
private ComponentMapper componentMapper;

Expand Down Expand Up @@ -491,7 +493,7 @@ private String updateComponentKerberosFile(Long clusterId, Component addComponen
//删除本地文件夹
String kerberosPath = this.getLocalKerberosPath(clusterId, addComponent.getComponentTypeCode());
try {
FileUtils.deleteDirectory(new File(kerberosPath));
deleteLocalKerberosDirectory(kerberosPath);
} catch (IOException e) {
LOGGER.error("delete old kerberos directory {} error", kerberosPath, e);
}
Expand Down Expand Up @@ -614,7 +616,22 @@ public String getLocalKerberosPath(Long clusterId, Integer componentCode) {
if (null == one) {
throw new TaierDefineException(ErrorCode.CANT_NOT_FIND_CLUSTER);
}
return env.getTempDir() + File.separator + one.getClusterName() + File.separator + EComponentType.getByCode(componentCode).name() + File.separator + KERBEROS;
if (StringUtils.isBlank(env.getTempDir())) {
throw new TaierDefineException("Temp dir cannot be empty");
}
return env.getTempDir() + File.separator + LOCAL_KERBEROS_CLUSTER_DIR_PREFIX + clusterId
+ File.separator + EComponentType.getByCode(componentCode).name() + File.separator + KERBEROS;
}

private void deleteLocalKerberosDirectory(String kerberosPath) throws IOException {
File tempDir = new File(env.getTempDir()).getCanonicalFile();
File kerberosDir = new File(kerberosPath).getCanonicalFile();
String tempDirPath = tempDir.getPath();
String kerberosDirPath = kerberosDir.getPath();
if (!kerberosDirPath.startsWith(tempDirPath + File.separator)) {
throw new TaierDefineException("Invalid kerberos directory");
}
FileUtils.deleteDirectory(kerberosDir);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.dtstack.taier.common.exception.ErrorCode;
import com.dtstack.taier.common.exception.TaierDefineException;
import com.dtstack.taier.develop.dto.user.DTToken;
import org.joda.time.DateTime;
Expand Down Expand Up @@ -82,6 +84,11 @@ public DTToken decryption(String tokenText) {
log.error("JWT Token expire.", e);
}
throw new TaierDefineException("DT Token已过期");
} catch (JWTVerificationException | IllegalArgumentException e) {
if (log.isErrorEnabled()) {
log.error("JWT Token invalid.", e);
}
throw new TaierDefineException(ErrorCode.TOKEN_IS_INVALID);
}
}

Expand All @@ -100,6 +107,8 @@ public DTToken decryptionWithOutExpire(String tokenText) {
return token;
} catch (UnsupportedEncodingException e) {
throw new TaierDefineException("DT Token解码异常.");
} catch (JWTVerificationException | IllegalArgumentException e) {
throw new TaierDefineException(ErrorCode.TOKEN_IS_INVALID);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/

package com.dtstack.taier.develop.controller.console;

import com.dtstack.taier.common.exception.TaierDefineException;
import com.dtstack.taier.dao.dto.Resource;
import com.google.common.collect.Lists;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.List;

public class UploadControllerTest {

@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();

@Test
public void testGetResourcesFromFilesSaveFileUnderUploadDir() throws Exception {
File uploadDir = temporaryFolder.newFolder("file-uploads");
ReflectionTestUtils.setField(UploadController.class, "uploadsDir", uploadDir.getAbsolutePath());
UploadController uploadController = new UploadController();
MultipartFile multipartFile = new MockMultipartFile("fileName", "config.json",
"application/json", "{\"k\":\"v\"}".getBytes(StandardCharsets.UTF_8));

List<Resource> resources = ReflectionTestUtils.invokeMethod(uploadController,
"getResourcesFromFiles", Lists.newArrayList(multipartFile));

Assert.assertNotNull(resources);
Assert.assertEquals(1, resources.size());
Resource resource = resources.get(0);
Assert.assertEquals("config.json", resource.getFileName());
Assert.assertEquals("fileName", resource.getKey());
File savedFile = new File(resource.getUploadedFileName());
Assert.assertTrue(savedFile.exists());
Assert.assertEquals(uploadDir.getCanonicalPath(), savedFile.getParentFile().getCanonicalPath());
Assert.assertEquals("{\"k\":\"v\"}", new String(Files.readAllBytes(savedFile.toPath()), StandardCharsets.UTF_8));
}

@Test(expected = TaierDefineException.class)
public void testGetResourcesFromFilesRejectPathTraversalFileName() throws Exception {
File uploadDir = temporaryFolder.newFolder("file-uploads");
ReflectionTestUtils.setField(UploadController.class, "uploadsDir", uploadDir.getAbsolutePath());
UploadController uploadController = new UploadController();
MultipartFile multipartFile = new MockMultipartFile("fileName", "../../../../tmp/x.txt",
"application/octet-stream", "x".getBytes(StandardCharsets.UTF_8));

ReflectionTestUtils.invokeMethod(uploadController,
"getResourcesFromFiles", Lists.newArrayList(multipartFile));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/

package com.dtstack.taier.develop.service.console;

import com.dtstack.taier.common.exception.TaierDefineException;
import com.dtstack.taier.dao.domain.Cluster;
import com.dtstack.taier.dao.mapper.ClusterMapper;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.test.util.ReflectionTestUtils;

import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class ConsoleClusterServiceTest {

private ConsoleClusterService consoleClusterService;

private ClusterMapper clusterMapper;

@Before
public void setUp() {
consoleClusterService = new ConsoleClusterService();
clusterMapper = mock(ClusterMapper.class);
ReflectionTestUtils.setField(consoleClusterService, "clusterMapper", clusterMapper);
}

@Test
public void testAddCluster() {
when(clusterMapper.getByClusterName("cluster_a")).thenReturn(null);
doAnswer(invocation -> {
Cluster cluster = invocation.getArgumentAt(0, Cluster.class);
cluster.setId(1L);
return 1;
}).when(clusterMapper).insert(any(Cluster.class));

Long clusterId = consoleClusterService.addCluster("cluster_a");

Assert.assertEquals(Long.valueOf(1L), clusterId);
verify(clusterMapper).insert(any(Cluster.class));
}

@Test(expected = TaierDefineException.class)
public void testAddClusterRejectPathTraversalName() {
try {
consoleClusterService.addCluster("../../../../tmp/x");
} finally {
verify(clusterMapper, never()).getByClusterName(any(String.class));
verify(clusterMapper, never()).insert(any(Cluster.class));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/

package com.dtstack.taier.develop.service.console;

import com.dtstack.taier.common.enums.EComponentType;
import com.dtstack.taier.common.env.EnvironmentContext;
import com.dtstack.taier.dao.domain.Cluster;
import com.dtstack.taier.dao.mapper.ClusterMapper;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.test.util.ReflectionTestUtils;

import java.io.File;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class ConsoleComponentServiceTest {

@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();

@Test
public void testGetLocalKerberosPathUseClusterIdInsteadOfClusterName() throws Exception {
File tempDir = temporaryFolder.newFolder("temp");
Long clusterId = 12L;
Cluster cluster = new Cluster();
cluster.setId(clusterId);
cluster.setClusterName("../../../../tmp/x");

ClusterMapper clusterMapper = mock(ClusterMapper.class);
when(clusterMapper.getOne(clusterId)).thenReturn(cluster);
EnvironmentContext environmentContext = mock(EnvironmentContext.class);
when(environmentContext.getTempDir()).thenReturn(tempDir.getAbsolutePath());

ConsoleComponentService consoleComponentService = new ConsoleComponentService();
ReflectionTestUtils.setField(consoleComponentService, "clusterMapper", clusterMapper);
ReflectionTestUtils.setField(consoleComponentService, "env", environmentContext);

String localKerberosPath = consoleComponentService.getLocalKerberosPath(clusterId, EComponentType.HDFS.getTypeCode());
File localKerberosDir = new File(localKerberosPath);

Assert.assertEquals(new File(tempDir, "CLUSTER_12" + File.separator + "HDFS" + File.separator + "kerberos").getPath(),
localKerberosPath);
Assert.assertTrue(localKerberosDir.getCanonicalPath().startsWith(tempDir.getCanonicalPath() + File.separator));
Assert.assertFalse(localKerberosPath.contains(".."));
Assert.assertFalse(localKerberosPath.contains("tmp/x"));
}
}
Loading
Loading