/************************************************************************* * Copyright 2009-2015 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. * * This file may incorporate work covered under the following copyright * and permission notice: * * Software License Agreement (BSD License) * * Copyright (c) 2008, Regents of the University of California * All rights reserved. * * Redistribution and use of this software in source and binary forms, * with or without modification, are permitted provided that the * following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE * THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL, * COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE, * AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING * IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, * SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, * WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION, * REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO * IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT * NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS. ************************************************************************/ package com.eucalyptus.auth; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPublicKey; import java.util.List; import java.util.Objects; import java.util.ServiceLoader; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.log4j.Logger; import com.eucalyptus.auth.api.PrincipalProvider; import com.eucalyptus.auth.policy.PolicySpec; import com.eucalyptus.auth.policy.ern.EuareResourceName; import com.eucalyptus.auth.principal.AccountIdentifiers; import com.eucalyptus.auth.principal.AccountIdentifiersImpl; import com.eucalyptus.auth.principal.BaseInstanceProfile; import com.eucalyptus.auth.principal.BaseOpenIdConnectProvider; import com.eucalyptus.auth.principal.BaseRole; import com.eucalyptus.auth.principal.HasRole; import com.eucalyptus.auth.principal.InstanceProfile; import com.eucalyptus.auth.principal.OpenIdConnectProvider; import com.eucalyptus.auth.principal.Principals; import com.eucalyptus.auth.principal.Role; import com.eucalyptus.auth.principal.SecurityTokenContent; import com.eucalyptus.auth.principal.User; import com.eucalyptus.auth.principal.UserPrincipal; import com.eucalyptus.util.Exceptions; import com.google.common.base.Function; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; /** * <h2>Eucalyptus/AWS IDs & Access Keys:</h2> * <p> * <strong>NOTE:IMPORTANT: It SHOULD NOT be the @Id of the underlying entity as this value is not * guaranteed to be fixed in the future (e.g., disrupted by upgrade, version changes, * etc.).</strong> * </p> * <ol> * <li>- AWS Account Number: Public ID for an ACCOUNT.</li> * <ul> * <li>- "globally" unique 12-digit number associated with the Eucalyptus account.</li> * <li>- is a shared value; other users may need it or discover it during normal operation of the * system</li> * <li>- _MUST_ be a 12-digit number. User commands require this value as input in certain cases and * enforce the length of the ID.</li> * </ul> * </li> * <li>AWS Access Key: Identifier value corresponding to the AWS Secret Access Key used to sign * requests.</li> * <ul> * <li>- "globally" unique 20 alpha-numeric characters * <li> * <li>- is a shared value; other users may need it or discover it during normal operation of the * system * <li> * <li>- _MUST_ be 20-alphanum characters; per the specification (e.g., * s3.amazonaws.com/awsdocs/ImportExport/latest/AWSImportExport-dg.pdf). User commands require this * value as input in certain cases and enforce the length of the ID. * <li> * </ul> * </ol> */ public class Accounts { private static final Logger LOG = Logger.getLogger( Accounts.class ); private static Supplier<PrincipalProvider> identities = serviceLoaderSupplier( PrincipalProvider.class ); protected static <T> Supplier<T> serviceLoaderSupplier( final Class<T> serviceClass ) { return Suppliers.memoize( new Supplier<T>() { @Override public T get( ) { return ServiceLoader.load( serviceClass ).iterator( ).next( ); } } ); } public static void setIdentityProvider( PrincipalProvider provider ) { synchronized ( Accounts.class ) { LOG.info( "Setting the identity provider to: " + provider.getClass( ) ); identities = Suppliers.ofInstance( provider ); } } protected static PrincipalProvider getIdentityProvider( ) { return identities.get(); } /** * Get the euare service certificate for a region, locate by account number. * * @param accountNumber The account number used to identify the region * @return The euare certificate for the accounts region * @throws AuthException On error */ public static X509Certificate getEuareCertificate( final String accountNumber ) throws AuthException { return getIdentityProvider( ).getCertificateByAccountNumber( accountNumber ); } /** * Create a certificate signed by the euare certificate for a region, locate by account number. * * @param accountNumber The account number used to identify the region * @param publicKey The public key for the certificate * @param principal The principal for the certificate subject * @param expiryInDays The certificate expiry * @return The ne certificate * @throws AuthException On error */ public static X509Certificate signCertificate( final String accountNumber, final RSAPublicKey publicKey, final String principal, final int expiryInDays ) throws AuthException { return getIdentityProvider().signCertificate( accountNumber, publicKey, principal, expiryInDays ); } public static String lookupAccountIdByAlias( String alias ) throws AuthException { if ( isAccountNumber( alias ) ) { return alias; } else { return getIdentityProvider( ).lookupAccountIdentifiersByAlias( alias ).getAccountNumber( ); } } public static String lookupAccountIdByCanonicalId( String canonicalId ) throws AuthException { throwIfFakeIdentity( canonicalId ); return getIdentityProvider( ).lookupAccountIdentifiersByCanonicalId( canonicalId ).getAccountNumber(); } public static String lookupCanonicalIdByAccountId( String accountId ) throws AuthException { return getIdentityProvider( ).lookupPrincipalByAccountNumber( accountId ).getCanonicalId(); } public static String lookupCanonicalIdByEmail( String email ) throws AuthException { return getIdentityProvider( ).lookupAccountIdentifiersByEmail( email ).getCanonicalId(); } public static String lookupAccountAliasById( String accountId ) throws AuthException { return getIdentityProvider( ).lookupPrincipalByAccountNumber( accountId ).getAccountAlias(); } public static AccountIdentifiers lookupAccountIdentifiersByAlias( final String alias ) throws AuthException { return Accounts.getIdentityProvider( ).lookupAccountIdentifiersByAlias( alias ); } public static AccountIdentifiers lookupAccountIdentifiersByCanonicalId( final String canonicalId ) throws AuthException { throwIfFakeIdentity( canonicalId ); return Accounts.getIdentityProvider( ).lookupAccountIdentifiersByCanonicalId( canonicalId ); } public static AccountIdentifiers lookupAccountIdentifiersById( final String accountId ) throws AuthException { final UserPrincipal user = Accounts.getIdentityProvider( ).lookupPrincipalByAccountNumber( accountId ); return new AccountIdentifiersImpl( user.getAccountNumber( ), user.getAccountAlias(), user.getCanonicalId( ) ); } public static boolean isAdministrativeAccount( String accountName ) { return AccountIdentifiers.SYSTEM_ACCOUNT.equals( accountName ) || AccountIdentifiers.AWS_EXEC_READ_SYSTEM_ACCOUNT.equals( accountName ); } public static boolean isSystemAccount( String accountName ) { return AccountIdentifiers.SYSTEM_ACCOUNT.equals( accountName ) || Objects.toString( accountName, "" ).startsWith( AccountIdentifiers.SYSTEM_ACCOUNT_PREFIX ); } @Nonnull public static List<String> listAccountNumbersForName( final String accountAliasExpression ) throws AuthException { return Lists.newArrayList( Iterables.transform( listAccountIdentifiersForName( accountAliasExpression ), AccountIdentifiers.Properties.accountNumber() ) ); } @Nonnull public static List<AccountIdentifiers> listAccountIdentifiersForName( final String accountAliasExpression ) throws AuthException { return getIdentityProvider( ).listAccountIdentifiersByAliasMatch( accountAliasExpression ); } @Nonnull public static UserPrincipal lookupPrincipalByAccountNumber( String accountNumber ) throws AuthException { return getIdentityProvider( ).lookupPrincipalByAccountNumber( accountNumber ); } @Nonnull public static UserPrincipal lookupPrincipalByAccountNumberAndUsername( String accountNumber, String username ) throws AuthException { return getIdentityProvider( ).lookupPrincipalByAccountNumberAndUsername( accountNumber, username ); } @Nonnull public static UserPrincipal lookupPrincipalByCanonicalId( String canonicalId ) throws AuthException { throwIfFakeIdentity( canonicalId ); return getIdentityProvider( ).lookupPrincipalByCanonicalId( canonicalId ); } @Nonnull public static UserPrincipal lookupPrincipalByAccessKeyId( String accessKeyId, String nonce ) throws AuthException { return getIdentityProvider( ).lookupPrincipalByAccessKeyId( accessKeyId, nonce ); } @Nonnull public static UserPrincipal lookupPrincipalByUserId( String userId ) throws AuthException { return getIdentityProvider( ).lookupPrincipalByUserId( userId, null ); } @Nonnull public static UserPrincipal lookupPrincipalByUserId( String userId, String nonce ) throws AuthException { return getIdentityProvider( ).lookupPrincipalByUserId( userId, nonce ); } @Nonnull public static UserPrincipal lookupPrincipalByRoleId( String roleId ) throws AuthException { return getIdentityProvider( ).lookupPrincipalByRoleId( roleId, null ); } @Nonnull public static UserPrincipal lookupPrincipalByRoleId( String roleId, String nonce ) throws AuthException { return getIdentityProvider( ).lookupPrincipalByRoleId( roleId, nonce ); } @Nonnull public static UserPrincipal lookupPrincipalByCertificateId( String certificateId ) throws AuthException { return getIdentityProvider( ).lookupPrincipalByCertificateId( certificateId ); } @Nonnull public static UserPrincipal lookupCachedPrincipalByAccountNumber( String accountNumber ) throws AuthException { return getIdentityProvider( ).lookupCachedPrincipalByAccountNumber( null, accountNumber ); } @Nonnull public static UserPrincipal lookupCachedPrincipalByAccessKeyId( String accessKeyId, String nonce ) throws AuthException { return getIdentityProvider( ).lookupCachedPrincipalByAccessKeyId( null, accessKeyId, nonce ); } @Nonnull public static UserPrincipal lookupCachedPrincipalByUserId( String userId, String nonce ) throws AuthException { return getIdentityProvider( ).lookupCachedPrincipalByUserId( null, userId, nonce ); } @Nonnull public static UserPrincipal lookupCachedPrincipalByRoleId( String roleId, String nonce ) throws AuthException { return getIdentityProvider( ).lookupCachedPrincipalByRoleId( null, roleId, nonce ); } @Nonnull public static UserPrincipal lookupCachedPrincipalByCertificateId( String certificateId ) throws AuthException { return getIdentityProvider( ).lookupCachedPrincipalByCertificateId( null, certificateId ); } @Nonnull public static InstanceProfile lookupInstanceProfileByName( String accountNumber, String name ) throws AuthException { return getIdentityProvider( ).lookupInstanceProfileByName( accountNumber, name ); } @Nonnull public static Role lookupRoleByName( String accountNumber, String name ) throws AuthException { return getIdentityProvider( ).lookupRoleByName( accountNumber, name ); } @Nonnull public static OpenIdConnectProvider lookupOidcProviderByUrl( String accountNumber, String url ) throws AuthException { return getIdentityProvider( ).lookupOidcProviderByUrl( accountNumber, url ); } @Nonnull public static SecurityTokenContent decodeSecurityToken( String accessKeyIdentifier, String securityToken ) throws AuthException { return getIdentityProvider().decodeSecurityToken( accessKeyIdentifier, securityToken ); } /** * Lookup a system account by alias * * @param alias The alias for the account * @return The principal representing the accounts admin user * @throws AuthException If the alias does not represent a system account or on other error */ public static UserPrincipal lookupSystemAccountByAlias( final String alias ) throws AuthException { if ( !isSystemAccount( alias ) ) { throw new AuthException( "Not a system account: " + alias ); } final String accountNumber = lookupAccountIdentifiersByAlias( alias ).getAccountNumber( ); return lookupPrincipalByAccountNumber( accountNumber ); } /** * Lookup the admin use for the eucalyptus account. * * @return The principal representing the eucalyptus admin user * @throws AuthException */ public static UserPrincipal lookupSystemAdmin( ) throws AuthException { return lookupSystemAccountByAlias( AccountIdentifiers.SYSTEM_ACCOUNT ); } public static String getNameFromFullName( final String fullName ) { String name = fullName; if ( name != null ) { int pathEndIndex = name.lastIndexOf( '/' ); if ( pathEndIndex > -1 && pathEndIndex < name.length( )) { name = name.substring( pathEndIndex + 1, name.length( ) ); } } return name; } public static String getAccountFullName( AccountIdentifiers account ) { return "/" + account.getAccountAlias(); } public static String getUserFullName( User user ) { if ( user.getPath( ).endsWith( "/" ) ) { return user.getPath( ) + user.getName( ); } else { return user.getPath( ) + "/" + user.getName( ); } } public static String getRoleFullName( BaseRole role ) { if ( role.getPath( ).endsWith( "/" ) ) { return role.getPath( ) + role.getName( ); } else { return role.getPath( ) + "/" + role.getName( ); } } public static String getInstanceProfileFullName( BaseInstanceProfile instanceProfile ) { if ( instanceProfile.getPath( ).endsWith( "/" ) ) { return instanceProfile.getPath( ) + instanceProfile.getName( ); } else { return instanceProfile.getPath( ) + "/" + instanceProfile.getName( ); } } public static String getAuthenticatedFullName( final UserPrincipal user ) { if ( isRoleIdentifier( user.getAuthenticatedId( ) ) ) { return getRoleFullName( ((HasRole)user).getRole( ) ); } else { return getUserFullName( user ); } } @Nonnull public static String getAccountArn( @Nonnull final String accountId ) throws AuthException { if ( !accountId.matches( "[0-9]{12}" ) ) { throw new AuthException( "Invalid account identifier '" + accountId +"'" ); } return "arn:aws:iam::"+accountId+":root"; } public static String getUserArn( final User user ) throws AuthException { return buildArn( user.getAccountNumber( ), PolicySpec.IAM_RESOURCE_USER, user.getPath(), user.getName() ); } public static String getUserArn( final UserPrincipal user ) { return buildArn( user.getAccountNumber( ), PolicySpec.IAM_RESOURCE_USER, user.getPath(), user.getName() ); } public static String getRoleArn( final BaseRole role ) throws AuthException { return buildArn( role.getAccountNumber( ), PolicySpec.IAM_RESOURCE_ROLE, role.getPath(), role.getName() ); } public static String getRoleArn( final Role role ) { return buildArn( role.getAccountNumber( ), PolicySpec.IAM_RESOURCE_ROLE, role.getPath(), role.getName() ); } public static String getAssumedRoleArn( final BaseRole role, final String roleSessionName ) throws AuthException { return "arn:aws:sts::"+role.getAccountNumber()+":assumed-role"+Accounts.getRoleFullName( role )+"/"+roleSessionName; } public static String getAuthenticatedArn( final UserPrincipal user ) { if ( isRoleIdentifier( user.getAuthenticatedId( ) ) ) { return getRoleArn( ((HasRole)user).getRole( ) ); } else { return getUserArn( user ); } } public static String getInstanceProfileArn( final BaseInstanceProfile instanceProfile ) throws AuthException { return buildArn( instanceProfile.getAccountNumber( ), PolicySpec.IAM_RESOURCE_INSTANCE_PROFILE, instanceProfile.getPath( ), instanceProfile.getName( ) ); } public static String getOpenIdConnectProviderArn( final BaseOpenIdConnectProvider provider ) throws AuthException { return buildArn( provider.getAccountNumber( ), PolicySpec.IAM_RESOURCE_OPENID_CONNECT_PROVIDER, "/", provider.getUrl( ) ); } protected static String buildArn( final String accountNumber, final String type, final String path, final String name ) { return new EuareResourceName( accountNumber, type, path, name ).toString( ); } /** * Check the prefix of the given identifier to check for a role. * * This method does not check the full identifier, so can be used to check * for assumed role identities where the role identifier is combined with a * session name suffix. * * @param identifier The identifier to check * @return True if the identifier is for a role */ public static boolean isRoleIdentifier( @Nonnull final String identifier ) { return identifier.startsWith( "ARO" ); } public static boolean isAccountNumber( final String identifier ) { return identifier.matches( "[0-9]{12}" ); } public static Function<User,String> toUserId() { return UserStringProperties.USER_ID; } /** * Get the base identifier, removing any text after ':'. */ public static String getIdentifier( @Nullable final String identifier ) { String cleanedId = identifier; int suffixIndex; if ( identifier != null && ( suffixIndex = identifier.indexOf( ':' ) ) > 0 ) { cleanedId = identifier.substring( 0, suffixIndex ); } return cleanedId; } /** * Get the value after the base identifier, the text after ':'. */ @Nullable public static String getIdentifierSuffix( @Nullable final String identifier ) { String idSuffix = null; int suffixIndex; if ( identifier != null && ( suffixIndex = identifier.indexOf( ':' ) ) > 0 ) { idSuffix = identifier.substring( suffixIndex + 1 ); } return idSuffix; } private static void throwIfFakeIdentity( final String id ) throws AuthException { if ( Principals.isFakeIdentify( id ) ) { throw new AuthException( "Invalid identity: " + id ); } } private enum UserStringProperties implements Function<User,String> { ACCOUNT_NUMBER { @Override public String apply( final User user ) { try { return user.getAccountNumber( ); } catch ( AuthException e ) { throw Exceptions.toUndeclared( e ); } } }, USER_ID { @Override public String apply( final User user ) { return user.getUserId( ); } } } }