http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextDelegate.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextDelegate.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextDelegate.java new file mode 100644 index 0000000..ff14986 --- /dev/null +++ b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextDelegate.java @@ -0,0 +1,649 @@ +/* + * Copyright (C) 2010-2012 The University of Manchester + * + * See the file "LICENSE" for license terms. + */ +package org.taverna.server.master.worker; + +import static java.lang.String.format; +import static java.util.Arrays.fill; +import static java.util.UUID.randomUUID; +import static org.taverna.server.master.defaults.Default.CERTIFICATE_FIELD_NAMES; +import static org.taverna.server.master.defaults.Default.CERTIFICATE_TYPE; +import static org.taverna.server.master.defaults.Default.CREDENTIAL_FILE_SIZE_LIMIT; +import static org.taverna.server.master.identity.WorkflowInternalAuthProvider.PREFIX; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.rmi.RemoteException; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; +import javax.security.auth.x500.X500Principal; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.UriBuilder; +import javax.xml.ws.handler.MessageContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.taverna.server.localworker.remote.ImplementationException; +import org.taverna.server.localworker.remote.RemoteSecurityContext; +import org.taverna.server.master.common.Credential; +import org.taverna.server.master.common.Trust; +import org.taverna.server.master.exceptions.FilesystemAccessException; +import org.taverna.server.master.exceptions.InvalidCredentialException; +import org.taverna.server.master.exceptions.NoDirectoryEntryException; +import org.taverna.server.master.interfaces.File; +import org.taverna.server.master.interfaces.TavernaSecurityContext; +import org.taverna.server.master.utils.UsernamePrincipal; + +/** + * Implementation of a security context. + * + * @author Donal Fellows + */ +public abstract class SecurityContextDelegate implements TavernaSecurityContext { + Log log = LogFactory.getLog("Taverna.Server.Worker"); + private final UsernamePrincipal owner; + private final List<Credential> credentials = new ArrayList<>(); + private final List<Trust> trusted = new ArrayList<>(); + private final RemoteRunDelegate run; + private final Object lock = new Object(); + final SecurityContextFactory factory; + + private transient Keystore keystore; + private transient Map<URI, String> uriToAliasMap; + + /** + * Initialise the context delegate. + * + * @param run + * What workflow run is this for? + * @param owner + * Who owns the workflow run? + * @param factory + * What class built this object? + */ + protected SecurityContextDelegate(RemoteRunDelegate run, + UsernamePrincipal owner, SecurityContextFactory factory) { + this.run = run; + this.owner = owner; + this.factory = factory; + } + + @Override + public SecurityContextFactory getFactory() { + return factory; + } + + @Override + public UsernamePrincipal getOwner() { + return owner; + } + + @Override + public Credential[] getCredentials() { + synchronized (lock) { + return credentials.toArray(new Credential[credentials.size()]); + } + } + + /** + * Get the human-readable name of a principal. + * + * @param principal + * The principal being decoded. + * @return A name. + */ + protected final String getPrincipalName(X500Principal principal) { + return factory.x500Utils.getName(principal, CERTIFICATE_FIELD_NAMES); + } + + /** + * Cause the current state to be flushed to the database. + */ + protected final void flushToDB() { + factory.db.flushToDisk(run); + } + + @Override + public void addCredential(Credential toAdd) { + synchronized (lock) { + int idx = credentials.indexOf(toAdd); + if (idx != -1) + credentials.set(idx, toAdd); + else + credentials.add(toAdd); + flushToDB(); + } + } + + @Override + public void deleteCredential(Credential toDelete) { + synchronized (lock) { + credentials.remove(toDelete); + flushToDB(); + } + } + + @Override + public Trust[] getTrusted() { + synchronized (lock) { + return trusted.toArray(new Trust[trusted.size()]); + } + } + + @Override + public void addTrusted(Trust toAdd) { + synchronized (lock) { + int idx = trusted.indexOf(toAdd); + if (idx != -1) + trusted.set(idx, toAdd); + else + trusted.add(toAdd); + flushToDB(); + } + } + + @Override + public void deleteTrusted(Trust toDelete) { + synchronized (lock) { + trusted.remove(toDelete); + flushToDB(); + } + } + + @Override + public abstract void validateCredential(Credential c) + throws InvalidCredentialException; + + @Override + public void validateTrusted(Trust t) throws InvalidCredentialException { + InputStream contentsAsStream; + if (t.certificateBytes != null && t.certificateBytes.length > 0) { + contentsAsStream = new ByteArrayInputStream(t.certificateBytes); + t.certificateFile = null; + } else if (t.certificateFile == null + || t.certificateFile.trim().isEmpty()) + throw new InvalidCredentialException( + "absent or empty certificateFile"); + else { + contentsAsStream = contents(t.certificateFile); + t.certificateBytes = null; + } + t.serverName = null; + if (t.fileType == null || t.fileType.trim().isEmpty()) + t.fileType = CERTIFICATE_TYPE; + t.fileType = t.fileType.trim(); + try { + t.loadedCertificates = CertificateFactory.getInstance(t.fileType) + .generateCertificates(contentsAsStream); + t.serverName = new ArrayList<>(t.loadedCertificates.size()); + for (Certificate c : t.loadedCertificates) + t.serverName.add(getPrincipalName(((X509Certificate) c) + .getSubjectX500Principal())); + } catch (CertificateException e) { + throw new InvalidCredentialException(e); + } catch (ClassCastException e) { + // Do nothing; truncates the list of server names + } + } + + @Override + public void initializeSecurityFromContext(SecurityContext securityContext) + throws Exception { + // This is how to get the info from Spring Security + Authentication auth = securityContext.getAuthentication(); + if (auth == null) + return; + auth.getPrincipal(); + // do nothing else in this implementation + } + + @Override + public void initializeSecurityFromSOAPContext(MessageContext context) { + // do nothing in this implementation + } + + @Override + public void initializeSecurityFromRESTContext(HttpHeaders context) { + // do nothing in this implementation + } + + private UriBuilder getUB() { + return factory.uriSource.getRunUriBuilder(run); + } + + private RunDatabaseDAO getDAO() { + return ((RunDatabase) factory.db).dao; + } + + @Nullable + private List<X509Certificate> getCerts(URI uri) throws IOException, + GeneralSecurityException { + return factory.certFetcher.getTrustsForURI(uri); + } + + private void installLocalPasswordCredential(List<Credential> credentials, + List<Trust> trusts) throws InvalidCredentialException, IOException, + GeneralSecurityException { + Credential.Password pw = new Credential.Password(); + pw.id = "run:self"; + pw.username = PREFIX + run.id; + pw.password = getDAO().getSecurityToken(run.id); + UriBuilder ub = getUB().segment("").fragment(factory.httpRealm); + pw.serviceURI = ub.build(); + validateCredential(pw); + log.info("issuing self-referential credential for " + pw.serviceURI); + credentials.add(pw); + List<X509Certificate> myCerts = getCerts(pw.serviceURI); + if (myCerts != null && myCerts.size() > 0) { + Trust t = new Trust(); + t.loadedCertificates = getCerts(pw.serviceURI); + trusts.add(t); + } + } + + /** + * Builds and transfers a keystore with suitable credentials to the back-end + * workflow execution engine. + * + * @throws GeneralSecurityException + * If the manipulation of the keystore, keys or certificates + * fails. + * @throws IOException + * If there are problems building the data (should not happen). + * @throws RemoteException + * If the conveyancing fails. + */ + @Override + public final void conveySecurity() throws GeneralSecurityException, + IOException, ImplementationException { + RemoteSecurityContext rc = run.run.getSecurityContext(); + + List<Trust> trusted = new ArrayList<>(this.trusted); + this.trusted.clear(); + List<Credential> credentials = new ArrayList<>(this.credentials); + this.credentials.clear(); + + try { + installLocalPasswordCredential(credentials, trusted); + } catch (Exception e) { + log.warn("failed to construct local credential: " + + "interaction service will fail", e); + } + + char[] password = null; + try { + password = generateNewPassword(); + + log.info("constructing merged keystore"); + Truststore truststore = new Truststore(password); + Keystore keystore = new Keystore(password); + Map<URI, String> uriToAliasMap = new HashMap<>(); + int trustedCount = 0, keyCount = 0; + + synchronized (lock) { + try { + for (Trust t : trusted) { + if (t == null || t.loadedCertificates == null) + continue; + for (Certificate cert : t.loadedCertificates) + if (cert != null) { + truststore.addCertificate(cert); + trustedCount++; + } + } + + this.uriToAliasMap = uriToAliasMap; + this.keystore = keystore; + for (Credential c : credentials) { + addCredentialToKeystore(c); + keyCount++; + } + } finally { + this.uriToAliasMap = null; + this.keystore = null; + credentials.clear(); + trusted.clear(); + flushToDB(); + } + } + + byte[] trustbytes = null, keybytes = null; + try { + trustbytes = truststore.serialize(); + keybytes = keystore.serialize(); + + // Now we've built the security information, ship it off... + + log.info("transfering merged truststore with " + trustedCount + + " entries"); + rc.setTruststore(trustbytes); + + log.info("transfering merged keystore with " + keyCount + + " entries"); + rc.setKeystore(keybytes); + } finally { + if (trustbytes != null) + fill(trustbytes, (byte) 0); + if (keybytes != null) + fill(keybytes, (byte) 0); + } + rc.setPassword(password); + + log.info("transferring serviceURL->alias map with " + + uriToAliasMap.size() + " entries"); + rc.setUriToAliasMap(uriToAliasMap); + } finally { + if (password != null) + fill(password, ' '); + } + + synchronized (lock) { + conveyExtraSecuritySettings(rc); + } + } + + /** + * Hook that allows additional information to be conveyed to the remote run. + * + * @param remoteSecurityContext + * The remote resource that information would be passed to. + * @throws IOException + * If anything goes wrong with the communication. + */ + protected void conveyExtraSecuritySettings( + RemoteSecurityContext remoteSecurityContext) throws IOException { + // Does nothing by default; overrideable + } + + /** + * @return A new password with a reasonable level of randomness. + */ + protected final char[] generateNewPassword() { + return randomUUID().toString().toCharArray(); + } + + /** + * Adds a credential to the current keystore. + * + * @param alias + * The alias to create within the keystore. + * @param c + * The key-pair. + * @throws KeyStoreException + */ + protected final void addKeypairToKeystore(String alias, Credential c) + throws KeyStoreException { + if (c.loadedKey == null) + throw new KeyStoreException("critical: credential was not verified"); + if (uriToAliasMap.containsKey(c.serviceURI)) + log.warn("duplicate URI in alias mapping: " + c.serviceURI); + keystore.addKey(alias, c.loadedKey, c.loadedTrustChain); + uriToAliasMap.put(c.serviceURI, alias); + } + + /** + * Adds a credential to the current keystore. + * + * @param c + * The credential to add. + * @throws KeyStoreException + */ + public abstract void addCredentialToKeystore(Credential c) + throws KeyStoreException; + + /** + * Read a file up to {@value #FILE_SIZE_LIMIT}kB in size. + * + * @param name + * The path name of the file, relative to the context run's + * working directory. + * @return A stream of the file's contents. + * @throws InvalidCredentialException + * If anything goes wrong. + */ + final InputStream contents(String name) throws InvalidCredentialException { + try { + File f = (File) factory.fileUtils.getDirEntry(run, name); + long size = f.getSize(); + if (size > CREDENTIAL_FILE_SIZE_LIMIT * 1024) + throw new InvalidCredentialException(CREDENTIAL_FILE_SIZE_LIMIT + + "kB limit hit"); + return new ByteArrayInputStream(f.getContents(0, (int) size)); + } catch (NoDirectoryEntryException | FilesystemAccessException e) { + throw new InvalidCredentialException(e); + } catch (ClassCastException e) { + throw new InvalidCredentialException("not a file", e); + } + } + + @Override + public Set<String> getPermittedDestroyers() { + return run.getDestroyers(); + } + + @Override + public void setPermittedDestroyers(Set<String> destroyers) { + run.setDestroyers(destroyers); + } + + @Override + public Set<String> getPermittedUpdaters() { + return run.getWriters(); + } + + @Override + public void setPermittedUpdaters(Set<String> updaters) { + run.setWriters(updaters); + } + + @Override + public Set<String> getPermittedReaders() { + return run.getReaders(); + } + + @Override + public void setPermittedReaders(Set<String> readers) { + run.setReaders(readers); + } + + /** + * Reinstall the credentials and the trust extracted from serialization to + * the database. + * + * @param credentials + * The credentials to reinstall. + * @param trust + * The trusted certificates to reinstall. + */ + void setCredentialsAndTrust(Credential[] credentials, Trust[] trust) { + synchronized (lock) { + this.credentials.clear(); + if (credentials != null) + for (Credential c : credentials) + try { + validateCredential(c); + this.credentials.add(c); + } catch (InvalidCredentialException e) { + log.warn("failed to revalidate credential: " + c, e); + } + this.trusted.clear(); + if (trust != null) + for (Trust t : trust) + try { + validateTrusted(t); + this.trusted.add(t); + } catch (InvalidCredentialException e) { + log.warn("failed to revalidate trust assertion: " + t, + e); + } + } + } + + static class SecurityStore { + private KeyStore ks; + private char[] password; + + SecurityStore(char[] password) throws GeneralSecurityException { + this.password = password.clone(); + ks = KeyStore.getInstance("UBER", "BC"); + try { + ks.load(null, this.password); + } catch (IOException e) { + throw new GeneralSecurityException( + "problem initializing blank truststore", e); + } + } + + final synchronized void setCertificate(String alias, Certificate c) + throws KeyStoreException { + if (ks == null) + throw new IllegalStateException("store already written"); + ks.setCertificateEntry(alias, c); + } + + final synchronized void setKey(String alias, Key key, Certificate[] trustChain) + throws KeyStoreException { + if (ks == null) + throw new IllegalStateException("store already written"); + ks.setKeyEntry(alias, key, password, trustChain); + } + + final synchronized byte[] serialize(boolean logIt) + throws GeneralSecurityException { + if (ks == null) + throw new IllegalStateException("store already written"); + try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) { + ks.store(stream, password); + if (logIt) + LogFactory.getLog("Taverna.Server.Worker").debug( + "serialized UBER/BC truststore (size: " + ks.size() + + ") with password \"" + + new String(password) + "\""); + return stream.toByteArray(); + } catch (IOException e) { + throw new GeneralSecurityException( + "problem serializing keystore", e); + } finally { + ks = null; + fill(password, ' '); + } + } + + @Override + protected final void finalize() { + fill(password, ' '); + ks = null; + } + } + + /** + * A trust store that can only be added to or serialized. Only trusted + * certificates can be placed in it. + * + * @author Donal Fellows + */ + class Truststore extends SecurityStore { + Truststore(char[] password) throws GeneralSecurityException { + super(password); + } + + /** + * Add a trusted certificate to the truststore. No certificates can be + * added after the truststore is serialized. + * + * @param cert + * The certificate (typically belonging to a root CA) to add. + * @throws KeyStoreException + * If anything goes wrong. + */ + public void addCertificate(Certificate cert) throws KeyStoreException { + X509Certificate c = (X509Certificate) cert; + String alias = format("trustedcert#%s#%s#%s", + getPrincipalName(c.getSubjectX500Principal()), + getPrincipalName(c.getIssuerX500Principal()), + factory.x500Utils.getSerial(c)); + setCertificate(alias, c); + if (log.isDebugEnabled() && factory.logSecurityDetails) + log.debug("added cert with alias \"" + alias + "\" of type " + + c.getClass().getCanonicalName()); + } + + /** + * Get the byte serialization of this truststore. This can only be + * fetched exactly once. + * + * @return The serialization. + * @throws GeneralSecurityException + * If anything goes wrong. + */ + public byte[] serialize() throws GeneralSecurityException { + return serialize(log.isDebugEnabled() && factory.logSecurityDetails); + } + } + + /** + * A key store that can only be added to or serialized. Only keys can be + * placed in it. + * + * @author Donal Fellows + */ + class Keystore extends SecurityStore { + Keystore(char[] password) throws GeneralSecurityException { + super(password); + } + + /** + * Add a key to the keystore. No keys can be added after the keystore is + * serialized. + * + * @param alias + * The alias of the key. + * @param key + * The secret/private key to add. + * @param trustChain + * The trusted certificate chain of the key. Should be + * <tt>null</tt> for secret keys. + * @throws KeyStoreException + * If anything goes wrong. + */ + public void addKey(String alias, Key key, Certificate[] trustChain) + throws KeyStoreException { + setKey(alias, key, trustChain); + if (log.isDebugEnabled() && factory.logSecurityDetails) + log.debug("added key with alias \"" + alias + "\" of type " + + key.getClass().getCanonicalName()); + } + + /** + * Get the byte serialization of this keystore. This can only be fetched + * exactly once. + * + * @return The serialization. + * @throws GeneralSecurityException + * If anything goes wrong. + */ + public byte[] serialize() throws GeneralSecurityException { + return serialize(log.isDebugEnabled() && factory.logSecurityDetails); + } + } +} \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextDelegateImpl.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextDelegateImpl.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextDelegateImpl.java new file mode 100644 index 0000000..d36d2da --- /dev/null +++ b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextDelegateImpl.java @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2010-2012 The University of Manchester + * + * See the file "LICENSE" for license terms. + */ +package org.taverna.server.master.worker; + +import static java.lang.String.format; +import static javax.xml.ws.handler.MessageContext.HTTP_REQUEST_HEADERS; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.rmi.RemoteException; +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Map; + +import javax.crypto.spec.SecretKeySpec; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MultivaluedMap; +import javax.xml.ws.handler.MessageContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.taverna.server.localworker.remote.RemoteSecurityContext; +import org.taverna.server.master.common.Credential; +import org.taverna.server.master.exceptions.InvalidCredentialException; +import org.taverna.server.master.utils.UsernamePrincipal; +import org.taverna.server.master.utils.X500Utils; + +/** + * Factoring out of the part of the security context handling that actually + * deals with the different types of credentials. + * + * @author Donal Fellows + */ +class SecurityContextDelegateImpl extends SecurityContextDelegate { + private static final char USERNAME_PASSWORD_SEPARATOR = '\u0000'; + private static final String USERNAME_PASSWORD_KEY_ALGORITHM = "DUMMY"; + /** What passwords are encoded as. */ + private static final Charset UTF8 = Charset.forName("UTF-8"); + + private X500Utils x500Utils; + + /** + * Initialise the context delegate. + * + * @param run + * What workflow run is this for? + * @param owner + * Who owns the workflow run? + * @param factory + * What class built this object? + */ + protected SecurityContextDelegateImpl(RemoteRunDelegate run, + UsernamePrincipal owner, SecurityContextFactory factory) { + super(run, owner, factory); + this.x500Utils = factory.x500Utils; + } + + @Override + public void validateCredential(Credential c) + throws InvalidCredentialException { + try { + if (c instanceof Credential.Password) + validatePasswordCredential((Credential.Password) c); + else if (c instanceof Credential.KeyPair) + validateKeyCredential((Credential.KeyPair) c); + else + throw new InvalidCredentialException("unknown credential type"); + } catch (InvalidCredentialException e) { + throw e; + } catch (Exception e) { + throw new InvalidCredentialException(e); + } + } + + @Override + public void addCredentialToKeystore(Credential c) throws KeyStoreException { + try { + if (c instanceof Credential.Password) + addUserPassToKeystore((Credential.Password) c); + else if (c instanceof Credential.KeyPair) + addKeypairToKeystore((Credential.KeyPair) c); + else + throw new KeyStoreException("unknown credential type"); + } catch (KeyStoreException e) { + throw e; + } catch (Exception e) { + throw new KeyStoreException(e); + } + } + + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + /** + * Tests whether the given username+password credential descriptor is valid. + * If it is invalid, an exception will be thrown describing what the problem + * is. Validation mainly consists of listing what the username is. + * + * @param passwordDescriptor + * The credential descriptor to validate. + * @throws InvalidCredentialException + * If the username is empty. NB: the password may be empty! + * That's legal (if unwise). + */ + protected void validatePasswordCredential( + Credential.Password passwordDescriptor) + throws InvalidCredentialException { + if (passwordDescriptor.username == null + || passwordDescriptor.username.trim().isEmpty()) + throw new InvalidCredentialException("absent or empty username"); + if (passwordDescriptor.serviceURI == null) + throw new InvalidCredentialException("absent service URI"); + String keyToSave = passwordDescriptor.username + + USERNAME_PASSWORD_SEPARATOR + passwordDescriptor.password; + passwordDescriptor.loadedKey = encodeKey(keyToSave); + passwordDescriptor.loadedTrustChain = null; + } + + private static Key encodeKey(String key) { + return new SecretKeySpec(key.getBytes(UTF8), + USERNAME_PASSWORD_KEY_ALGORITHM); + } + + /** + * Adds a username/password credential pair to the current keystore. + * + * @param userpassCredential + * The username and password. + * @throws KeyStoreException + */ + protected void addUserPassToKeystore(Credential.Password userpassCredential) + throws KeyStoreException { + String alias = format("password#%s", + userpassCredential.serviceURI.toASCIIString()); + addKeypairToKeystore(alias, userpassCredential); + } + + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + /** + * Tests whether the given key-pair credential descriptor is valid. If it is + * invalid, an exception will be thrown describing what the problem is. + * + * @param keypairDescriptor + * The descriptor to validate. + * @throws InvalidCredentialException + * If the descriptor is invalid + * @throws KeyStoreException + * If we don't understand the keystore type or the contents of + * the keystore + * @throws NoSuchAlgorithmException + * If the keystore is of a known type but we can't comprehend + * its security + * @throws CertificateException + * If the keystore does not include enough information about the + * trust chain of the keypair + * @throws UnrecoverableKeyException + * If we can't get the key out of the keystore + * @throws IOException + * If we can't read the keystore for prosaic reasons (e.g., file + * absent) + */ + protected void validateKeyCredential(Credential.KeyPair keypairDescriptor) + throws InvalidCredentialException, KeyStoreException, + NoSuchAlgorithmException, CertificateException, IOException, + UnrecoverableKeyException { + if (keypairDescriptor.credentialName == null + || keypairDescriptor.credentialName.trim().isEmpty()) + throw new InvalidCredentialException( + "absent or empty credentialName"); + + InputStream contentsAsStream; + if (keypairDescriptor.credentialBytes != null + && keypairDescriptor.credentialBytes.length > 0) { + contentsAsStream = new ByteArrayInputStream( + keypairDescriptor.credentialBytes); + keypairDescriptor.credentialFile = null; + } else if (keypairDescriptor.credentialFile == null + || keypairDescriptor.credentialFile.trim().isEmpty()) + throw new InvalidCredentialException( + "absent or empty credentialFile"); + else { + contentsAsStream = contents(keypairDescriptor.credentialFile); + keypairDescriptor.credentialBytes = new byte[0]; + } + if (keypairDescriptor.fileType == null + || keypairDescriptor.fileType.trim().isEmpty()) + keypairDescriptor.fileType = KeyStore.getDefaultType(); + keypairDescriptor.fileType = keypairDescriptor.fileType.trim(); + + KeyStore ks = KeyStore.getInstance(keypairDescriptor.fileType); + char[] password = keypairDescriptor.unlockPassword.toCharArray(); + ks.load(contentsAsStream, password); + + try { + keypairDescriptor.loadedKey = ks.getKey( + keypairDescriptor.credentialName, password); + } catch (UnrecoverableKeyException ignored) { + keypairDescriptor.loadedKey = ks.getKey( + keypairDescriptor.credentialName, new char[0]); + } + if (keypairDescriptor.loadedKey == null) + throw new InvalidCredentialException( + "no such credential in key store"); + keypairDescriptor.loadedTrustChain = ks + .getCertificateChain(keypairDescriptor.credentialName); + if (keypairDescriptor.loadedTrustChain == null + || keypairDescriptor.loadedTrustChain.length == 0) + throw new InvalidCredentialException( + "could not establish trust chain for credential"); + } + + /** + * Adds a key-pair to the current keystore. + * + * @param c + * The key-pair. + * @throws KeyStoreException + */ + protected void addKeypairToKeystore(Credential.KeyPair c) + throws KeyStoreException { + X509Certificate subjectCert = (X509Certificate) c.loadedTrustChain[0]; + String alias = format("keypair#%s#%s#%s", + getPrincipalName(subjectCert.getSubjectX500Principal()), + getPrincipalName(subjectCert.getIssuerX500Principal()), + x500Utils.getSerial(subjectCert)); + addKeypairToKeystore(alias, c); + } +} + +/** + * Special subclass that adds support for HELIO project security tokens. + * + * @author Donal Fellows + */ +class HelioSecurityContextDelegateImpl extends SecurityContextDelegateImpl { + /** + * Initialise the context delegate. + * + * @param run + * What workflow run is this for? + * @param owner + * Who owns the workflow run? + * @param factory + * What class built this object? + */ + protected HelioSecurityContextDelegateImpl(RemoteRunDelegate run, + UsernamePrincipal owner, SecurityContextFactory factory) { + super(run, owner, factory); + } + + private Log log = LogFactory.getLog("Taverna.Server.Worker"); + /** The name of the HTTP header holding the CIS token. */ + private static final String HELIO_CIS_TOKEN = "X-Helio-CIS"; + private transient String helioToken; + + @Override + public void initializeSecurityFromSOAPContext(MessageContext context) { + // does nothing + @SuppressWarnings("unchecked") + Map<String, List<String>> headers = (Map<String, List<String>>) context + .get(HTTP_REQUEST_HEADERS); + if (factory.supportHelioToken && headers.containsKey(HELIO_CIS_TOKEN)) + helioToken = headers.get(HELIO_CIS_TOKEN).get(0); + } + + @Override + public void initializeSecurityFromRESTContext(HttpHeaders context) { + // does nothing + MultivaluedMap<String, String> headers = context.getRequestHeaders(); + if (factory.supportHelioToken && headers.containsKey(HELIO_CIS_TOKEN)) + helioToken = headers.get(HELIO_CIS_TOKEN).get(0); + } + + @Override + protected void conveyExtraSecuritySettings(RemoteSecurityContext rc) + throws RemoteException { + try { + if (factory.supportHelioToken && helioToken != null) { + if (factory.logSecurityDetails) + log.info("transfering HELIO CIS token: " + helioToken); + rc.setHelioToken(helioToken); + } + } finally { + helioToken = null; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextFactory.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextFactory.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextFactory.java new file mode 100644 index 0000000..cbccf34 --- /dev/null +++ b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextFactory.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2011-2012 The University of Manchester + * + * See the file "LICENSE" for license terms. + */ +package org.taverna.server.master.worker; + +import static java.security.Security.addProvider; +import static java.security.Security.getProvider; +import static java.security.Security.removeProvider; +import static org.apache.commons.logging.LogFactory.getLog; +import static org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME; + +import java.io.Serializable; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import org.apache.commons.logging.Log; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.springframework.beans.factory.annotation.Required; +import org.springframework.beans.factory.annotation.Value; +import org.taverna.server.master.interfaces.TavernaRun; +import org.taverna.server.master.interfaces.UriBuilderFactory; +import org.taverna.server.master.utils.CertificateChainFetcher; +import org.taverna.server.master.utils.FilenameUtils; +import org.taverna.server.master.utils.UsernamePrincipal; +import org.taverna.server.master.utils.X500Utils; + +/** + * Singleton factory. Really is a singleton (and is also very trivial); the + * singleton-ness is just about limiting the number of instances of this around + * even when lots of serialization is going on. + * + * @see Serializable + * @author Donal Fellows + */ +public class SecurityContextFactory implements + org.taverna.server.master.interfaces.SecurityContextFactory { + private static final long serialVersionUID = 12345678987654321L; + private static SecurityContextFactory instance; + transient RunDBSupport db; + transient FilenameUtils fileUtils; + transient X500Utils x500Utils; + transient UriBuilderFactory uriSource; + transient CertificateChainFetcher certFetcher; + transient String httpRealm; + private transient PasswordIssuer passwordIssuer; + private transient BouncyCastleProvider provider; + + /** + * Whether to support HELIO CIS tokens. + */ + @Value("${helio.cis.enableTokenPassing}") + boolean supportHelioToken; + + /** + * Whether to log the details of security (passwords, etc). + */ + @Value("${log.security.details}") + boolean logSecurityDetails; + + private Log log() { + return getLog("Taverna.Server.Worker.Security"); + } + + private void installAsInstance(SecurityContextFactory handle) { + instance = handle; + } + + @PreDestroy + void removeAsSingleton() { + installAsInstance(null); + try { + if (provider != null) + removeProvider(provider.getName()); + } catch (SecurityException e) { + log().warn( + "failed to remove BouncyCastle security provider; " + + "might be OK if configured in environment", e); + } + } + + @PostConstruct + void setAsSingleton() { + installAsInstance(this); + if (getProvider(PROVIDER_NAME) == null) + try { + provider = new BouncyCastleProvider(); + if (addProvider(provider) == -1) + provider = null; + } catch (SecurityException e) { + log().warn( + "failed to install BouncyCastle security provider; " + + "might be OK if already configured", e); + provider = null; + } + } + + @Required + public void setRunDatabase(RunDBSupport db) { + this.db = db; + } + + @Required + public void setCertificateFetcher(CertificateChainFetcher fetcher) { + this.certFetcher = fetcher; + } + + @Required + public void setFilenameConverter(FilenameUtils fileUtils) { + this.fileUtils = fileUtils; + } + + @Required + public void setX500Utils(X500Utils x500Utils) { + this.x500Utils = x500Utils; + } + + @Required + public void setUriSource(UriBuilderFactory uriSource) { + this.uriSource = uriSource; + } + + @Required + public void setHttpRealm(String realm) { + this.httpRealm = realm; //${http.realmName} + } + + @Required + public void setPasswordIssuer(PasswordIssuer issuer) { + this.passwordIssuer = issuer; + } + + @Override + public SecurityContextDelegate create(TavernaRun run, + UsernamePrincipal owner) throws Exception { + Log log = log(); + if (log.isDebugEnabled()) + log.debug("constructing security context delegate for " + owner); + RemoteRunDelegate rrd = (RemoteRunDelegate) run; + return new HelioSecurityContextDelegateImpl(rrd, owner, this); + } + + private Object readResolve() { + if (instance == null) + installAsInstance(this); + return instance; + } + + public String issueNewPassword() { + return passwordIssuer.issue(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SimpleFormattedCompletionNotifier.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SimpleFormattedCompletionNotifier.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SimpleFormattedCompletionNotifier.java new file mode 100644 index 0000000..793d291 --- /dev/null +++ b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SimpleFormattedCompletionNotifier.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2010-2011 The University of Manchester + * + * See the file "LICENSE" for license terms. + */ +package org.taverna.server.master.worker; + +import static org.taverna.server.master.defaults.Default.NOTIFY_MESSAGE_FORMAT; + +import java.text.MessageFormat; + +import org.springframework.beans.factory.annotation.Required; + +/** + * Completion notifier that sends messages by email. + * + * @author Donal Fellows + */ +public class SimpleFormattedCompletionNotifier implements CompletionNotifier { + @Required + public void setName(String name) { + this.name = name; + } + + /** + * @param subject + * The subject of the notification email. + */ + @Required + public void setSubject(String subject) { + this.subject = subject; + } + + /** + * @param messageFormat + * The template for the body of the message to send. Parameter #0 + * will be substituted with the ID of the job, and parameter #1 + * will be substituted with the exit code. + */ + public void setMessageFormat(String messageFormat) { + this.format = new MessageFormat(messageFormat); + } + + private String name; + private String subject; + private MessageFormat format = new MessageFormat(NOTIFY_MESSAGE_FORMAT); + + @Override + public String makeCompletionMessage(String name, RemoteRunDelegate run, + int code) { + return format.format(new Object[] { name, code }); + } + + @Override + public String makeMessageSubject(String name, RemoteRunDelegate run, + int code) { + return subject; + } + + @Override + public String getName() { + return name; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/VelocityCompletionNotifier.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/VelocityCompletionNotifier.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/VelocityCompletionNotifier.java new file mode 100644 index 0000000..cf67853 --- /dev/null +++ b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/VelocityCompletionNotifier.java @@ -0,0 +1,105 @@ +package org.taverna.server.master.worker; + +import java.io.StringWriter; + +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; +import org.springframework.beans.factory.annotation.Required; +import org.taverna.server.master.common.version.Version; +import org.taverna.server.master.exceptions.NoListenerException; +import org.taverna.server.master.interfaces.Listener; +import org.taverna.server.master.interfaces.UriBuilderFactory; + +public class VelocityCompletionNotifier implements CompletionNotifier { + private String subject; + private VelocityEngine engine; + private Template template; + private String name; + private String templateName; + private UriBuilderFactory ubf; + + @Override + public String getName() { + return name; + } + + /** + * @param subject + * The subject of the notification email. + */ + @Required + public void setSubject(String subject) { + this.subject = subject; + } + + /** + * @param engine + * The configured Apache Velocity engine. + */ + @Required + public void setVelocityEngine(VelocityEngine engine) { + this.engine = engine; + } + + /** + * @param uriBuilderFactory + * The configured URI builder factory. + */ + @Required + public void setUriBuilderFactory(UriBuilderFactory uriBuilderFactory) { + this.ubf = uriBuilderFactory; + } + + /** + * @param name + * The name of the template. + */ + @Required + public void setName(String name) { + this.name = name; + this.templateName = getClass().getName() + "_" + name + ".vtmpl"; + } + + private Template getTemplate() { + if (template == null) + synchronized(this) { + if (template == null) + template = engine.getTemplate(templateName); + } + return template; + } + + @Override + public String makeCompletionMessage(String name, RemoteRunDelegate run, + int code) { + VelocityContext ctxt = new VelocityContext(); + ctxt.put("id", name); + ctxt.put("uriBuilder", ubf.getRunUriBuilder(run)); + ctxt.put("name", run.getName()); + ctxt.put("creationTime", run.getCreationTimestamp()); + ctxt.put("startTime", run.getStartTimestamp()); + ctxt.put("finishTime", run.getFinishTimestamp()); + ctxt.put("expiryTime", run.getExpiry()); + ctxt.put("serverVersion", Version.JAVA); + for (Listener l : run.getListeners()) + if (l.getName().equals("io")) { + for (String p : l.listProperties()) + try { + ctxt.put("prop_" + p, l.getProperty(p)); + } catch (NoListenerException e) { + // Ignore... + } + break; + } + StringWriter sw = new StringWriter(); + getTemplate().merge(ctxt, sw); + return sw.toString(); + } + + @Override + public String makeMessageSubject(String name, RemoteRunDelegate run, + int code) { + return subject; + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/WorkerModel.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/WorkerModel.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/WorkerModel.java new file mode 100644 index 0000000..1abe617 --- /dev/null +++ b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/WorkerModel.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2010-2013 The University of Manchester + * + * See the file "LICENSE" for license terms. + */ +package org.taverna.server.master.worker; + +import java.net.URI; +import java.util.List; + +import org.taverna.server.master.common.Status; + +/** + * Profile of the getters and setters in a worker system. Ensures that the + * persisted state matches the public view on the state model at least fairly + * closely. + * + * @author Donal Fellows + */ +public interface WorkerModel extends PolicyLimits { + + /** + * @param defaultLifetime + * how long a workflow run should live by default, in minutes. + */ + public abstract void setDefaultLifetime(int defaultLifetime); + + /** + * @return how long a workflow run should live by default, in minutes. + */ + public abstract int getDefaultLifetime(); + + /** + * @param maxRuns + * the maximum number of extant workflow runs + */ + public abstract void setMaxRuns(int maxRuns); + + /** + * @param factoryProcessNamePrefix + * the prefix used for factory processes in RMI + */ + public abstract void setFactoryProcessNamePrefix( + String factoryProcessNamePrefix); + + /** + * @return the prefix used for factory processes in RMI + */ + public abstract String getFactoryProcessNamePrefix(); + + /** + * @param executeWorkflowScript + * the script to run to actually run a workflow + */ + public abstract void setExecuteWorkflowScript(String executeWorkflowScript); + + /** + * @return the script to run to actually run a workflow + */ + public abstract String getExecuteWorkflowScript(); + + /** + * @param extraArgs + * the extra arguments to pass into the workflow runner + */ + public abstract void setExtraArgs(String[] extraArgs); + + /** + * @return the extra arguments to pass into the workflow runner + */ + public abstract String[] getExtraArgs(); + + /** + * @param waitSeconds + * the number of seconds to wait for subprocesses to start + */ + public abstract void setWaitSeconds(int waitSeconds); + + /** + * @return the number of seconds to wait for subprocesses to start + */ + public abstract int getWaitSeconds(); + + /** + * @param sleepMS + * milliseconds to wait between polling for a started + * subprocess's status + */ + public abstract void setSleepMS(int sleepMS); + + /** + * @return milliseconds to wait between polling for a started subprocess's + * status + */ + public abstract int getSleepMS(); + + /** + * @param serverWorkerJar + * the full path name of the file system access worker + * subprocess's implementation JAR + */ + public abstract void setServerWorkerJar(String serverWorkerJar); + + /** + * @return the full path name of the file system access worker subprocess's + * implementation JAR + */ + public abstract String getServerWorkerJar(); + + /** + * @param javaBinary + * the full path name to the Java binary to use + */ + public abstract void setJavaBinary(String javaBinary); + + /** + * @return the full path name to the Java binary to use + */ + public abstract String getJavaBinary(); + + /** + * @param registryPort + * what port is the RMI registry on + */ + public abstract void setRegistryPort(int registryPort); + + /** + * @return what port is the RMI registry on + */ + public abstract int getRegistryPort(); + + /** + * @param registryHost + * what host (network interface) is the RMI registry on + */ + public abstract void setRegistryHost(String registryHost); + + /** + * @return what host (network interface) is the RMI registry on + */ + public abstract String getRegistryHost(); + + /** + * @param serverForkerJar + * the full path name of the impersonation engine's + * implementation JAR + */ + public abstract void setServerForkerJar(String serverForkerJar); + + /** + * @return the full path name of the impersonation engine's implementation + * JAR + */ + public abstract String getServerForkerJar(); + + /** + * @param passwordFile + * the full path name of a file containing a password to use with + * sudo (or empty for none) + */ + public abstract void setPasswordFile(String passwordFile); + + /** + * @return the full path name of a file containing a password to use with + * sudo (or empty for none) + */ + public abstract String getPasswordFile(); + + /** + * @param operatingLimit + * the maximum number of runs in the + * {@linkplain Status#Operating operating} state at once + */ + public abstract void setOperatingLimit(int operatingLimit); + + @Override + void setPermittedWorkflowURIs(List<URI> permittedWorkflows); + + /** + * @return the full path name of the RMI registry subprocess's + * implementation JAR + */ + String getRegistryJar(); + + /** + * @param rmiRegistryJar + * the full path name of the RMI registry subprocess's + * implementation JAR + */ + void setRegistryJar(String rmiRegistryJar); + + /** + * @return whether a run should generate provenance information by default + */ + boolean getGenerateProvenance(); + + /** + * @param generateProvenance + * whether a run should generate provenance information by + * default + */ + void setGenerateProvenance(boolean generateProvenance); +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/package-info.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/package-info.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/package-info.java new file mode 100644 index 0000000..6007f88 --- /dev/null +++ b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/package-info.java @@ -0,0 +1,10 @@ +/* + * Copyright (C) 2013 The University of Manchester + * + * See the file "LICENSE" for license terms. + */ +/** + * A Taverna Server back-end that works by forking off workflow executors. + */ +package org.taverna.server.master.worker; + http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/replacementscripts/executeworkflow.bat ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/replacementscripts/executeworkflow.bat b/taverna-server-webapp/src/main/replacementscripts/executeworkflow.bat new file mode 100644 index 0000000..c678855 --- /dev/null +++ b/taverna-server-webapp/src/main/replacementscripts/executeworkflow.bat @@ -0,0 +1,25 @@ +@ECHO OFF + +REM Taverna startup script + +REM distribution directory +set TAVERNA_HOME=%~dp0 + +REM 300 MB memory, 140 MB for classes +set ARGS=-Xmx300m -XX:MaxPermSize=140m + +REM Taverna system properties +set ARGS=%ARGS% "-Draven.profile=file:%TAVERNA_HOME%conf/current-profile.xml" +set ARGS=%ARGS% -Djava.system.class.loader=net.sf.taverna.raven.prelauncher.BootstrapClassLoader +set ARGS=%ARGS% -Draven.launcher.app.main=net.sf.taverna.t2.commandline.CommandLineLauncher +set ARGS=%ARGS% -Draven.launcher.show_splashscreen=false +set ARGS=%ARGS% -Djava.awt.headless=true +set ARGS=%ARGS% "-Dtaverna.startup=%TAVERNA_HOME%." +IF NOT x%RAVEN_APPHOME%==x SET ARGS=%ARGS% "-Draven.launcher.app.home=%RAVEN_APPHOME%" +IF NOT x%TAVERNA_RUN_ID%==x SET ARGS=%ARGS% "-Dtaverna.runid=%TAVERNA_RUN_ID%" +IF NOT x%INTERACTION_HOST%==x SET ARGS=%ARGS% "-Dtaverna.interaction.host=%INTERACTION_HOST%" +IF NOT x%INTERACTION_PORT%==x SET ARGS=%ARGS% "-Dtaverna.interaction.port=%INTERACTION_PORT%" +IF NOT x%INTERACTION_WEBDAV%==x SET ARGS=%ARGS% "-Dtaverna.interaction.webdav_path=%INTERACTION_WEBDAV%" +IF NOT x%INTERACTION_FEED%==x SET ARGS=%ARGS% "-Dtaverna.interaction.feed_path=%INTERACTION_FEED%" + +java %ARGS% -jar "%TAVERNA_HOME%lib\prelauncher-2.3.jar" %* http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/replacementscripts/executeworkflow.sh ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/replacementscripts/executeworkflow.sh b/taverna-server-webapp/src/main/replacementscripts/executeworkflow.sh new file mode 100644 index 0000000..e9e1d36 --- /dev/null +++ b/taverna-server-webapp/src/main/replacementscripts/executeworkflow.sh @@ -0,0 +1,72 @@ +#!/bin/sh + +set -e + +# 300 MB memory, 140 MB for classes +memlimit=-Xmx300m +permsize=-XX:MaxPermSize=140m + +## Parse the command line to extract the pieces to move around to before or +## after the JAR filename... +pre=-Djava.awt.headless=true +post= +for arg +do + case $arg in + -JXmx*) memlimit=`echo $arg | sed 's/-JX/-X/'` ;; + -JXX:MaxPermSize=*) permsize=`echo $arg | sed 's/-JXX/-XX/'` ;; + -J*) pre="$pre `echo $arg | sed 's/-J/-/'`" ;; + -D*) pre="$pre $arg" ;; + *) post="$post \"$arg\"" ;; + esac +done +if test "xx" = "x${post}x"; then + echo "Missing arguments! Bug in argument processing?" >&2 + exit 1 +fi +eval set x $post +shift + +## resolve links - $0 may be a symlink +prog="$0" + +real_path() { + readlink -m "$1" 2>/dev/null || python -c 'import os,sys;print os.path.realpath(sys.argv[1])' "$1" +} + +realprog=`real_path "$prog"` +taverna_home=`dirname "$realprog"` +javabin=java +if test -x "$JAVA_HOME/bin/java"; then + javabin="$JAVA_HOME/bin/java" +fi +APPHOME_PROP= +if test x != "x$TAVERNA_APPHOME"; then + APPHOME_PROP="-Dtaverna.app.home=$TAVERNA_APPHOME" +fi +RUNID_PROP= +if test x != "x$TAVERNA_RUN_ID"; then + RUNID_PROP="-Dtaverna.runid=$TAVERNA_RUN_ID" +fi +INTERACTION_PROPS=-Dtaverna.interaction.ignore_requests=true +if test x != "x$INTERACTION_HOST"; then + INTERACTION_PROPS="$INTERACTION_PROPS -Dtaverna.interaction.host=$INTERACTION_HOST" + INTERACTION_PROPS="$INTERACTION_PROPS -Dtaverna.interaction.port=$INTERACTION_PORT" + INTERACTION_PROPS="$INTERACTION_PROPS -Dtaverna.interaction.webdav_path=$INTERACTION_WEBDAV" + INTERACTION_PROPS="$INTERACTION_PROPS -Dtaverna.interaction.feed_path=$INTERACTION_FEED" + if test x != "x$INTERACTION_PUBLISH"; then + INTERACTION_PROPS="$INTERACTION_PROPS -Dtaverna.interaction.publishAddressOverride=$INTERACTION_PUBLISH" + fi +fi + +MainClass=net.sf.taverna.t2.commandline.CommandLineLauncher + +echo "pid:$$" +exec "$javabin" $memlimit $permsize \ + "-Dlog4j.configuration=file://$taverna_home/conf/log4j.properties " \ + "-Djava.util.logging.config.file=$taverna_home/conf/logging.properties " \ + "-Dtaverna.app.startup=$taverna_home" -Dtaverna.interaction.ignore_requests=true \ + $APPHOME_PROP $RUNID_PROP $INTERACTION_PROPS -Djava.awt.headless=true \ + -Dcom.sun.net.ssl.enableECC=false -Djsse.enableSNIExtension=false $pre \ + -jar "$taverna_home/lib/taverna-command-line-0.1.1.jar" \ + ${1+"$@"} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/resources/admin.html ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/resources/admin.html b/taverna-server-webapp/src/main/resources/admin.html new file mode 100644 index 0000000..a80a783 --- /dev/null +++ b/taverna-server-webapp/src/main/resources/admin.html @@ -0,0 +1,240 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> +<title>Taverna Server ${project.version} Administration Interface</title> +<link id="admin" href="admin" /> +<script type="text/javascript" src="admin/static/jquery-1.8.0.min.js"></script> +<script type="text/javascript" src="admin/static/jquery-ui-1.8.23.custom.min.js"></script> +<script type="text/javascript" src="admin/static/admin.js"></script> +<link href="admin/static/jquery-ui-1.8.23.custom.css" rel="stylesheet" type="text/css" /> + +</head> +<body> +<img height="70" style="float:left" src="admin/static/t2cogs.png"> +<h1>Taverna Server ${project.version} Administration Interface</h1> +<br clear="left"/> +<div id="body"> +<ul> + <li><a href="#t-global">Global Settings</a></li> + <li><a href="#t-users">Users</a></li> + <li><a href="#t-workflows">Workflows</a></li> + <li><a href="#t-usage">Usage Records</a></li> + <li><a href="#t-worker">Local Worker Configuration</a></li> +</ul> + +<div id="t-global"> +<label title="The number of invocations of the main interface webapp that have been done. Be aware that one service call can result in many invocations due to resource resolution." for="invokationCount">Invocation Count:</label> +<span title="The number of invocations of the main interface webapp that have been done. Be aware that one service call can result in many invocations due to resource resolution." id="invokationCount">0</span> +<br> +<label title="The number of runs that currently exist." for="runCount">Run Count:</label> +<span title="The number of runs that currently exist." id="runCount">0</span> +<br> +<label title="The number of runs that are currently operating." for="operatingCount">Operating Run Count:</label> +<span title="The number of runs that are currently operating." id="operatingCount">0</span> +<br> +<label title="Whether workflow runs should create provenance traces by default. Users can explicitly override this." for="generateProvenance">Generate Provenance by Default</label> +<input type="checkbox" id="generateProvenance" /> +<br> +<label title="The time it took for the back-end engine to start up, in seconds. Should usually be short." for="startupTime">Back-End Startup Time (seconds):</label> +<span title="The time it took for the back-end engine to start up, in seconds. Should usually be short." id="startupTime">0</span> +<br> +<label title="The exit code from the last time the back-end was shut down. Blank if the back end has never been shut down while the current webapp instance is running (i.e., since the last boot of the container)." for="lastExitCode">Back-End Last Exit Code:</label> +<span title="The exit code from the last time the back-end was shut down. Blank if the back end has never been shut down while the current webapp instance is running (i.e., since the last boot of the container)." id="lastExitCode"></span> +<p> +<label title="Whether new workflow runs should be created. Disabling this does not prevent existing runs from executing." for="allowNew">Allow New Runs</label> +<input type="checkbox" id="allowNew" /> +<label title="Whether to record the workflows being run by users. Very noisy due to length of workflow documents, occasionally useful." for="logWorkflows">Log Executed Workflows</label> +<input type="checkbox" id="logWorkflows" /> +<label title="Whether to record exceptions generated by users in the code (as well as converting them to faults and error responses). Useful for debugging, but noisy." for="logFaults">Log User Exceptions</label> +<input type="checkbox" id="logFaults" /> +<p> +<label title="The maximum number of workflow runs that can exist at once, in any state." for="runLimit">Maximum Simultaneous Existing Workflow Runs</label> +<input title="The maximum number of workflow runs that can exist at once, in any state." id="runLimit" size="3" /> +<br> +<label title="The maximum number of workflow runs that can be executing at once." for="operatingLimit">Maximum Simultaneous Executing Workflow Runs</label> +<input title="The maximum number of workflow runs that can be executing at once." id="operatingLimit" size="3" /> +<br> +<label title="How long to allow a workflow to execute for by default (clients can change this), in minutes." for="defaultLifetime">Default Run Lifetime (minutes)</label> +<input title="How long to allow a workflow to execute for by default (clients can change this), in minutes." id="defaultLifetime" size="7" /> +</div><!-- t-global --> + +<div id="t-users"> +<table id="userList"> + <tr><th>Username<th>System Username</tr> +</table> +<h3>Add a user</h3> +<table border=1> + <tr> + <td><label title="The user name to create." for="newUsername">Username</label> + <td><input title="The user name to create." size=12 id="newUsername" /> + </tr> + <tr> + <td><label title="The password to use for the user." for="newPassword">Password</label> + <td><input title="The password to use for the user." size=12 id="newPassword" type="password"/> + </tr> + <tr> + <td><label title="The system account to run the user's workflows in; leave blank for the default." for="newSysID">System ID</label> + <td><input title="The system account to run the user's workflows in; leave blank for the default." size=12 id="newSysID" /> + </tr> + <tr><td colspan=2> + <label title="Whether to allow this user to log in at all." for="newEnabled">Enabled</label> + <input type="checkbox" id="newEnabled" /> + <label title="Whether the user has administrative privileges (can see all workflow runs, can access the administration page)." for="newAdmin">Admin</label> + <input type="checkbox" id="newAdmin" /> + </td></tr> + <tr><td colspan=2> + <button id="makeNewUser">Create a new user</button> + </td></tr> +</table> +</div><!-- t-users --> + +<div id="t-workflows"> +<label title="Workflow URIs to limit execution to." for="workflows">Workflow URIs (one per line)</label> +<br> +<textarea title="Workflow URIs to limit execution to." rows="5" cols="60" id="workflows"></textarea> +<p> +<button id="saveWorkflows">Save</button> <button id="refreshWorkflows">Refresh</button> <button id="emptyWorkflows">Empty URIs list</button> +</div> + +<div id="t-usage"> +Download <a href="#" id="ur">usage records</a> (warning: may be slow!) +<p> +<label title="The name of a file to write usage records to. Note that this file will end up containing many XML documents concatenated together; it is up to you to split them up as necessary. Each record is only written as it is generated; this does not produce historic data." for="usageRecordDumpFile">Usage Record Dump File</label> +<input title="The name of a file to write usage records to. Note that this file will end up containing many XML documents concatenated together; it is up to you to split them up as necessary. Each record is only written as it is generated; this does not produce historic data." id="usageRecordDumpFile" size="50" /> +</div><!-- t-usage --> + +<div id="t-worker"> + <div id="a-worker"> + + <h3><a href="#">Subprocess Implementation Control</a></h3> + <div> + <table> + <tr> + <td> <label title="The full path of the Java executable to use. Normally set correct by default." for="javaBinary">Java Executable (for subprocesses):</label> </td> + <td> <input title="The full path of the Java executable to use. Normally set correct by default." id="javaBinary" size="80" /> </td> + </tr> + <tr> + <td> <label title="The full path of the secure subprocess fork engine to use. Normally set correct by default." for="serverForkerJar">Subprocess Factory JAR:</label> </td> + <td> <input title="The full path of the secure subprocess fork engine to use. Normally set correct by default." id="serverForkerJar" size="80" /> </td> + </tr> + <tr> + <td> <label title="The full path of a file containing the credentials to use with sudo. Leave blank to use a password-less connection (see documentation for how to configure)." for="runasPasswordFile">File with password for sudo:</label> </td> + <td> <input title="The full path of a file containing the credentials to use with sudo. Leave blank to use a password-less connection (see documentation for how to configure)." id="runasPasswordFile" size="80" /> </td> + </tr> + <tr> + <td> <label title="The full path of the user filesystem access and workflow initiation engine to use. Normally set correct by default." for="serverWorkerJar">User Filesystem Access JAR:</label> </td> + <td> <input title="The full path of the user filesystem access and workflow initiation engine to use. Normally set correct by default." id="serverWorkerJar" size="80" /> </td> + </tr> + <tr> + <td> <label title="The full path of the workflow engine executable. Normally set correctly by default." for="executeWorkflowScript">Workflow Engine Executable:</label> </td> + <td> <input title="The full path of the workflow engine executable. Normally set correctly by default." id="executeWorkflowScript" size="80" /> </td> + </tr> + </table> + </div> + + <h3><a href="#">Worker Registration Control</a></h3> + <div> + <label title="The machine hosting the RMI registry. WARNING: changing this will probably break your configuration! Contact the myGrid team for help before adjusting!" for="registryHost">Registry Host</label> + <input title="The machine hosting the RMI registry. WARNING: changing this will probably break your configuration! Contact the myGrid team for help before adjusting!" id="registryHost" size="20" /> + <label title="The port number the RMI registry. WARNING: changing this will probably break your configuration! Contact the myGrid team for help before adjusting!" for="registryPort">Port</label> + <input title="The port number the RMI registry. WARNING: changing this will probably break your configuration! Contact the myGrid team for help before adjusting!" id="registryPort" size="5" /> + <br> + <label title="The full path of the RMI registry implementation JAR file. WARNING: changing this will probably break your configuration! Contact the myGrid team for help before adjusting!" for="registryJar">RMI Registry JAR</label> + <input title="The full path of the RMI registry implementation JAR file. WARNING: changing this will probably break your configuration! Contact the myGrid team for help before adjusting!" id="registryJar" size="80" /> + <br> + <label title="The time to wait (in seconds) for the back-end processes to boot and register themselves with the RMI registry. Busy machines may need a longer value here." for="registrationWaitSeconds">Time to wait for registration (seconds)</label> + <input title="The time to wait (in seconds) for the back-end processes to boot and register themselves with the RMI registry. Busy machines may need a longer value here." id="registrationWaitSeconds" size="5" /> + <br> + <label title="How long to wait (in milliseconds) between probes to the registry to detect the registration of a back-end process." for="registrationPollMillis">Time to wait between polling to + detect registration (milliseconds)</label> + <input title="How long to wait (in milliseconds) between probes to the registry to detect the registration of a back-end process." id="registrationPollMillis" size="5" /> + </div> + + <h3><a href="#">System User/Factory ID Mapping</a></h3> + <div> + <table title="The mapping of system user IDs to factory identifiers (used in the RMI registry). Note that this is read-only." id="factoryProcessMapping" border="1"> + </table> + </div> + + <h3><a href="#">Extra Workflow Engine Configuration</a></h3> + <div> + <h4>System Properties</h4> + <table id="extraArguments-prop"> + <tr><td></td><td><button title="Add a system property to pass to the back-end engine." id="extra-prop-add">Add System Property</button></td></tr> + </table> + <h4>Environment Variables</h4> + <table id="extraArguments-env"> + <tr><td></td><td><button title="Add an environment variable to pass to the back-end engine." id="extra-env-add">Add Environment Variable</button></td></tr> + </table> + <h4>Java Runtime Configuration</h4> + <table id="extraArguments-runtime"> + <tr><td></td><td><button title="Add a Java runtime parameter (e.g., Xmx=400m to set the memory usage limit to 400MB) to pass to the back-end engine. Note the lack of a leading '-' character!" id="extra-run-add">Add Runtime Configuration</button></td></tr> + </table> + </div> + + </div><!-- a-worker --> +</div><!-- t-worker --> + +</div> + +<hr> +<address>Donal Fellows / University of Manchester</address> + +<!-- DIALOG BOXES --> +<div id="dialog-confirm" title="Delete user?" style="display: none"> + <p> + <span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;"></span> + This user will be permanently deleted from the system. Are you sure? + </p> +</div> + +<div id="dialog-password" title="Change password?" style="display: none"> + <p> + <span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;"></span> + This will permanently change the user's password. Make sure you wish + to do this. + </p> + <p> + <input title="New password" id="change-password" type="password" size="12" /> + <br> + Please repeat it to be sure... + <br> + <input title="New password (again)" id="change-password2" type="password" size="12" /> + </p> +</div> + +<div id="dialog-environment" title="Set environment variable?" style="display: none"> + <p> + <span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;"></span> + Set an environment variable to be passed to the workflow engine. + </p> + <p> + <input title="Environment variable name" id="env-key" size="15" /> = + <input title="Environment variable value" id="env-value" size="20" /> + </p> +</div> + +<div id="dialog-runtime" title="Set runtime configuration?" style="display: none"> + <p> + <span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;"></span> + Set a runtime parameter (e.g., -Xmx400m for a 400MB memory limit) for the Java runtime. + </p> + <p> + <input title="Java runtime configuration parameter" id="runtime-value" size="20" /> + </p> +</div> + +<div id="dialog-property" title="Set runtime property?" style="display: none"> + <p> + <span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;"></span> + Set a configuration property for the Java runtime. + </p> + <p> + <input title="System property name" id="prop-key" size="15" /> = + <input title="System property value" id="prop-value" size="20" /> + </p> +</div> + +</body> +</html> http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/resources/capabilities.properties ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/resources/capabilities.properties b/taverna-server-webapp/src/main/resources/capabilities.properties new file mode 100644 index 0000000..2b4844f --- /dev/null +++ b/taverna-server-webapp/src/main/resources/capabilities.properties @@ -0,0 +1,38 @@ +# This is currently a hand-curated list. This sucks! + +######## --- PLATFORM --- ######## +http\://ns.taverna.org.uk/2013/software/taverna = 2.5 + +######## --- OUTPUTS/PROVENANCE --- ######## +http\://ns.taverna.org.uk/2013/bundle/run = 1.0 +http\://ns.taverna.org.uk/2013/provenance/prov = 1.0 + +######## --- ACTIVITIES --- ######## +http\://ns.taverna.org.uk/2010/activity/nested-workflow = 1.5 +http\://ns.taverna.org.uk/2010/activity/apiconsumer = 1.5 +http\://ns.taverna.org.uk/2010/activity/beanshell = 1.5 +http\://ns.taverna.org.uk/2010/activity/localworker = 1.5 +http\://ns.taverna.org.uk/2010/activity/biomart = 1.5 +http\://ns.taverna.org.uk/2010/activity/biomoby/object = 1.5 +http\://ns.taverna.org.uk/2010/activity/biomoby/service = 1.5 +http\://ns.taverna.org.uk/2010/activity/rshell = 1.5 +http\://ns.taverna.org.uk/2010/activity/soaplab = 1.5 +http\://ns.taverna.org.uk/2010/activity/spreadsheet-import = 1.5 +http\://ns.taverna.org.uk/2010/activity/constant = 1.5 +http\://ns.taverna.org.uk/2010/activity/component = 1.5 +http\://ns.taverna.org.uk/2010/activity/wsdl = 1.5 +http\://ns.taverna.org.uk/2010/activity/wsdl/xml-splitter/in = 1.5 +http\://ns.taverna.org.uk/2010/activity/wsdl/xml-splitter/out = 1.5 +http\://ns.taverna.org.uk/2010/activity/tool = 1.5 +http\://ns.taverna.org.uk/2010/activity/rest = 1.5 +http\://ns.taverna.org.uk/2010/activity/xpath = 1.5 +http\://ns.taverna.org.uk/2010/activity/webdav = 1.5 +http\://ns.taverna.org.uk/2010/activity/interaction = 1.5 + +######## --- DISPATCH LAYERS --- ######## +http\://ns.taverna.org.uk/2010/scufl2/taverna/dispatchlayer/ErrorBounce = 1.5 +http\://ns.taverna.org.uk/2010/scufl2/taverna/dispatchlayer/Failover = 1.5 +http\://ns.taverna.org.uk/2010/scufl2/taverna/dispatchlayer/Invoke = 1.5 +http\://ns.taverna.org.uk/2010/scufl2/taverna/dispatchlayer/Loop = 1.5 +http\://ns.taverna.org.uk/2010/scufl2/taverna/dispatchlayer/Parallelize = 1.5 +http\://ns.taverna.org.uk/2010/scufl2/taverna/dispatchlayer/Retry = 1.5 \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/resources/log4j.properties ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/resources/log4j.properties b/taverna-server-webapp/src/main/resources/log4j.properties new file mode 100644 index 0000000..ea0ea12 --- /dev/null +++ b/taverna-server-webapp/src/main/resources/log4j.properties @@ -0,0 +1,39 @@ +log4j.rootLogger=info, R +log4j.category.DataNucleus.Query=warn +#log4j.category.DataNucleus.Datastore.Schema=debug +#log4j.logger.org.springframework.security=DEBUG +#log4j.category.Taverna=debug + +log4j.appender.R=org.apache.log4j.RollingFileAppender +log4j.appender.R.File=${catalina.home}/logs/tavserv.out +log4j.appender.R.MaxFileSize=10MB +log4j.appender.R.MaxBackupIndex=30 +log4j.appender.R.layout=org.apache.log4j.PatternLayout +log4j.appender.R.layout.ConversionPattern=%d{yyyyMMdd'T'HHmmss.SSS} %-5p %c{1} %C{1} - %m%n + +#log4j.category.Taverna=INFO, A1 +#log4j.category.Taverna.Server.LocalWorker.RunDB=INFO +#log4j.category.Taverna.Server.Webapp=INFO +#log4j.category.Taverna.Server.LocalWorker.Policy=INFO +#log4j.category.Taverna.Server.LocalWorker.Security=INFO +## Swallow Derby's messages +#log4j.category.Derby=WARN, B2 +## Will you _shut up_, DataNucleus! <hits with rolled-up newspaper> +#log4j.category.DataNucleus=WARN, B2 +##log4j.category.DataNucleus.SchemaTool=DEBUG +##log4j.category.DataNucleus.Datastore.Schema=DEBUG +##log4j.category.DataNucleus.Datastore.Native=DEBUG +##log4j.logger.org.springframework.security=DEBUG, B2 +#log4j.category.org.springframework=INFO, B2 +#log4j.category.org.apache.cxf=INFO, B2 +#log4j.category.org.apache.cxf.jaxrs.utils.JAXRSUtils=INFO +#log4j.category.eu.medsea=INFO, B2 +#log4j.category.org.apache.axiom=INFO, B2 +## Appender for Taverna Server components +#log4j.appender.A1=org.apache.log4j.ConsoleAppender +#log4j.appender.A1.layout=org.apache.log4j.PatternLayout +#log4j.appender.A1.layout.ConversionPattern=%d{yyyyMMdd'T'HHmmss.SSS} %-5p %c{1} %C{1} - %m%n +## Appender for Framework components +#log4j.appender.B2=org.apache.log4j.ConsoleAppender +#log4j.appender.B2.layout=org.apache.log4j.PatternLayout +#log4j.appender.B2.layout.ConversionPattern=%d{yyyyMMdd'T'HHmmss.SSS} %-5p %c{1} - %m%n http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/resources/security.policy ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/resources/security.policy b/taverna-server-webapp/src/main/resources/security.policy new file mode 100644 index 0000000..1ec4166 --- /dev/null +++ b/taverna-server-webapp/src/main/resources/security.policy @@ -0,0 +1,3 @@ +grant { + permission java.security.AllPermission "*:*"; +};
