/************************************************************************* * 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. ************************************************************************/ package com.eucalyptus.auth.euare.persist; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPublicKey; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import javax.persistence.PersistenceException; import org.hibernate.FlushMode; import org.hibernate.exception.ConstraintViolationException; import com.eucalyptus.auth.AccessKeys; import com.eucalyptus.auth.euare.Accounts; import com.eucalyptus.auth.AuthException; import com.eucalyptus.auth.InvalidAccessKeyAuthException; import com.eucalyptus.auth.api.PrincipalProvider; import com.eucalyptus.auth.euare.EuareServerCertificateUtil; import com.eucalyptus.auth.euare.persist.entities.AccessKeyEntity; import com.eucalyptus.auth.euare.persist.entities.AccessKeyEntity_; import com.eucalyptus.auth.euare.persist.entities.AccountEntity; import com.eucalyptus.auth.euare.persist.entities.CertificateEntity; import com.eucalyptus.auth.euare.persist.entities.InstanceProfileEntity; import com.eucalyptus.auth.euare.persist.entities.OpenIdProviderEntity; import com.eucalyptus.auth.euare.persist.entities.ReservedNameEntity; import com.eucalyptus.auth.euare.persist.entities.RoleEntity; import com.eucalyptus.auth.euare.persist.entities.UserEntity; import com.eucalyptus.auth.euare.persist.entities.UserEntity_; import com.eucalyptus.auth.euare.principal.EuareOpenIdConnectProvider; import com.eucalyptus.auth.euare.principal.EuareRole; import com.eucalyptus.auth.euare.principal.EuareUser; import com.eucalyptus.auth.euare.principal.GlobalNamespace; import com.eucalyptus.auth.principal.AccessKey; import com.eucalyptus.auth.principal.AccountIdentifiers; import com.eucalyptus.auth.principal.Certificate; import com.eucalyptus.auth.euare.principal.EuareInstanceProfile; import com.eucalyptus.auth.principal.InstanceProfile; import com.eucalyptus.auth.principal.OpenIdConnectProvider; import com.eucalyptus.auth.principal.PolicyVersion; import com.eucalyptus.auth.principal.Role; import com.eucalyptus.auth.principal.SecurityTokenContent; import com.eucalyptus.auth.principal.UserPrincipal; import com.eucalyptus.auth.euare.UserPrincipalImpl; import com.eucalyptus.auth.tokens.SecurityTokenManager; import com.eucalyptus.component.annotation.ComponentNamed; import com.eucalyptus.component.auth.SystemCredentials; import com.eucalyptus.component.id.Euare; import com.eucalyptus.entities.Entities; import com.eucalyptus.entities.TransactionResource; import com.eucalyptus.util.CollectionUtils; import com.eucalyptus.auth.principal.OwnerFullName; import com.google.common.base.Optional; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; /** * */ @SuppressWarnings( { "unused", "Guava", "StaticPseudoFunctionalStyleMethod" } ) @ComponentNamed( "localPrincipalProvider" ) public class DatabasePrincipalProvider implements PrincipalProvider { @Override public UserPrincipal lookupPrincipalByUserId( final String userId, final String nonce ) throws AuthException { try ( final TransactionResource tx = Entities.readOnlyDistinctTransactionFor( UserEntity.class ) ) { try { final UserEntity user = DatabaseAuthUtils.getUnique( UserEntity.class, UserEntity_.userId, userId ); return decorateCredentials( new UserPrincipalImpl( user ), nonce, user.getToken( ) ); } catch ( Exception e ) { throw new AuthException( AuthException.NO_SUCH_USER, e ); } } } @Override public UserPrincipal lookupPrincipalByRoleId( final String roleId, final String nonce ) throws AuthException { try ( final TransactionResource tx = Entities.readOnlyDistinctTransactionFor( RoleEntity.class ) ) { final EuareRole role = Accounts.lookupRoleById( Accounts.getIdentifier( roleId ) ); return decorateCredentials( Accounts.roleAsPrincipal( role, Accounts.getIdentifierSuffix( roleId ) ), nonce, role.getSecret() ); } } @Override public UserPrincipal lookupPrincipalByAccessKeyId( final String keyId, final String nonce ) throws AuthException { try ( final TransactionResource tx = Entities.readOnlyDistinctTransactionFor( AccessKeyEntity.class ) ) { final Optional<UserEntity> user; try { user = Entities.criteriaQuery( UserEntity.class ) .join( UserEntity_.keys ).whereEqual( AccessKeyEntity_.accessKey, keyId ) .entityCriteriaQuery( ) .fetchSize( FlushMode.MANUAL ) .readonly( ) .uniqueResultOption( ); } catch ( Exception e ) { throw new InvalidAccessKeyAuthException( "Failed to find access key", e ); } if ( !user.isPresent( ) ) { throw new InvalidAccessKeyAuthException( "Failed to find access key" ); } final UserPrincipal principal = new UserPrincipalImpl( user.get( ) ); final Optional<AccessKey> accessKey = Iterables.tryFind( principal.getKeys( ), CollectionUtils.propertyPredicate( keyId, AccessKeys.accessKeyIdentifier( ) ) ); if ( !Iterables.any( accessKey.asSet( ), AccessKeys.isActive( ) ) ) { throw new InvalidAccessKeyAuthException( "Invalid access key or token" ); } return decorateCredentials( principal, nonce, accessKey.get( ).getSecretKey() ); } } @Override public UserPrincipal lookupPrincipalByCertificateId( final String certificateId ) throws AuthException { try ( final TransactionResource tx = Entities.readOnlyDistinctTransactionFor( CertificateEntity.class ) ) { final Certificate certificate = Accounts.lookupCertificateByHashId( certificateId ); if ( !certificate.isActive( ) ) { throw new AuthException( "Certificate is inactive or revoked: " + certificate.getX509Certificate( ).getSubjectX500Principal( ) ); } return certificate.getPrincipal(); } } @Override public UserPrincipal lookupPrincipalByCanonicalId( final String canonicalId ) throws AuthException { try ( final TransactionResource tx = Entities.readOnlyDistinctTransactionFor( UserEntity.class ) ) { return Accounts.userAsPrincipal( Accounts.lookupAccountByCanonicalId( canonicalId ).lookupAdmin() ); } } @Override public UserPrincipal lookupPrincipalByAccountNumber( final String accountNumber ) throws AuthException { try ( final TransactionResource tx = Entities.readOnlyDistinctTransactionFor( UserEntity.class ) ) { return Accounts.userAsPrincipal( Accounts.lookupAccountById( accountNumber ).lookupAdmin() ); } } @Override public UserPrincipal lookupPrincipalByAccountNumberAndUsername( final String accountNumber, final String name ) throws AuthException { try ( final TransactionResource tx = Entities.readOnlyDistinctTransactionFor( UserEntity.class ) ) { return Accounts.userAsPrincipal( Accounts.lookupAccountById( accountNumber ).lookupUserByName( name ) ); } } @Override public UserPrincipal lookupCachedPrincipalByUserId( final UserPrincipal cached, final String userId, final String nonce ) throws AuthException { return lookupPrincipalByUserId( userId, nonce ); } @Override public UserPrincipal lookupCachedPrincipalByRoleId( final UserPrincipal cached, final String roleId, final String nonce ) throws AuthException { return lookupPrincipalByRoleId( roleId, nonce ); } @Override public UserPrincipal lookupCachedPrincipalByAccessKeyId( final UserPrincipal cached, final String keyId, final String nonce ) throws AuthException { return lookupPrincipalByAccessKeyId( keyId, nonce ); } @Override public UserPrincipal lookupCachedPrincipalByCertificateId( final UserPrincipal cached, final String certificateId ) throws AuthException { return lookupPrincipalByCertificateId( certificateId ); } @Override public UserPrincipal lookupCachedPrincipalByAccountNumber( final UserPrincipal cached, final String accountNumber ) throws AuthException { return lookupPrincipalByAccountNumber( accountNumber ); } @Override public AccountIdentifiers lookupAccountIdentifiersByAlias( final String alias ) throws AuthException { try ( final TransactionResource tx = Entities.readOnlyDistinctTransactionFor( AccountEntity.class ) ) { return Accounts.lookupAccountByName( alias ); } } @Override public AccountIdentifiers lookupAccountIdentifiersByCanonicalId( final String canonicalId ) throws AuthException { try ( final TransactionResource tx = Entities.readOnlyDistinctTransactionFor( AccountEntity.class ) ) { return Accounts.lookupAccountByCanonicalId( canonicalId ); } } @Override public AccountIdentifiers lookupAccountIdentifiersByEmail( final String email ) throws AuthException { try ( final TransactionResource tx = Entities.readOnlyDistinctTransactionFor( UserEntity.class ) ) { final EuareUser euareUser = Accounts.lookupUserByEmailAddress( email ); if ( euareUser.isAccountAdmin( ) ) { return euareUser.getAccount(); } throw new AuthException( AuthException.NO_SUCH_USER ); } } @Override public List<AccountIdentifiers> listAccountIdentifiersByAliasMatch( final String aliasExpression ) throws AuthException { try ( final TransactionResource tx = Entities.readOnlyDistinctTransactionFor( AccountEntity.class ) ) { return Accounts.resolveAccountNumbersForName( aliasExpression ); } } @Override public InstanceProfile lookupInstanceProfileByName( final String accountNumber, final String name ) throws AuthException { try ( final TransactionResource tx = Entities.readOnlyDistinctTransactionFor( InstanceProfileEntity.class ) ) { final EuareInstanceProfile profile = Accounts.lookupAccountById( accountNumber ).lookupInstanceProfileByName( name ); final String profileArn = Accounts.getInstanceProfileArn( profile ); final EuareRole euareRole = profile.getRole( ); final String roleArn = euareRole == null ? null : Accounts.getRoleArn( euareRole ); final String roleAccountNumber = euareRole == null ? null : euareRole.getAccountNumber( ); final PolicyVersion rolePolicy = euareRole == null ? null : euareRole.getPolicy( ); final Role role = euareRole == null ? null : new Role( ) { @Override public String getAccountNumber( ) { return roleAccountNumber; } @Override public String getRoleId( ) { return euareRole.getRoleId( ); } @Override public String getRoleArn( ) { return roleArn; } @Override public String getPath( ) { return euareRole.getPath( ); } @Override public String getName( ) { return euareRole.getName( ); } @Override public String getSecret( ) { return euareRole.getSecret( ); } @Override public PolicyVersion getPolicy( ) { return rolePolicy; } @Override public String getDisplayName( ) { return Accounts.getRoleFullName( this ); } @Override public OwnerFullName getOwner( ) { return euareRole.getOwner( ); } }; return new InstanceProfile( ) { @Override public String getAccountNumber( ) { return accountNumber; } @Override public String getInstanceProfileId( ) { return profile.getInstanceProfileId( ); } @Override public String getInstanceProfileArn( ) { return profileArn; } @Nullable @Override public Role getRole( ) { return role; } @Override public String getName( ) { return profile.getName( ); } @Override public String getPath( ) { return profile.getPath(); } }; } } @Override public Role lookupRoleByName( final String accountNumber, final String name ) throws AuthException { try ( final TransactionResource tx = Entities.readOnlyDistinctTransactionFor( RoleEntity.class ) ) { final EuareRole euareRole = Accounts.lookupAccountById( accountNumber ).lookupRoleByName( name ); final String roleArn = Accounts.getRoleArn( euareRole ); final String roleAccountNumber = euareRole.getAccountNumber( ); final PolicyVersion assumeRolePolicy = euareRole.getPolicy( ); return new Role( ) { @Override public String getAccountNumber( ) { return roleAccountNumber; } @Override public String getRoleId( ) { return euareRole.getRoleId( ); } @Override public String getRoleArn( ) { return roleArn; } @Override public String getPath( ) { return euareRole.getPath( ); } @Override public String getName( ) { return euareRole.getName(); } @Override public String getSecret( ) { return euareRole.getSecret( ); } @Override public PolicyVersion getPolicy( ) { return assumeRolePolicy; } @Override public String getDisplayName( ) { return Accounts.getRoleFullName( this ); } @Override public OwnerFullName getOwner( ) { return euareRole.getOwner(); } }; } } @Override public OpenIdConnectProvider lookupOidcProviderByUrl( final String accountNumber, final String url ) throws AuthException { try ( final TransactionResource tx = Entities.readOnlyDistinctTransactionFor( OpenIdProviderEntity.class ) ) { final EuareOpenIdConnectProvider euareProvider = Accounts.lookupAccountById( accountNumber ).lookupOpenIdConnectProvider( url ); final String providerArn = Accounts.getOpenIdConnectProviderArn( euareProvider ); final String providerAccountNumber = euareProvider.getAccountNumber( ); final List<String> providerClientIds = ImmutableList.copyOf( euareProvider.getClientIds( ) ); final List<String> providerThumbprints = ImmutableList.copyOf( euareProvider.getThumbprints( ) ); return new OpenIdConnectProvider( ) { @Override public String getAccountNumber( ) { return providerAccountNumber; } @Override public String getArn( ) { return providerArn; } @Override public String getUrl( ) { return euareProvider.getUrl( ); } @Override public String getHost( ) { return euareProvider.getHost( ); } @Override public Integer getPort( ) { return euareProvider.getPort( ); } @Override public String getPath( ) { return euareProvider.getPath( ); } @Override public List<String> getClientIds( ) { return providerClientIds; } @Override public List<String> getThumbprints( ) { return providerThumbprints; } }; } } @Override public SecurityTokenContent decodeSecurityToken( final String accessKeyIdentifier, final String securityToken ) throws AuthException { return SecurityTokenManager.decodeSecurityToken( accessKeyIdentifier, securityToken ); } private UserPrincipal decorateCredentials( final UserPrincipal userPrincipal, final String nonce, final String secret ) throws AuthException { final UserPrincipal decorated; if ( nonce == null ) { decorated = userPrincipal; } else { final String secretKey = SecurityTokenManager.generateSecret( nonce, secret ); final Collection<AccessKey> keys = Collections.<AccessKey>singleton( new AccessKey( ) { @Override public Boolean isActive( ) { return true; } @Override public String getAccessKey( ) { return null; } @Override public String getSecretKey( ) { return secretKey; } @Override public Date getCreateDate( ) { return null; } @Override public UserPrincipal getPrincipal( ) { return null; } } ); decorated = new UserPrincipalImpl( userPrincipal, keys ); } return decorated; } @Override public void reserveGlobalName( final String namespace, final String name, final Integer duration, final String clientToken ) throws AuthException { final GlobalNamespace globalNamespace; try { globalNamespace = GlobalNamespace.forNamespace( namespace ); } catch ( IllegalArgumentException e ) { throw new AuthException( e ); } if ( duration == null || duration < 1 || duration > TimeUnit.DAYS.toSeconds( 1 ) ) { throw new AuthException( "Requested duration not supported: " + duration ); } try ( final TransactionResource tx = Entities.transactionFor( ReservedNameEntity.class ) ) { Entities.persist( ReservedNameEntity.create( namespace, name, duration, Strings.emptyToNull( clientToken ) ) ); tx.commit( ); } catch ( ConstraintViolationException e ) { boolean conflict = true; if ( !Strings.isNullOrEmpty( clientToken ) ) try ( final TransactionResource tx = Entities.readOnlyDistinctTransactionFor( ReservedNameEntity.class ) ) { // use the existing reservation for the token if it matches and // has half the duration remaining final ReservedNameEntity entity = Entities.criteriaQuery( ReservedNameEntity.exampleWithToken( clientToken ) ).uniqueResult( ); conflict = !entity.getNamespace( ).equals( namespace ) || !entity.getName( ).equals( name ) || entity.getExpiry( ).before( new Date( System.currentTimeMillis( ) + TimeUnit.SECONDS.toMillis( duration / 2 ) ) ); } catch ( PersistenceException|NoSuchElementException e1 ) { // fail with conflict } if ( conflict ) { throw new AuthException( AuthException.CONFLICT ); } } catch ( final Exception e ) { throw new AuthException( e ); } switch ( globalNamespace ) { case Account_Alias: try { Accounts.lookupAccountByName( name ); throw new AuthException( AuthException.CONFLICT ); } catch ( AuthException e ) { if ( !AuthException.NO_SUCH_ACCOUNT.equals( e.getMessage() ) ) { throw new AuthException( AuthException.CONFLICT ); } } break; case Signing_Certificate_Id: try { Accounts.lookupCertificateById( name ); throw new AuthException( AuthException.CONFLICT ); } catch ( AuthException e ) { if ( !AuthException.NO_SUCH_CERTIFICATE.equals( e.getMessage() ) ) { throw new AuthException( AuthException.CONFLICT ); } } break; default: throw new AuthException( AuthException.CONFLICT ); } } @Override public X509Certificate getCertificateByAccountNumber( final String accountNumber ) { return SystemCredentials.lookup( Euare.class ).getCertificate( ); } @Override public X509Certificate signCertificate( final String accountNumber, final RSAPublicKey publicKey, final String principal, final int expiryInDays ) throws AuthException { return EuareServerCertificateUtil.generateVMCertificate( publicKey, principal, expiryInDays ); } }