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:
Uwe Schindler 2015-07-01 00:16:37 +00:00
parent b6e28103ec
commit e50f72e073
6 changed files with 210 additions and 1 deletions

View File

@ -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

View File

@ -125,7 +125,10 @@ public abstract class FSDirectory extends BaseDirectory {
*/
protected FSDirectory(Path path, LockFactory lockFactory) throws IOException {
super(lockFactory);
// 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();
}

View File

@ -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
}
}

View File

@ -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;

View File

@ -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
}
}

View File

@ -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";
};