Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
9 changes: 9 additions & 0 deletions cavern/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ org.opencadc.cavern.resourceID = ivo://{authority}/{name}
# (optional) identify which container nodes are allocations
org.opencadc.cavern.allocationParent = {top level node}

# (optional) API Keys with administrative privileges. Multiple keys can be specified, one per line.
# Example:
Comment thread
pdowler marked this conversation as resolved.
Outdated
# org.opencadc.cavern.adminAPIKey = prepareData:p1wqj1Xj2SVg1
# org.opencadc.cavern.adminAPIKey = skaha:CmCQ1O7OMOaxr
#
# - {applicationClientName} is the name of the application client (e.g. "skaha", "prepareData")
# - {apiKeyToken} is any string of characters that is used to authenticate the application client.
# org.opencadc.cavern.adminAPIKey = {applicationClientName}:{apiKeyToken}

# (optional) provide a class implementing the org.opencadc.cavern.nodes.QuotaPlugin interface to control Quotas
# for users.
# Optional, but the default of NoQuotaPlugin will get used if not specified, and users will have free reign.
Expand Down
2 changes: 1 addition & 1 deletion cavern/VERSION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## deployable containers have a semantic and build tag
# semantic version tag: major.minor
# build version tag: timestamp
VER=0.8.5
VER=0.8.6
TAGS="${VER} ${VER}-$(date -u +"%Y%m%dT%H%M%S")"
unset VER
42 changes: 42 additions & 0 deletions cavern/src/main/java/org/opencadc/cavern/CavernConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.security.auth.Subject;
import org.apache.log4j.Logger;
import org.opencadc.cavern.nodes.NoQuotaPlugin;
Expand All @@ -94,6 +96,9 @@ public class CavernConfig {

private static final Logger log = Logger.getLogger(CavernConfig.class);

// The challenge type (after the authorization header and before the token) for API Key authentication. This is isolated in Cavern for now.
public static final String ALLOCATION_API_KEY_HEADER_CHALLENGE_TYPE = "admin-api-key";

public static final String CAVERN_PROPERTIES = "cavern.properties";
private static final String CAVERN_KEY = CavernConfig.class.getPackage().getName();
public static final String RESOURCE_ID = CAVERN_KEY + ".resourceID";
Expand All @@ -109,10 +114,20 @@ public class CavernConfig {

public static final String ALLOCATION_PARENT = CAVERN_KEY + ".allocationParent";

// An API Key representing a client that has administrative access. More than one is possible.
// Format is <applicationClientName>:<apiKeyToken>
public static final String ADMIN_API_KEY = CAVERN_KEY + ".adminAPIKey";

// Default quota in GB for new allocations, if not specified in the allocation request.
public static final String DEFAULT_QUOTA_GB = CAVERN_KEY + ".defaultQuotaGB";

public static final String QUOTA_PLUGIN_IMPLEMENTATION = QuotaPlugin.class.getName();

private final URI resourceID;
private final List<String> allocationParents = new ArrayList<>();

private final List<String> adminAPIKeys = new ArrayList<>();
private final double defaultQuotaGB;

private final Path root;
private final Path secrets;
Expand Down Expand Up @@ -169,6 +184,19 @@ public CavernConfig() {
allocationParents.add(ap);
}

adminAPIKeys.addAll(mvp.getProperty(CavernConfig.ADMIN_API_KEY));

final String configuredDefaultQuotaGB = mvp.getFirstPropertyValue(CavernConfig.DEFAULT_QUOTA_GB);
if (configuredDefaultQuotaGB == null) {
throw new InvalidConfigException(CavernConfig.DEFAULT_QUOTA_GB + " must be specified");
Comment thread
pdowler marked this conversation as resolved.
Outdated
} else {
try {
this.defaultQuotaGB = Double.parseDouble(configuredDefaultQuotaGB);
} catch (NumberFormatException e) {
throw new InvalidConfigException(CavernConfig.DEFAULT_QUOTA_GB + " must be a valid number: (" + configuredDefaultQuotaGB + ")");
}
}

this.secrets = Paths.get(baseDir, "secrets");
}

Expand All @@ -184,6 +212,20 @@ public List<String> getAllocationParents() {
return allocationParents;
}

/**
* Obtain the API keys for administrative tasks. The Key is the application client name, and the Value is the API key token.
* @return Map of allocation API keys, where the key is the application client name, and the value is the API key token.
*/
public Map<String, String> getAdminAPIKeys() {
return this.adminAPIKeys.stream().map(apiKey -> apiKey.split(":")).collect(
Collectors.toMap(splitKey -> splitKey[0], splitKey -> splitKey[1]));
}

public long getDefaultQuotaBytes() {
// Convert to bytes as that's what the NodeProperty expects. This assumes that the default quota is in GiB.
return (long) (defaultQuotaGB * 1024L * 1024L * 1024L);
Comment thread
pdowler marked this conversation as resolved.
Outdated
}

public Path getSecrets() {
return secrets;
}
Expand Down
25 changes: 24 additions & 1 deletion cavern/src/main/java/org/opencadc/cavern/CavernInitAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,14 @@

package org.opencadc.cavern;

import ca.nrc.cadc.auth.AuthenticationUtil;
import ca.nrc.cadc.auth.IdentityManager;
import ca.nrc.cadc.db.DBUtil;
import ca.nrc.cadc.rest.InitAction;
import ca.nrc.cadc.util.InvalidConfigException;
import ca.nrc.cadc.util.RsaSignatureGenerator;
import ca.nrc.cadc.uws.server.impl.InitDatabaseUWS;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
Expand All @@ -87,6 +89,7 @@
import javax.sql.DataSource;
import org.apache.log4j.Logger;
import org.opencadc.cavern.nodes.FileSystemNodePersistence;
import org.opencadc.cavern.nodes.PosixIdentityManager;
import org.opencadc.vospace.server.NodePersistence;

/**
Expand All @@ -103,6 +106,7 @@ public CavernInitAction() {

@Override
public void doInit() {
initIdentityManager();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initIdentityManager needs to be called after initNodePersistence because it needs a value of jndiNodePersistence

initNodePersistence();
initDatabase();
}
Expand Down Expand Up @@ -138,6 +142,25 @@ private void initNodePersistence() {
log.error("Failed to create JNDI Key " + jndiNodePersistence, ex);
}
}

private void initIdentityManager() {
final String configuredIdentityManagerClassName = System.getProperty(IdentityManager.class.getName());
if (configuredIdentityManagerClassName == null) {
throw new InvalidConfigException("No existing IdentityManager found. Ensure that the " + IdentityManager.class.getName()
+ " System Property is set to a valid implementation.");
}

// Assuming there isn't more than one Cavern deployed within a JVM.
PosixIdentityManager.JNDI_NODE_PERSISTENCE_PROPERTY = this.jndiNodePersistence;

// To be used by the PosixIdentityManager to wrap the existing IdentityManager.
System.setProperty(PosixIdentityManager.WRAPPED_IDENTITY_MANAGER_CLASS_PROPERTY, configuredIdentityManagerClassName);

// Override the existing IdentityManager.
System.setProperty(IdentityManager.class.getName(), PosixIdentityManager.class.getName());

log.debug("IdentityManager set to: " + PosixIdentityManager.class.getName() + " from " + configuredIdentityManagerClassName);
Comment thread
pdowler marked this conversation as resolved.
Outdated
}

// generate key pair for preauth URL generation
private void initSecrets(CavernConfig conf) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,14 @@
package org.opencadc.cavern.nodes;

import ca.nrc.cadc.auth.AuthenticationUtil;
import ca.nrc.cadc.auth.IdentityManager;
import ca.nrc.cadc.auth.PosixPrincipal;
import ca.nrc.cadc.io.ResourceIterator;
import ca.nrc.cadc.net.TransientException;
import ca.nrc.cadc.reg.Standards;
import ca.nrc.cadc.reg.client.LocalAuthority;
import ca.nrc.cadc.util.InvalidConfigException;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
Expand All @@ -82,7 +84,9 @@
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeSet;
Expand All @@ -97,6 +101,7 @@
import org.opencadc.vospace.LinkNode;
import org.opencadc.vospace.Node;
import org.opencadc.vospace.NodeNotSupportedException;
import org.opencadc.vospace.NodeProperty;
import org.opencadc.vospace.VOS;
import org.opencadc.vospace.VOSURI;
import org.opencadc.vospace.server.LocalServiceURI;
Expand Down Expand Up @@ -132,7 +137,7 @@ public class FileSystemNodePersistence implements NodePersistence {
VOS.PROPERTY_URI_CREATOR,
VOS.PROPERTY_URI_QUOTA
));

private final PosixIdentityManager identityManager;
private final GroupCache groupCache;
private final QuotaPlugin quotaImpl;
Expand All @@ -144,16 +149,27 @@ public class FileSystemNodePersistence implements NodePersistence {
private final CavernConfig config;
private final boolean localGroupsOnly;

// Set of default properties for nodes. This will be built from configuration.
private final List<NodeProperty> defaultProperties = new ArrayList<>();
Comment thread
pdowler marked this conversation as resolved.
Outdated

public FileSystemNodePersistence() {
this.config = new CavernConfig();
this.rootPath = config.getRoot();
this.quotaImpl = config.getQuotaPlugin();

defaultProperties.add(new NodeProperty(VOS.PROPERTY_URI_QUOTA, Long.toString(config.getDefaultQuotaBytes())));

LocalServiceURI loc = new LocalServiceURI(config.getResourceID());
this.rootURI = loc.getVOSBase();

// must be hard coded to this and not set via java system properties
this.identityManager = new PosixIdentityManager();
final IdentityManager configuredIdentityManager = AuthenticationUtil.getIdentityManager();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is simply a check that init did the setup correctly. The error should not be exposed to operators as a failure here is a bug, not a config issue. Instead: just catch the possible ClasscastException at line 172, eg:

try {
    this.identityManager = (PosixIdentityManager) AuthenticationUtil.getIdentityManager();
} catch (ClasscastException ex) {
    throw new RuntimeException("BUG: init failed to wrap IdentityManager with PosixIdentityManager", ex);
}

Yeah, I literally put "BUG" in the error message :-)


if (!(configuredIdentityManager instanceof PosixIdentityManager)) {
throw new InvalidConfigException("BUG: PosixIdentityManager required but found: " + configuredIdentityManager.getClass().getName());
}

this.identityManager = (PosixIdentityManager) AuthenticationUtil.getIdentityManager();

// root node
UUID rootID = new UUID(0L, 0L); // cosmetic: not used in cavern
Expand Down Expand Up @@ -409,10 +425,7 @@ public Node put(Node node) throws NodeNotSupportedException, TransientException
}
node.ownerID = identityManager.toPosixPrincipal(node.owner);
}

//if (node.isStructured()) {
// throw new NodeNotSupportedException("StructuredDataNode is not supported.");
//}

if (localGroupsOnly) {
if (!node.getReadOnlyGroup().isEmpty() || !node.getReadWriteGroup().isEmpty()) {
LocalAuthority loc = new LocalAuthority();
Expand Down Expand Up @@ -452,6 +465,14 @@ public Node put(Node node) throws NodeNotSupportedException, TransientException
throw new UnsupportedOperationException("link to external resource", ex);
}
}
} else if ((node instanceof ContainerNode) && isAllocation((ContainerNode) node)
&& node.getProperty(VOS.PROPERTY_URI_QUOTA) == null) {
final NodeProperty defaultNodeProperty = this.defaultProperties.get(this.defaultProperties.indexOf(new NodeProperty(VOS.PROPERTY_URI_QUOTA)));
if (defaultNodeProperty == null) {
log.warn("No default quota (" + VOS.PROPERTY_URI_QUOTA + ") configured.");
} else {
node.getProperties().add(new NodeProperty(VOS.PROPERTY_URI_QUOTA, defaultNodeProperty.getValue()));
Comment thread
pdowler marked this conversation as resolved.
Outdated
}
}

// this is a complicated way to get the Path
Expand Down
Loading