mirror of https://github.com/apache/lucene.git
LUCENE-6542: FSDirectory's ctor now works with security policies or file systems that restrict write access
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1688537 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
b6e28103ec
commit
e50f72e073
|
@ -232,6 +232,9 @@ Changes in Runtime Behavior
|
|||
API, however the returned bits may be called on different documents compared
|
||||
to before. (Adrien Grand)
|
||||
|
||||
* LUCENE-6542: FSDirectory's ctor now works with security policies or file systems
|
||||
that restrict write access. (Trejkaz, hossman, Uwe Schindler)
|
||||
|
||||
Optimizations
|
||||
|
||||
* LUCENE-6548: Some optimizations for BlockTree's intersect with very
|
||||
|
@ -264,6 +267,11 @@ Test Framework
|
|||
* LUCENE-6637: Fix FSTTester to not violate file permissions on
|
||||
-Dtests.verbose=true. (Mesbah M. Alam, Uwe Schindler)
|
||||
|
||||
* LUCENE-6542: LuceneTestCase now has runWithRestrictedPermissions() to run
|
||||
an action with reduced permissions. This can be used to simulate special
|
||||
environments (e.g., read-only dirs). If tests are running without a security
|
||||
manager, an assume cancels test execution automatically. (Uwe Schindler)
|
||||
|
||||
Changes in Backwards Compatibility Policy
|
||||
|
||||
* LUCENE-6553: The iterator returned by the LeafReader.postings method now
|
||||
|
|
|
@ -125,7 +125,10 @@ public abstract class FSDirectory extends BaseDirectory {
|
|||
*/
|
||||
protected FSDirectory(Path path, LockFactory lockFactory) throws IOException {
|
||||
super(lockFactory);
|
||||
Files.createDirectories(path); // create directory, if it doesn't exist
|
||||
// If only read access is permitted, createDirectories fails even if the directory already exists.
|
||||
if (!Files.isDirectory(path)) {
|
||||
Files.createDirectories(path); // create directory, if it doesn't exist
|
||||
}
|
||||
directory = path.toRealPath();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
package org.apache.lucene.index;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import java.io.FilePermission;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.PropertyPermission;
|
||||
|
||||
import org.apache.lucene.analysis.Analyzer;
|
||||
import org.apache.lucene.analysis.MockAnalyzer;
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.PhraseQuery;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.TermQuery;
|
||||
import org.apache.lucene.search.TopDocs;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.store.FSDirectory;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
public class TestReadOnlyIndex extends LuceneTestCase {
|
||||
|
||||
private static final String longTerm = "longtermlongtermlongtermlongtermlongtermlongtermlongtermlongtermlongtermlongtermlongtermlongtermlongtermlongtermlongtermlongtermlongtermlongterm";
|
||||
private static final String text = "This is the text to be indexed. " + longTerm;
|
||||
|
||||
private static Path indexPath;
|
||||
|
||||
@BeforeClass
|
||||
public static void buildIndex() throws Exception {
|
||||
indexPath = Files.createTempDirectory("readonlyindex");
|
||||
|
||||
// borrows from TestDemo, but not important to keep in sync with demo
|
||||
Analyzer analyzer = new MockAnalyzer(random());
|
||||
Directory directory = newFSDirectory(indexPath);
|
||||
RandomIndexWriter iwriter = new RandomIndexWriter(random(), directory, analyzer);
|
||||
Document doc = new Document();
|
||||
doc.add(newTextField("fieldname", text, Field.Store.YES));
|
||||
iwriter.addDocument(doc);
|
||||
iwriter.close();
|
||||
directory.close();
|
||||
analyzer.close();
|
||||
}
|
||||
|
||||
public void testReadOnlyIndex() throws Exception {
|
||||
runWithRestrictedPermissions(this::doTestReadOnlyIndex,
|
||||
// add some basic permissions (because we are limited already - so we grant all important ones):
|
||||
new RuntimePermission("*"),
|
||||
new PropertyPermission("*", "read"),
|
||||
// only allow read to the given index dir, nothing else:
|
||||
new FilePermission(indexPath.toString(), "read"),
|
||||
new FilePermission(indexPath.resolve("-").toString(), "read")
|
||||
);
|
||||
}
|
||||
|
||||
private Void doTestReadOnlyIndex() throws Exception {
|
||||
Directory dir = FSDirectory.open(indexPath);
|
||||
IndexReader ireader = DirectoryReader.open(dir);
|
||||
IndexSearcher isearcher = newSearcher(ireader);
|
||||
|
||||
// borrows from TestDemo, but not important to keep in sync with demo
|
||||
|
||||
assertEquals(1, isearcher.search(new TermQuery(new Term("fieldname", longTerm)), 1).totalHits);
|
||||
Query query = new TermQuery(new Term("fieldname", "text"));
|
||||
TopDocs hits = isearcher.search(query, 1);
|
||||
assertEquals(1, hits.totalHits);
|
||||
// Iterate through the results:
|
||||
for (int i = 0; i < hits.scoreDocs.length; i++) {
|
||||
StoredDocument hitDoc = isearcher.doc(hits.scoreDocs[i].doc);
|
||||
assertEquals(text, hitDoc.get("fieldname"));
|
||||
}
|
||||
|
||||
// Test simple phrase query
|
||||
PhraseQuery phraseQuery = new PhraseQuery("fieldname", "to", "be");
|
||||
assertEquals(1, isearcher.search(phraseQuery, 1).totalHits);
|
||||
|
||||
ireader.close();
|
||||
return null; // void
|
||||
}
|
||||
|
||||
}
|
|
@ -34,6 +34,14 @@ import java.lang.reflect.Method;
|
|||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.AccessControlContext;
|
||||
import java.security.AccessController;
|
||||
import java.security.Permission;
|
||||
import java.security.PermissionCollection;
|
||||
import java.security.Permissions;
|
||||
import java.security.PrivilegedActionException;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
@ -101,6 +109,7 @@ import org.junit.Test;
|
|||
import org.junit.rules.RuleChain;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import com.carrotsearch.randomizedtesting.JUnit4MethodProvider;
|
||||
import com.carrotsearch.randomizedtesting.LifecycleScope;
|
||||
import com.carrotsearch.randomizedtesting.MixWithSuiteName;
|
||||
|
@ -2605,6 +2614,25 @@ public abstract class LuceneTestCase extends Assert {
|
|||
return createTempFile("tempFile", ".tmp");
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a code part with restricted permissions (be sure to add all required permissions,
|
||||
* because it would start with empty permissions). You cannot grant more permissions than
|
||||
* our policy file allows, but you may restrict writing to several dirs...
|
||||
* <p><em>Note:</em> This assumes a {@link SecurityManager} enabled, otherwise it
|
||||
* stops test execution.
|
||||
*/
|
||||
public static <T> T runWithRestrictedPermissions(PrivilegedExceptionAction<T> action, Permission... permissions) throws Exception {
|
||||
assumeTrue("runWithRestrictedPermissions requires a SecurityManager enabled", System.getSecurityManager() != null);
|
||||
final PermissionCollection perms = new Permissions();
|
||||
Arrays.stream(permissions).forEach(perms::add);
|
||||
final AccessControlContext ctx = new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) });
|
||||
try {
|
||||
return AccessController.doPrivileged(action, ctx);
|
||||
} catch (PrivilegedActionException e) {
|
||||
throw e.getException();
|
||||
}
|
||||
}
|
||||
|
||||
/** True if assertions (-ea) are enabled (at least for this class). */
|
||||
public static final boolean assertsAreEnabled;
|
||||
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
package org.apache.lucene.util;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.AllPermission;
|
||||
|
||||
public class TestRunWithRestrictedPermissions extends LuceneTestCase {
|
||||
|
||||
public void testDefaultsPass() throws Exception {
|
||||
runWithRestrictedPermissions(this::doSomeForbiddenStuff, new AllPermission());
|
||||
}
|
||||
|
||||
public void testNormallyAllowedStuff() throws Exception {
|
||||
try {
|
||||
runWithRestrictedPermissions(this::doSomeForbiddenStuff);
|
||||
fail("this should not pass!");
|
||||
} catch (SecurityException se) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
public void testCompletelyForbidden1() throws Exception {
|
||||
try {
|
||||
runWithRestrictedPermissions(this::doSomeCompletelyForbiddenStuff);
|
||||
fail("this should not pass!");
|
||||
} catch (SecurityException se) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
public void testCompletelyForbidden2() throws Exception {
|
||||
try {
|
||||
runWithRestrictedPermissions(this::doSomeCompletelyForbiddenStuff, new AllPermission());
|
||||
fail("this should not pass (not even with AllPermission)");
|
||||
} catch (SecurityException se) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
private Void doSomeForbiddenStuff() throws IOException {
|
||||
createTempDir("cannot_create_temp_folder");
|
||||
return null; // Void
|
||||
}
|
||||
|
||||
// something like this should not never pass!!
|
||||
private Void doSomeCompletelyForbiddenStuff() throws IOException {
|
||||
Files.createFile(Paths.get("denied"));
|
||||
return null; // Void
|
||||
}
|
||||
|
||||
}
|
|
@ -110,4 +110,7 @@ grant {
|
|||
// SSL related properties for jetty
|
||||
permission java.security.SecurityPermission "getProperty.ssl.KeyManagerFactory.algorithm";
|
||||
permission java.security.SecurityPermission "getProperty.ssl.TrustManagerFactory.algorithm";
|
||||
|
||||
// allows LuceneTestCase#runWithRestrictedPermissions to execute with lower (or no) permission
|
||||
permission java.security.SecurityPermission "createAccessControlContext";
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue