Skip to content

Commit

Permalink
Merge pull request #550 from pdowler/vos2
Browse files Browse the repository at this point in the history
vault: add support for separate nodes and inventory pools
  • Loading branch information
pdowler authored Jan 23, 2024
2 parents 240fff8 + c645b73 commit e6c9593
Show file tree
Hide file tree
Showing 11 changed files with 263 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
import ca.nrc.cadc.rest.InlineContentHandler;
import ca.nrc.cadc.rest.RestAction;
import java.io.OutputStream;
import java.io.PrintWriter;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
Expand Down Expand Up @@ -111,7 +112,14 @@ public void doAction() throws Exception {
ostream.flush();
}
} catch (NamingException ex) {
throw new RuntimeException("BUG: failed to find keys via JNDI", ex);
syncOutput.setHeader("content-type", "test/plain");
syncOutput.setCode(404);
try (OutputStream ostream = syncOutput.getOutputStream()) {
PrintWriter w = new PrintWriter(ostream);
w.println("not found: key signing disabled");
w.flush();
w.close();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -499,29 +499,31 @@ private List<Protocol> doPushTo(URI artifactURI, Transfer transfer, String authT
if (sec == null) {
sec = Standards.SECURITY_METHOD_ANON;
}
boolean incToken = Standards.SECURITY_METHOD_ANON.equals(sec);
boolean anon = Standards.SECURITY_METHOD_ANON.equals(sec);
Interface iface = filesCap.findInterface(sec);
log.debug("PUT: " + storageSite + " proto: " + proto + " iface: " + iface);
if (iface != null) {
URL baseURL = iface.getAccessURL().getURL();
//log.debug("base url for site " + storageSite.getResourceID() + ": " + baseURL);
if (protocolCompat(proto, baseURL)) {

StringBuilder sb = new StringBuilder();
sb.append(baseURL.toExternalForm()).append("/");
if (authToken != null && incToken) {
sb.append(authToken).append("/");
}
sb.append(artifactURI.toASCIIString());
Protocol p = new Protocol(proto.getUri());
if (transfer.version == VOS.VOSPACE_21) {
p.setSecurityMethod(proto.getSecurityMethod());
// // no plain anon URL for put: !anon or anon+token
boolean gen = (!anon || (anon && authToken != null));
if (gen) {
StringBuilder sb = new StringBuilder();
sb.append(baseURL.toExternalForm()).append("/");
if (authToken != null && anon) {
sb.append(authToken).append("/");
}
sb.append(artifactURI.toASCIIString());
Protocol p = new Protocol(proto.getUri());
if (transfer.version == VOS.VOSPACE_21) {
p.setSecurityMethod(proto.getSecurityMethod());
}
p.setEndpoint(sb.toString());
protos.add(p);
log.debug("added: " + p);
}
p.setEndpoint(sb.toString());
protos.add(p);
log.debug("added: " + p);

// no plain anon URL for put

} else {
log.debug("PUT: " + storageSite + "PUT: reject protocol: " + proto
+ " reason: no compatible URL protocol");
Expand Down
17 changes: 10 additions & 7 deletions raven/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,29 +42,32 @@ org.opencadc.raven.inventory.schema={schema}
# consistency settings
org.opencadc.raven.consistency.preventNotFound=true|false
# url signing key usage
org.opencadc.raven.keys.preauth={true|false}
```
The _preventNotFound_ key can be used to configure `raven` to prevent artifact-not-found errors that might
result due to the eventual consistency nature of the system by directly checking for the artifact at
_all known_ sites. This feature introduces an overhead for the genuine not-found cases.

The _keys.preauth_ key configures `raven` to use URL-signing. When enabled, `raven` can generate a signed token
and embeds it into the URL; `minoc` services can validate the token and grant access without further permission
checks. With transfer negotiation, the signed URL gets added as an additional "anonymous" URL.


The following optional keys configure raven to use external service(s) to obtain grant information in order
to perform authorization checks:
to perform authorization checks and generate signed URLs:
```
org.opencadc.raven.readGrantProvider={resourceID of a permission granting service}
org.opencadc.raven.writeGrantProvider={resourceID of a permission granting service}
# url signing key usage
org.opencadc.raven.keys.preauth={true|false}
```
The optional _readGrantProvider_ and _writeGrantProvider_ keys configure minoc to call other services to get grants (permissions) for
operations. Multiple values of the permission granting service resourceID(s) may be provided by including multiple property
settings. All services will be consulted but a single positive result is sufficient to grant permission for an
action.

The _keys.preauth_ key (default: false) configures `raven` to use URL-signing. When enabled, `raven` can generate a signed token
and embed it into the URL; `minoc` services that are configured to trust a `raven` service will download the public key and can
validate the token and grant access without further permission checks. With transfer negotiation, the signed URL gets added as
an additional "anonymous" URL.

The following optional keys configure raven to prioritize sites returned in transfer negotiation, with higher priority
sites first in the list of transfer URL's. Multiple values of _namespace_ may be specified for a single _resourceID_.
The _namespace_ value(s) must end with a colon (:) or slash (/) so one namespace cannot accidentally match (be a
Expand Down
2 changes: 1 addition & 1 deletion raven/src/main/java/org/opencadc/raven/ArtifactAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ protected ArtifactAction() {
this.preauthKeys = Boolean.valueOf(pak);
log.debug("Using preauth keys: " + this.preauthKeys);
} else {
throw new IllegalStateException("invalid config: missing keys.preauth configuration");
this.preauthKeys = false;
}
}

Expand Down
18 changes: 12 additions & 6 deletions raven/src/main/java/org/opencadc/raven/HeadFilesAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,11 @@
import ca.nrc.cadc.rest.SyncOutput;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Set;
import org.apache.log4j.Logger;
import org.opencadc.inventory.Artifact;
import org.opencadc.inventory.InventoryUtil;
import org.opencadc.inventory.StorageSite;
import org.opencadc.inventory.db.StorageSiteDAO;
import org.opencadc.inventory.transfer.ProtocolsGenerator;
import org.opencadc.permissions.ReadGrant;
Expand Down Expand Up @@ -108,23 +110,27 @@ public void doAction() throws Exception {
log.debug("Starting HEAD action for " + artifactURI.toASCIIString());
Artifact artifact = artifactDAO.get(artifactURI);

if (artifact == null) {
if (this.preventNotFound) {
if (artifact == null && preventNotFound) {
StorageSiteDAO storageSiteDAO = new StorageSiteDAO(artifactDAO);
Set<StorageSite> sites = storageSiteDAO.list();
if (!sites.isEmpty()) {
// check known storage sites
ProtocolsGenerator pg = new ProtocolsGenerator(
this.artifactDAO, this.siteAvailabilities, this.siteRules);
pg.tokenGen = this.tokenGen;
pg.user = this.user;
pg.preventNotFound = this.preventNotFound;
pg.storageResolver = this.storageResolver;
StorageSiteDAO storageSiteDAO = new StorageSiteDAO(artifactDAO);

Transfer transfer = new Transfer(artifactURI, Direction.pullFromVoSpace);
Protocol proto = new Protocol(VOS.PROTOCOL_HTTPS_GET);
proto.setSecurityMethod(Standards.SECURITY_METHOD_ANON);
transfer.getProtocols().add(proto);
// TODO: tokenGen is optional so this can fail
String authToken = tokenGen.generateToken(artifactURI, ReadGrant.class, user);
artifact = pg.getUnsyncedArtifact(artifactURI, transfer, storageSiteDAO.list(), authToken);
String authToken = null;
if (tokenGen != null) {
authToken = tokenGen.generateToken(artifactURI, ReadGrant.class, user);
}
artifact = pg.getUnsyncedArtifact(artifactURI, transfer, sites, authToken);
}
}

Expand Down
9 changes: 0 additions & 9 deletions raven/src/main/java/org/opencadc/raven/RavenInitAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -330,15 +330,6 @@ static MultiValuedProperties getConfig() {
sb.append("OK");
}

String preauthKeys = mvp.getFirstPropertyValue(RavenInitAction.PREAUTH_KEY);
sb.append("\n\t").append(RavenInitAction.PREAUTH_KEY).append(": ");
if (preauthKeys == null) {
sb.append("MISSING");
ok = false;
} else {
sb.append("OK");
}

if (!ok) {
throw new IllegalStateException(sb.toString());
}
Expand Down
24 changes: 24 additions & 0 deletions vault/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ org.opencadc.vault.nodes.username={username for vospace pool}
org.opencadc.vault.nodes.password={password for vospace pool}
org.opencadc.vault.nodes.url=jdbc:postgresql://{server}/{database}
org.opencadc.vault.inventory.maxActive={max connections for inventory pool}
# optional: config for separate inventory pool
org.opencadc.vault.inventory.username={username for inventory pool}
org.opencadc.vault.inventory.password={password for inventory pool}
org.opencadc.vault.inventory.url=jdbc:postgresql://{server}/{database}
org.opencadc.vault.uws.maxActive={max connections for uws pool}
org.opencadc.vault.uws.username={username for uws pool}
org.opencadc.vault.uws.password={password for uws pool}
Expand All @@ -50,6 +56,15 @@ all the content (insert, update, delete). The database is specified in the JDBC
in the vault.properties (below). Failure to connect or initialize the database will show up in logs and in the
VOSI-availability output.

The _inventory_ account owns and manages (create, alter, drop) inventory database objects and manages
all the content (update and delete Artifact, insert DeletedArtifactEvent). The database is specified
in the JDBC URL and the schema name is specified in the minoc.properties (below). Failure to connect or
initialize the database will show up in logs and in the VOSI-availability output. The _inventory_ content
may be in the same database as the _nodes_, in a different database in the same server, or in a different
server entirely. See `org.opencadc.vault.singlePool` below for the pros and cons. Note: it is a good
idea to set `maxActive` to a valid integer (e.g. 0) when using a single pool; this avoids an ugly but
meaningless stack trace in the logs at startup.

The _uws_ account owns and manages (create, alter, drop) uws database objects in the `uws` schema and manages all
the content (insert, update, delete). The database is specified in the JDBC URLFailure to connect or initialize the
database will show up in logs and in the VOSI-availability output.
Expand All @@ -70,6 +85,7 @@ org.opencadc.vault.consistency.preventNotFound=true|false
# vault database settings
org.opencadc.vault.inventory.schema = {inventory schema name}
org.opencadc.vault.vospace.schema = {vospace schema name}
org.opencadc.vault.singlePool = {true|false}
# root container nodes
org.opencadc.vault.root.owner = {owner of root node}
Expand All @@ -92,6 +108,14 @@ to configuration limitations in <a href="../luskan">luskan</a>.
The _vospace.schema_ name is the name of the database schema used for all created database objects (tables, indices, etc). Note that with a single connection pool, the two schemas must currently be in the same database.
TODO: augment config to support separate inventory and vospace pools.

The _singlePool_ key configures `vault` to use a single pool (the _nodes_ pool) for both vospace and inventory
operations. The inventory and vospace content must be in the same database for this to work. When configured
to use a single pool, delete node operations can delete a DataNode and the associated Artifact and create the
DeletedArtifactEvent in a single transaction. When configured to use separate pools, the delete Artifact and create
DeletedArtifactEvent are done in a separate transaction and if that fails the Artifact will be left behind and
orphaned until the vault validation (see ???) runs and fixes such a discrepancy. However, _singlePool_ = `false` allows
the content to be stored in two separate databases or servers.

The _root.owner_ owns the root node and has full read and write permission in the root container, so it can
create and delete container nodes at the root and assign container node properties that are normally read-only
to normal users: owner, quota, etc. This must be set to the username of the admin.
Expand Down
2 changes: 1 addition & 1 deletion vault/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
# tags with and without build number so operators use the versioned
# tag but we always keep a timestamped tag in case a semantic tag gets
# replaced accidentally
VER=0.1.0
VER=0.2.0
TAGS="${VER} ${VER}-$(date --utc +"%Y%m%dT%H%M%S")"
unset VER
Loading

0 comments on commit e6c9593

Please sign in to comment.