Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make API signing secret a JvmSetting #7715 #31

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
2 changes: 2 additions & 0 deletions doc/sphinx-guides/source/api/external-tools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ Reserved Words
``{localeCode}`` optional The code for the language ("en" for English, "fr" for French, etc.) that user has selected from the language toggle in a Dataverse installation. See also :ref:`i18n`.
=========================== ========== ===========

.. _api-exttools-auth:

Authorization Options
+++++++++++++++++++++

Expand Down
5 changes: 4 additions & 1 deletion doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4106,7 +4106,8 @@ The fully expanded example above (without environment variables) looks like this
.. code-block:: bash

curl -X DELETE https://demo.dataverse.org/api/admin/template/24


.. _api-native-signed-url:

Request Signed URL
~~~~~~~~~~~~~~~~~~
Expand All @@ -4133,3 +4134,5 @@ A curl example using allowing access to a dataset's metadata

curl -H 'X-Dataverse-key:$API_KEY' -d $JSON $SERVER_URL/api/admin/requestSignedUrl

Please see :ref:`dataverse.api.signature-secret` for the configuration option to add a shared secret, enabling extra
security.
37 changes: 35 additions & 2 deletions doc/sphinx-guides/source/installation/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -580,8 +580,7 @@ Optionally, you may provide static credentials for each S3 storage using MicroPr
- ``dataverse.files.<id>.access-key`` for this storage's "access key ID"
- ``dataverse.files.<id>.secret-key`` for this storage's "secret access key"

You may provide the values for these via any of the
`supported config sources <https://docs.payara.fish/community/docs/documentation/microprofile/config/README.html>`_.
You may provide the values for these via any `supported MicroProfile Config API source`_.

**WARNING:**

Expand Down Expand Up @@ -1670,6 +1669,36 @@ This setting is useful in cases such as running your Dataverse installation behi
"HTTP_VIA",
"REMOTE_ADDR"


.. _dataverse.api.signature-secret:

dataverse.api.signature-secret
++++++++++++++++++++++++++++++

Context: Dataverse has the ability to create "Signed URLs" for it's API calls. Using a signed URL makes it obsolete to
provide API tokens to tools, which carries the risk of leaking extremely sensitive information on exposure. Signed URLs
can be limited to certain allowed actions, which is much more secure. See :ref:`api-exttools-auth` and
:ref:`api-native-signed-url` for more details. The key to sign a URL is created from the secret API token of the
creating user plus a shared secret provided by an administrator.

This setting will default to an empty string, but you should provide it for extra security.

Here is an example how to set your shared secret with the secure method "password alias":

.. code-block:: shell

echo "AS_ADMIN_ALIASPASSWORD=change-me-super-secret" > /tmp/password.txt
asadmin create-password-alias --passwordfile /tmp/password.txt dataverse.api.signature-secret
rm /tmp/password.txt

Can also be set via any `supported MicroProfile Config API source`_, e.g. the environment variable
``DATAVERSE_API_SIGNATURE_SECRET``.

**WARNING:** For security, do not use the sources "environment variable" or "system property" (JVM option) in a
production context! Rely on password alias, secrets directory or cloud based sources instead!



.. _:ApplicationServerSettings:

Application Server Settings
Expand Down Expand Up @@ -3067,3 +3096,7 @@ The interval in seconds between Dataverse calls to Globus to check on upload pro
+++++++++++++++++++++++++

A true/false option to add a Globus transfer option to the file download menu which is not yet fully supported in the dataverse-globus app. See :ref:`globus-support` for details.



.. _supported MicroProfile Config API source: https://docs.payara.fish/community/docs/Technical%20Documentation/MicroProfile/Config/Overview.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import edu.harvard.iq.dataverse.privateurl.PrivateUrlServiceBean;
import edu.harvard.iq.dataverse.locality.StorageSiteServiceBean;
import edu.harvard.iq.dataverse.search.savedsearch.SavedSearchServiceBean;
import edu.harvard.iq.dataverse.settings.JvmSettings;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import edu.harvard.iq.dataverse.util.BundleUtil;
import edu.harvard.iq.dataverse.util.SystemConfig;
Expand Down Expand Up @@ -440,7 +441,7 @@ private AuthenticatedUser getAuthenticatedUserFromSignedUrl() {
// ToDo - add null checks/ verify that calling methods catch things.
String user = httpRequest.getParameter("user");
AuthenticatedUser targetUser = authSvc.getAuthenticatedUser(user);
String key = System.getProperty(SystemConfig.API_SIGNING_SECRET, "")
String key = JvmSettings.API_SIGNING_SECRET.lookupOptional().orElse("")
+ authSvc.findApiTokenByUser(targetUser).getTokenString();
String signedUrl = httpRequest.getRequestURL().toString() + "?" + httpRequest.getQueryString();
String method = httpRequest.getMethod();
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/edu/harvard/iq/dataverse/api/Admin.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import edu.harvard.iq.dataverse.DataverseServiceBean;
import edu.harvard.iq.dataverse.DataverseSession;
import edu.harvard.iq.dataverse.DvObject;
import edu.harvard.iq.dataverse.settings.JvmSettings;
import edu.harvard.iq.dataverse.validation.EMailValidator;
import edu.harvard.iq.dataverse.EjbDataverseEngine;
import edu.harvard.iq.dataverse.GlobalId;
Expand Down Expand Up @@ -2277,7 +2278,7 @@ public Response getSignedUrl(JsonObject urlInfo) throws WrappedResponse {
if (key == null) {
return error(Response.Status.CONFLICT, "Do not have a valid user with apiToken");
}
key = System.getProperty(SystemConfig.API_SIGNING_SECRET, "") + key;
key = JvmSettings.API_SIGNING_SECRET.lookupOptional().orElse("") + key;
}

String baseUrl = urlInfo.getString("url");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import edu.harvard.iq.dataverse.Dataset;
import edu.harvard.iq.dataverse.FileMetadata;
import edu.harvard.iq.dataverse.authorization.users.ApiToken;
import edu.harvard.iq.dataverse.settings.JvmSettings;
import edu.harvard.iq.dataverse.util.SystemConfig;
import edu.harvard.iq.dataverse.util.URLTokenUtil;

Expand Down Expand Up @@ -117,7 +118,7 @@ public String handleRequest(boolean preview) {
}
if (apiToken != null) {
callback = UrlSignerUtil.signUrl(callback, 5, apiToken.getAuthenticatedUser().getUserIdentifier(), HttpMethod.GET,
System.getProperty(SystemConfig.API_SIGNING_SECRET, "") + apiToken.getTokenString());
JvmSettings.API_SIGNING_SECRET.lookupOptional().orElse("") + apiToken.getTokenString());
}
paramsString= "?callback=" + Base64.getEncoder().encodeToString(StringUtils.getBytesUtf8(callback));
if (getLocaleCode() != null) {
Expand Down Expand Up @@ -189,7 +190,7 @@ public JsonObjectBuilder createPostBody(JsonObject params) {
ApiToken apiToken = getApiToken();
if (apiToken != null) {
url = UrlSignerUtil.signUrl(apiPath, timeout, apiToken.getAuthenticatedUser().getUserIdentifier(), httpmethod,
System.getProperty(SystemConfig.API_SIGNING_SECRET, "") + getApiToken().getTokenString());
JvmSettings.API_SIGNING_SECRET.lookupOptional().orElse("") + getApiToken().getTokenString());
}
logger.fine("Signed URL: " + url);
apisBuilder.add(Json.createObjectBuilder().add(NAME, name).add(HTTP_METHOD, httpmethod)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ public enum JvmSettings {
VERSION(PREFIX, "version"),
BUILD(PREFIX, "build"),

// API SETTINGS
SCOPE_API(PREFIX, "api"),
API_SIGNING_SECRET(SCOPE_API, "signing-secret"),

;

private static final String SCOPE_SEPARATOR = ".";
Expand Down
5 changes: 0 additions & 5 deletions src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,6 @@ public class SystemConfig {
public final static String DEFAULTCURATIONLABELSET = "DEFAULT";
public final static String CURATIONLABELSDISABLED = "DISABLED";

// A secret used in signing URLs - individual urls are signed using this and the
// intended user's apiKey, creating an aggregate key that is unique to the user
// but not known to the user (as their apiKey is)
public final static String API_SIGNING_SECRET = "dataverse.api-signing-secret";

public String getVersion() {
return getVersion(false);
}
Expand Down