diff --git a/cadcAccessControl-Identity/build.xml b/cadcAccessControl-Identity/build.xml new file mode 100644 index 0000000000000000000000000000000000000000..b4e08ad9db4f89f5e3781c9213c0faa4f42020f5 --- /dev/null +++ b/cadcAccessControl-Identity/build.xml @@ -0,0 +1,119 @@ +<!-- +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2009. (c) 2009. +* Government of Canada Gouvernement du Canada +* National Research Council Conseil national de recherches +* Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 +* All rights reserved Tous droits réservés +* +* NRC disclaims any warranties, Le CNRC dénie toute garantie +* expressed, implied, or énoncée, implicite ou légale, +* statutory, of any kind with de quelque nature que ce +* respect to the software, soit, concernant le logiciel, +* including without limitation y compris sans restriction +* any warranty of merchantability toute garantie de valeur +* or fitness for a particular marchande ou de pertinence +* purpose. NRC shall not be pour un usage particulier. +* liable in any event for any Le CNRC ne pourra en aucun cas +* damages, whether direct or être tenu responsable de tout +* indirect, special or general, dommage, direct ou indirect, +* consequential or incidental, particulier ou général, +* arising from the use of the accessoire ou fortuit, résultant +* software. Neither the name de l'utilisation du logiciel. Ni +* of the National Research le nom du Conseil National de +* Council of Canada nor the Recherches du Canada ni les noms +* names of its contributors may de ses participants ne peuvent +* be used to endorse or promote être utilisés pour approuver ou +* products derived from this promouvoir les produits dérivés +* software without specific prior de ce logiciel sans autorisation +* written permission. préalable et particulière +* par écrit. +* +* This file is part of the Ce fichier fait partie du projet +* OpenCADC project. OpenCADC. +* +* OpenCADC is free software: OpenCADC est un logiciel libre ; +* you can redistribute it and/or vous pouvez le redistribuer ou le +* modify it under the terms of modifier suivant les termes de +* the GNU Affero General Public la “GNU Affero General Public +* License as published by the License” telle que publiée +* Free Software Foundation, par la Free Software Foundation +* either version 3 of the : soit la version 3 de cette +* License, or (at your option) licence, soit (à votre gré) +* any later version. toute version ultérieure. +* +* OpenCADC is distributed in the OpenCADC est distribué +* hope that it will be useful, dans l’espoir qu’il vous +* but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE +* without even the implied GARANTIE : sans même la garantie +* warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ +* or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF +* PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence +* General Public License for Générale Publique GNU Affero +* more details. pour plus de détails. +* +* You should have received Vous devriez avoir reçu une +* a copy of the GNU Affero copie de la Licence Générale +* General Public License along Publique GNU Affero avec +* with OpenCADC. If not, see OpenCADC ; si ce n’est +* <http://www.gnu.org/licenses/>. pas le cas, consultez : +* <http://www.gnu.org/licenses/>. +* +* $Revision: 4 $ +* +************************************************************************ +--> + +<!DOCTYPE project> +<project default="build" basedir="."> + <property environment="env"/> + <property file="local.build.properties" /> + + <!-- site-specific build properties or overrides of values in opencadc.properties --> + <property file="${env.CADC_PREFIX}/etc/local.properties" /> + + <!-- site-specific targets, e.g. install, cannot duplicate those in opencadc.targets.xml --> + <import file="${env.CADC_PREFIX}/etc/local.targets.xml" optional="true" /> + + <!-- default properties and targets --> + <property file="${env.CADC_PREFIX}/etc/opencadc.properties" /> + <import file="${env.CADC_PREFIX}/etc/opencadc.targets.xml"/> + + <!-- developer convenience: place for extra targets and properties --> + <import file="extras.xml" optional="true" /> + + <property name="project" value="cadcAccessControl-Identity" /> + + <property name="cadcUtil" value="${lib}/cadcUtil.jar" /> + <property name="cadcRegistry" value="${lib}/cadcRegistry.jar" /> + <property name="cadcLog" value="${lib}/cadcLog.jar" /> + <property name="cadcVOSI" value="${lib}/cadcVOSI.jar" /> + <property name="cadcAccessControl" value="${lib}/cadcAccessControl.jar" /> + + <property name="log4j" value="${ext.lib}/log4j.jar" /> + + <property name="jars" value="${log4j}:${cadcUtil}:${cadcRegistry}:${cadcLog}:${cadcVOSI}:${cadcAccessControl}" /> + + <target name="build" depends="compile"> + <jar jarfile="${build}/lib/${project}.jar" + basedir="${build}/class" + update="no"> + <include name="ca/nrc/cadc/**" /> + </jar> + </target> + + <!-- JAR files needed to run the test suite --> + <property name="xerces" value="${ext.lib}/xerces.jar" /> + <property name="asm" value="${ext.dev}/asm.jar" /> + <property name="cglib" value="${ext.dev}/cglib.jar" /> + <property name="easymock" value="${ext.dev}/easymock.jar" /> + <property name="junit" value="${ext.dev}/junit.jar" /> + <property name="objenesis" value="${ext.dev}/objenesis.jar" /> + <property name="jsonassert" value="${ext.dev}/jsonassert.jar" /> + + <property name="testingJars" value="${build}/class:${jsonassert}:${jars}:${xerces}:${asm}:${cglib}:${easymock}:${junit}:${objenesis}" /> + +</project> diff --git a/cadcAccessControl-Identity/src/ca/nrc/cadc/ac/auth/ACIdentityManager.java b/cadcAccessControl-Identity/src/ca/nrc/cadc/ac/auth/ACIdentityManager.java new file mode 100644 index 0000000000000000000000000000000000000000..7ea424eaead567d87224184be36d673c362235e5 --- /dev/null +++ b/cadcAccessControl-Identity/src/ca/nrc/cadc/ac/auth/ACIdentityManager.java @@ -0,0 +1,249 @@ +package ca.nrc.cadc.ac.auth; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.security.Principal; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.sql.Types; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import javax.security.auth.Subject; +import javax.security.auth.x500.X500Principal; + +import org.apache.log4j.Logger; + +import ca.nrc.cadc.ac.AC; +import ca.nrc.cadc.ac.User; +import ca.nrc.cadc.ac.client.UserClient; +import ca.nrc.cadc.auth.IdentityManager; +import ca.nrc.cadc.auth.NumericPrincipal; +import ca.nrc.cadc.auth.SSLUtil; +import ca.nrc.cadc.profiler.Profiler; +import ca.nrc.cadc.reg.client.RegistryClient; +import ca.nrc.cadc.vosi.avail.CheckResource; +import ca.nrc.cadc.vosi.avail.CheckWebService; + +/** + * AC implementation of the IdentityManager interface. This + * implementation returns the NumericPrincipal. + * + * @author pdowler + */ +public class ACIdentityManager implements IdentityManager +{ + private static final Logger log = Logger.getLogger(ACIdentityManager.class); + + private static final File DEFAULT_PRIVILEGED_PEM_FILE = new File(System.getProperty("user.home") + "/.ssl/cadcproxy.pem"); + private static final String ALT_PEM_KEY = ACIdentityManager.class.getName() + ".pemfile"; + + private File privilegedPemFile; + + public ACIdentityManager() + { + privilegedPemFile = DEFAULT_PRIVILEGED_PEM_FILE; + String altPemFile = System.getProperty(ALT_PEM_KEY); + if (altPemFile != null) + { + privilegedPemFile = new File(altPemFile); + } + } + + /** + * Returns a storage type constant from java.sql.Types. + * + * @return Types.INTEGER + */ + public int getOwnerType() + { + return Types.INTEGER; + } + + /** + * Returns a value of type specified by getOwnerType() for storage. + * + * @param subject + * @return an Integer internal CADC ID + */ + public Object toOwner(Subject subject) + { + X500Principal x500Principal = null; + if (subject != null) + { + Set<Principal> principals = subject.getPrincipals(); + for (Principal principal : principals) + { + if (principal instanceof NumericPrincipal) + { + NumericPrincipal cp = (NumericPrincipal) principal; + UUID id = cp.getUUID(); + Long l = Long.valueOf(id.getLeastSignificantBits()); + return l.intValue(); + } + if (principal instanceof X500Principal) + { + x500Principal = (X500Principal) principal; + } + } + } + + if (x500Principal == null) + { + return null; + } + + // The user has connected with a valid client cert but does + // not have an account (no numeric principal). + // Create an auto-approved account with their x500Principal. + NumericPrincipal numericPrincipal = createX500User(x500Principal); + subject.getPrincipals().add(numericPrincipal); + return Long.valueOf(numericPrincipal.getUUID().getLeastSignificantBits()); + } + + private NumericPrincipal createX500User(final X500Principal x500Principal) + { + PrivilegedExceptionAction<NumericPrincipal> action = new PrivilegedExceptionAction<NumericPrincipal>() + { + @Override + public NumericPrincipal run() throws Exception + { + UserClient userClient = new UserClient(new URI(AC.UMS_SERVICE_URI)); + User newUser = userClient.createUser(x500Principal); + + Set<NumericPrincipal> set = newUser.getIdentities(NumericPrincipal.class); + if (set.isEmpty()) + { + throw new IllegalStateException("missing internal id"); + } + return set.iterator().next(); + } + }; + + Subject servopsSubject = SSLUtil.createSubject(privilegedPemFile); + try + { + return Subject.doAs(servopsSubject, action); + } + catch (Exception e) + { + throw new IllegalStateException("failed to create internal id for user " + x500Principal.getName(), e); + } + } + + /** + * Get a consistent string representation of the user. + * + * @param subject + * @return an X509 distinguished name + */ + public String toOwnerString(Subject subject) + { + if (subject != null) + { + Set<Principal> principals = subject.getPrincipals(); + for (Principal principal : principals) + { + if (principal instanceof X500Principal) + { + return principal.getName(); + } + } + } + return null; + } + + /** + * Reconstruct the subject from the stored object. This method also + * re-populates the subject with all know alternate principals. + * + * @param o the stored object + * @return the complete subject + */ + public Subject toSubject(Object o) + { + try + { + if (o == null || !(o instanceof Integer)) + { + return null; + } + Integer i = (Integer) o; + if (i <= 0) + { + // identities <= 0 are internal + return new Subject(); + } + + UUID uuid = new UUID(0L, (long) i); + NumericPrincipal p = new NumericPrincipal(uuid); + Set<Principal> pset = new HashSet<Principal>(); + pset.add(p); + Subject ret = new Subject(false, pset, new HashSet(), new HashSet()); + + Profiler prof = new Profiler(ACIdentityManager.class); + augmentSubject(ret); + prof.checkpoint("CadcIdentityManager.augmentSubject"); + + return ret; + } + finally + { + + } + } + + public void augmentSubject(final Subject subject) + { + try + { + PrivilegedExceptionAction<Object> action = new PrivilegedExceptionAction<Object>() + { + public Object run() throws Exception + { + URI serviceURI = new URI(AC.UMS_SERVICE_URI); + UserClient userClient = new UserClient(serviceURI); + userClient.augmentSubject(subject); + return null; + } + }; + + log.debug("privileged user cert: " + privilegedPemFile.getAbsolutePath()); + Subject servopsSubject = SSLUtil.createSubject(privilegedPemFile); + Subject.doAs(servopsSubject, action); + } + catch (PrivilegedActionException e) + { + String msg = "Error augmenting subject " + subject; + throw new RuntimeException(msg, e); + } + } + + /** + * The returned CheckResource is the same as the one from AuthenticatorImpl. + * + * @return + */ + public static CheckResource getAvailabilityCheck() + { + try + { + RegistryClient regClient = new RegistryClient(); + URI serviceURI = new URI(AC.GMS_SERVICE_URI); + URL availURL = regClient.getServiceURL(serviceURI, "http", "/availability"); + return new CheckWebService(availURL.toExternalForm()); + } + catch (MalformedURLException e) + { + throw new RuntimeException(e); + } + catch (URISyntaxException e) + { + throw new RuntimeException(e); + } + } +} diff --git a/cadcAccessControl-Identity/src/ca/nrc/cadc/ac/auth/AuthenticatorImpl.java b/cadcAccessControl-Identity/src/ca/nrc/cadc/ac/auth/AuthenticatorImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..f3f2d4ac0b393b2e410e98d4479971b929fec255 --- /dev/null +++ b/cadcAccessControl-Identity/src/ca/nrc/cadc/ac/auth/AuthenticatorImpl.java @@ -0,0 +1,93 @@ +package ca.nrc.cadc.ac.auth; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +import javax.security.auth.Subject; +import javax.security.auth.x500.X500Principal; + +import org.apache.log4j.Logger; + +import ca.nrc.cadc.ac.AC; +import ca.nrc.cadc.auth.AuthMethod; +import ca.nrc.cadc.auth.AuthenticationUtil; +import ca.nrc.cadc.auth.Authenticator; +import ca.nrc.cadc.auth.HttpPrincipal; +import ca.nrc.cadc.profiler.Profiler; +import ca.nrc.cadc.reg.client.RegistryClient; +import ca.nrc.cadc.vosi.avail.CheckResource; +import ca.nrc.cadc.vosi.avail.CheckWebService; + +/** + * Implementation of default Authenticator for AuthenticationUtil in cadcUtil. + * This class augments the subject with additional identities using the access + * control library. + * + * @author pdowler + */ +public class AuthenticatorImpl implements Authenticator +{ + + private static final Logger log = Logger.getLogger(AuthenticatorImpl.class); + + public AuthenticatorImpl() + { + } + + /** + * @param subject + * @return the possibly modified subject + */ + public Subject getSubject(Subject subject) + { + AuthMethod am = AuthenticationUtil.getAuthMethod(subject); + if (am == null || AuthMethod.ANON.equals(am)) + { + return subject; + } + + if (subject != null && subject.getPrincipals().size() > 0) + { + Profiler prof = new Profiler(AuthenticatorImpl.class); + ACIdentityManager identityManager = new ACIdentityManager(); + identityManager.augmentSubject(subject); + prof.checkpoint("AuthenticatorImpl.augmentSubject()"); + + if (subject.getPrincipals(HttpPrincipal.class).isEmpty()) // no matching cadc account + { + // check to see if they connected with an client certificate at least + // they should be able to use services with only a client certificate + if (subject.getPrincipals(X500Principal.class).isEmpty()) + { + // if the caller had an invalid or forged CADC_SSO cookie, we could get + // in here and then not match any known identity: drop to anon + log.debug("HttpPrincipal not found - dropping to anon: " + subject); + subject = AuthenticationUtil.getAnonSubject(); + } + } + } + + return subject; + } + + public static CheckResource getAvailabilityCheck() + { + try + { + RegistryClient regClient = new RegistryClient(); + URI serviceURI = new URI(AC.GMS_SERVICE_URI); + URL availURL = regClient.getServiceURL(serviceURI, "http", "/availability"); + return new CheckWebService(availURL.toExternalForm()); + } + catch (MalformedURLException e) + { + throw new RuntimeException(e); + } + catch (URISyntaxException e) + { + throw new RuntimeException(e); + } + } +}