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 API, however the returned bits may be called on different documents compared
to before. (Adrien Grand) 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 Optimizations
* LUCENE-6548: Some optimizations for BlockTree's intersect with very * 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 * LUCENE-6637: Fix FSTTester to not violate file permissions on
-Dtests.verbose=true. (Mesbah M. Alam, Uwe Schindler) -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 Changes in Backwards Compatibility Policy
* LUCENE-6553: The iterator returned by the LeafReader.postings method now * 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 { protected FSDirectory(Path path, LockFactory lockFactory) throws IOException {
super(lockFactory); 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(); 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.NoSuchFileException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; 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.text.Collator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -101,6 +109,7 @@ import org.junit.Test;
import org.junit.rules.RuleChain; import org.junit.rules.RuleChain;
import org.junit.rules.TestRule; import org.junit.rules.TestRule;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import com.carrotsearch.randomizedtesting.JUnit4MethodProvider; import com.carrotsearch.randomizedtesting.JUnit4MethodProvider;
import com.carrotsearch.randomizedtesting.LifecycleScope; import com.carrotsearch.randomizedtesting.LifecycleScope;
import com.carrotsearch.randomizedtesting.MixWithSuiteName; import com.carrotsearch.randomizedtesting.MixWithSuiteName;
@ -2605,6 +2614,25 @@ public abstract class LuceneTestCase extends Assert {
return createTempFile("tempFile", ".tmp"); 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). */ /** True if assertions (-ea) are enabled (at least for this class). */
public static final boolean assertsAreEnabled; 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 // SSL related properties for jetty
permission java.security.SecurityPermission "getProperty.ssl.KeyManagerFactory.algorithm"; permission java.security.SecurityPermission "getProperty.ssl.KeyManagerFactory.algorithm";
permission java.security.SecurityPermission "getProperty.ssl.TrustManagerFactory.algorithm"; permission java.security.SecurityPermission "getProperty.ssl.TrustManagerFactory.algorithm";
// allows LuceneTestCase#runWithRestrictedPermissions to execute with lower (or no) permission
permission java.security.SecurityPermission "createAccessControlContext";
}; };