Repository navigation
SOLR-18618: Upload configsets from a backup repository - #5287
HoustonPutman wants to merge 2 commits into
Conversation
|
what about: Or, maybe you just keep Would distingushing on the accept type help? One takes a binary file and the other takes json? |
Yeah, and that would work great for full configSet upload. For a single file it gets iffy in case the file is of type json. Basically we can make it that if the file matches the schema ( |
|
🤖 AI text below 🤖 (reviewed by Nick Shanin) I prepared a follow-up covering V1/V2 body exclusivity, symlink rejection for local repository imports, and dot-prefixed directory imports. The comparison has eight files: the original seven plus a test-policy update that lets the symlink tests create links under Solr's test security manager. The tidy and test setup fixes are within existing files. VerificationAt The patch is a fast-forward from this PR's current head, From a checkout at the current PR head, the changes can be taken with: git fetch https://github.com/nick-boss-tech/solr.git pr-5287-configset-source-validation
git merge --ff-only d0cf2471f37ae973ca06390ec195cf51f8fccbb3 |
|
FWIW I kindof like the params-not-request-body approach here. There's less ambiguity, for one thing. For another you could also argue it's more REST-ful. The request-body of a POST/PUT is intended to represent the "resource" being created/updated. "repository" and "location" aren't really the configset resource itself, they're something else. (coordinates? metadata? not sure what it makes sense to call them). Anyway, I'm fine w/ either approach, but I'd opt for the query params personnally. |
gerlowskija
left a comment
There was a problem hiding this comment.
Left a few comments inline. I think the most important one(s) are around the security implications. Not sure whether I'm just being overly paranoid there, or whether those actually need addressed but would love some other eyes on those.
| .isEmpty()) { // Uploading a whole configset | ||
| final var repository = req.getParams().get(CoreAdminParams.BACKUP_REPOSITORY); | ||
| final var location = req.getParams().get(CoreAdminParams.BACKUP_LOCATION); | ||
| if ((repository == null) != (location == null)) { |
There was a problem hiding this comment.
[0] This validation should probably live in the v2 API implementation class(es). Or if you have added it there elsewhere in this diff, then it probably isn't needed here.
| if (!repository.exists(source)) { | ||
| throw new SolrException( | ||
| SolrException.ErrorCode.BAD_REQUEST, | ||
| "The location " + source + " does not exist in the backup repository"); |
There was a problem hiding this comment.
[Q] This line got me thinking: "Oh I wonder if users could use this to sniff out files that exist up in the repository", and that got me thinking about the security implications of this change more broadly.
With these params any users with upload-configset perms now have the ability to essentially read arbitrary content out of whatever repository the admins have set up.
A few disjointed thoughts along these lines:
- I guess, technically, arbitrary-read is a risk with the existing backup-restore code, but that seems significantly mitigated by the fact that backups have a lot of structure and the API is validating that "location" values point to a valid backup before its doing any file copies, etc. Configsets don't have that structure necessarily, so we can't do much similar validation here.
- Many repositories take a default or "root" location. The risk would be much reduced if S3BackupRepository and friends were coded to prevent path-escape from that root location. But at a glance that doesn't appear to the case?
- The community has generally agreed that "configset-upload" perms are pretty dangerous and should only be given out to very trusted users. So maybe this isn't a huge deal in practice, and could just be solved with documentation saying that "upload-configset allows users to read out of backupRepositories"
- If it's not just me and folks feel like there are some valid security concerns here, maybe a solr.xml setting or sysprop to gate the use of these parameters would help? i.e.
<allowBackupRepoConfigsetUpload>true</allowBackupRepoConfigsetUpload>
There was a problem hiding this comment.
- If it's not just me and folks feel like there are some valid security concerns here, maybe a solr.xml setting or sysprop to gate the use of these parameters would help? i.e.
<allowBackupRepoConfigsetUpload>true</allowBackupRepoConfigsetUpload>
I like this idea, but maybe as an option for the backup repo itself. <allowConfigSetUpload>
| } | ||
|
|
||
| // Read everything before writing so a failure doesn't leave a half-uploaded configset. | ||
| final Map<String, byte[]> files = new LinkedHashMap<>(); |
There was a problem hiding this comment.
[0] Could this be an OOM risk? if a user accidentally provides a truncated location value it seems like nothing would stop Solr from copying a (potentially large) file tree and keeping each byte on-heap.
| SolrException.ErrorCode.BAD_REQUEST, | ||
| "'location' is not specified as a parameter or as a default repository property"); | ||
| } | ||
| if ("file".equals(repository.createURI("x").getScheme())) { |
There was a problem hiding this comment.
[0] Creating a URI for a non-existent file seems a bit hacky. Could we just use the location string itself, or is there a reason to avoid that?
if ("file".equals(locationUri.getScheme())) {
core.getCoreContainer().assertPathAllowed(Paths.get(locationUri));
}
|
|
||
| private static final String SOLR_XML = | ||
| AbstractInstallShardTest.defaultSolrXmlTextWithBackupRepository( | ||
| "<str name=\"allowPaths\">${solr.security.allow.paths:}</str>\n" |
There was a problem hiding this comment.
| "<str name=\"allowPaths\">${solr.security.allow.paths:}</str>\n" | |
| "<str name=\"allowPaths\">*</str>\n" |
If we're always going to set this property to *, we don't need the placeholder variable here...
|
|
||
| @BeforeClass | ||
| public static void setupClass() throws Exception { | ||
| System.setProperty("solr.security.allow.paths", "*"); |
There was a problem hiding this comment.
| System.setProperty("solr.security.allow.paths", "*"); |
(Only accept this if you take the proposed change on L40 above)
| The `overwrite` and `cleanup` parameters work the same way as for zip uploads. | ||
|
|
||
| [source,bash] | ||
| ---- |
There was a problem hiding this comment.
[0] Love the docs. I'll push a change to reformat the v1/v2 examples so they use the "tabbed" formatting we've been adopting more and more for "paired" examples (e.g. v1/v2, Windows/Linux, etc.)
| } | ||
|
|
||
| private ConfigsetsApi.UploadConfigSet uploadRequest(String configSetName, URI source) { | ||
| final var request = new ConfigsetsApi.UploadConfigSet(configSetName, emptyBody()); |
There was a problem hiding this comment.
[+1] Love that you're using the generated client objects here, great to get test coverage on these!
There was a problem hiding this comment.
[-0] This zip file example now appears in the "Upload from Backup Repo" section, which probably isn't what you intended?
I already offered to handle the v1/v2 example tabbing, so I'm going to take a crack at fixing this as well.
|
🤖 AI text 🤖 There is also a path-boundary issue in the new local repository import. The allowed-path check is applied to the requested location, but traversal follows symbolic links inside that directory. A ConfigSet editor can therefore cause the import to read outside the intended allowed paths when the local repository contains such a link. The single-file import has the same concern. This depends on the local repository contents and the caller's ConfigSet permissions. The earlier offered patch includes path checks and symlink regression tests. |
https://issues.apache.org/jira/browse/SOLR-18618
The new API adds the features as params, instead of a body. The body would have been a good option, but it kind of contrasts with the existing query param options, and in the file API currently files being uploaded with a JSON content type would have a weird behavior going forward.
Unfortunately there's not a good way of gating the way that the API is used (body or repository & location). We could use a separate API path, but I couldn't think of anything acceptable for that. Open to suggestions though.