/************************************************************************* * Copyright 2009-2016 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.identity; import static com.eucalyptus.util.CollectionUtils.propertyPredicate; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPublicKey; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Collections; import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Named; import org.apache.axiom.om.OMElement; import org.apache.axiom.om.OMOutputFormat; import org.apache.axiom.om.impl.builder.StAXOMBuilder; import org.apache.log4j.Logger; import com.eucalyptus.auth.AccessKeys; import com.eucalyptus.auth.Accounts; import com.eucalyptus.auth.AuthException; import com.eucalyptus.auth.InvalidAccessKeyAuthException; import com.eucalyptus.auth.api.PrincipalProvider; import com.eucalyptus.auth.euare.EuareException; import com.eucalyptus.auth.euare.EuareServerCertificateUtil; import com.eucalyptus.auth.euare.UserPrincipalImpl; import com.eucalyptus.auth.euare.common.identity.Account; import com.eucalyptus.auth.euare.common.identity.DecodeSecurityTokenResponseType; import com.eucalyptus.auth.euare.common.identity.DecodeSecurityTokenResult; import com.eucalyptus.auth.euare.common.identity.DecodeSecurityTokenType; import com.eucalyptus.auth.euare.common.identity.DescribeAccountsResponseType; import com.eucalyptus.auth.euare.common.identity.DescribeAccountsResult; import com.eucalyptus.auth.euare.common.identity.DescribeAccountsType; import com.eucalyptus.auth.euare.common.identity.DescribeCertificateResponseType; import com.eucalyptus.auth.euare.common.identity.DescribeCertificateResult; import com.eucalyptus.auth.euare.common.identity.DescribeCertificateType; import com.eucalyptus.auth.euare.common.identity.DescribeInstanceProfileResponseType; import com.eucalyptus.auth.euare.common.identity.DescribeInstanceProfileResult; import com.eucalyptus.auth.euare.common.identity.DescribeInstanceProfileType; import com.eucalyptus.auth.euare.common.identity.DescribeOidcProviderResponseType; import com.eucalyptus.auth.euare.common.identity.DescribeOidcProviderResult; import com.eucalyptus.auth.euare.common.identity.DescribeOidcProviderType; import com.eucalyptus.auth.euare.common.identity.DescribePrincipalResponseType; import com.eucalyptus.auth.euare.common.identity.DescribePrincipalResult; import com.eucalyptus.auth.euare.common.identity.DescribePrincipalType; import com.eucalyptus.auth.euare.common.identity.DescribeRoleResponseType; import com.eucalyptus.auth.euare.common.identity.DescribeRoleResult; import com.eucalyptus.auth.euare.common.identity.DescribeRoleType; import com.eucalyptus.auth.euare.common.identity.OidcProvider; import com.eucalyptus.auth.euare.common.identity.Policy; import com.eucalyptus.auth.euare.common.identity.Principal; import com.eucalyptus.auth.euare.common.identity.ReserveNameResponseType; import com.eucalyptus.auth.euare.common.identity.ReserveNameResult; import com.eucalyptus.auth.euare.common.identity.ReserveNameType; import com.eucalyptus.auth.euare.common.identity.SecurityToken; import com.eucalyptus.auth.euare.common.identity.SecurityTokenAttribute; import com.eucalyptus.auth.euare.common.identity.SignCertificateResponseType; import com.eucalyptus.auth.euare.common.identity.SignCertificateResult; import com.eucalyptus.auth.euare.common.identity.SignCertificateType; import com.eucalyptus.auth.euare.common.identity.TunnelActionResponseType; import com.eucalyptus.auth.euare.common.identity.TunnelActionResult; import com.eucalyptus.auth.euare.common.identity.TunnelActionType; import com.eucalyptus.auth.principal.AccessKey; import com.eucalyptus.auth.principal.AccountIdentifiers; import com.eucalyptus.auth.principal.Certificate; import com.eucalyptus.auth.principal.InstanceProfile; import com.eucalyptus.auth.principal.OpenIdConnectProvider; import com.eucalyptus.auth.principal.PolicyVersion; import com.eucalyptus.auth.principal.PolicyVersions; import com.eucalyptus.auth.principal.Role; import com.eucalyptus.auth.principal.SecurityTokenContent; import com.eucalyptus.auth.principal.UserPrincipal; import com.eucalyptus.binding.Binding; import com.eucalyptus.binding.BindingManager; import com.eucalyptus.binding.HoldMe; import com.eucalyptus.component.annotation.ComponentNamed; import com.eucalyptus.component.auth.SystemCredentials; import com.eucalyptus.component.id.Euare; import com.eucalyptus.context.Contexts; import com.eucalyptus.crypto.util.B64; import com.eucalyptus.crypto.util.PEMFiles; import com.eucalyptus.util.Exceptions; import com.eucalyptus.util.LockResource; import com.eucalyptus.util.TypeMapper; import com.eucalyptus.util.TypeMappers; import com.eucalyptus.util.async.AsyncRequests; import com.google.common.base.Function; import com.google.common.base.MoreObjects; import com.google.common.base.Strings; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import edu.ucsb.eucalyptus.msgs.BaseMessage; /** * */ @SuppressWarnings( { "UnusedDeclaration", "StaticPseudoFunctionalStyleMethod" } ) @ComponentNamed public class IdentityService { private static final Logger logger = Logger.getLogger( IdentityService.class ); private PrincipalProvider principalProvider; @Inject public IdentityService( @Named( "localPrincipalProvider" ) final PrincipalProvider principalProvider ) { this.principalProvider = principalProvider; } public DescribePrincipalResponseType describePrincipal( final DescribePrincipalType request ) throws IdentityServiceException { final DescribePrincipalResponseType response = request.getReply( ); final DescribePrincipalResult result = new DescribePrincipalResult( ); try { final UserPrincipal user; if ( request.getAccessKeyId() != null ) { user = principalProvider.lookupPrincipalByAccessKeyId( request.getAccessKeyId(), request.getNonce() ); } else if ( request.getCertificateId() != null ) { user = principalProvider.lookupPrincipalByCertificateId( request.getCertificateId() ); } else if ( request.getUserId( ) != null ) { user = principalProvider.lookupPrincipalByUserId( request.getUserId( ), request.getNonce( ) ); } else if ( request.getRoleId( ) != null ) { user = principalProvider.lookupPrincipalByRoleId( request.getRoleId( ), request.getNonce( ) ); } else if ( request.getAccountId( ) != null && request.getUsername( ) != null ) { user = principalProvider .lookupPrincipalByAccountNumberAndUsername( request.getAccountId(), request.getUsername() ); } else if ( request.getAccountId( ) != null ) { user = principalProvider.lookupPrincipalByAccountNumber( request.getAccountId( ) ); } else if ( request.getCanonicalId( ) != null ) { user = principalProvider.lookupPrincipalByCanonicalId( request.getCanonicalId( ) ); } else { user = null; } if ( user != null ) { final String ptag = UserPrincipalImpl.ptag( user ); final Principal principal = new Principal( ); principal.setArn( Accounts.getUserArn( user ) ); principal.setUserId( user.getUserId( ) ); principal.setRoleId( Accounts.isRoleIdentifier( user.getAuthenticatedId( ) ) ? user.getAuthenticatedId( ) : null ); principal.setCanonicalId( user.getCanonicalId( ) ); principal.setPtag( ptag ); if ( !ptag.equals( request.getPtag( ) ) ) { principal.setAccountAlias( user.getAccountAlias() ); principal.setEnabled( user.isEnabled( ) ); principal.setToken( user.getToken( ) ); principal.setPasswordHash( user.getPassword( ) ); principal.setPasswordExpiry( user.getPasswordExpires( ) ); final ArrayList<com.eucalyptus.auth.euare.common.identity.AccessKey> accessKeys = Lists.newArrayList( ); for ( final AccessKey accessKey : Iterables.filter( user.getKeys( ), AccessKeys.isActive( ) ) ) { final com.eucalyptus.auth.euare.common.identity.AccessKey key = new com.eucalyptus.auth.euare.common.identity.AccessKey( ); key.setAccessKeyId( accessKey.getAccessKey( ) ); key.setSecretAccessKey( accessKey.getSecretKey( ) ); accessKeys.add( key ); } principal.setAccessKeys( accessKeys ); final ArrayList<com.eucalyptus.auth.euare.common.identity.Certificate> certificates = Lists.newArrayList( ); for ( final Certificate certificate : Iterables.filter( user.getCertificates( ), propertyPredicate( true, Certificate.Util.active( ) ) ) ) { final com.eucalyptus.auth.euare.common.identity.Certificate cert = new com.eucalyptus.auth.euare.common.identity.Certificate(); cert.setCertificateId( certificate.getCertificateId() ); cert.setCertificateBody( certificate.getPem() ); certificates.add( cert ); } principal.setCertificates( certificates ); final ArrayList<Policy> policies = Lists.newArrayList( ); if ( user.isEnabled( ) ) { Iterables.addAll( policies, Iterables.transform( user.getPrincipalPolicies( ), TypeMappers.lookup( PolicyVersion.class, Policy.class ) ) ); } principal.setPolicies( policies ); } result.setPrincipal( principal ); } } catch ( InvalidAccessKeyAuthException e ) { // not found, so empty response } catch ( AuthException e ) { if ( !isNotFoundError( e ) ) { throw handleException( e ); } } response.setDescribePrincipalResult( result ); return response; } public DescribeAccountsResponseType describeAccounts( final DescribeAccountsType request ) throws IdentityServiceException { final DescribeAccountsResponseType response = request.getReply( ); final DescribeAccountsResult result = new DescribeAccountsResult( ); try { final Iterable<AccountIdentifiers> accountIdentifiers; if ( request.getAlias() != null ) { accountIdentifiers = Collections.singleton( principalProvider.lookupAccountIdentifiersByAlias( request.getAlias() ) ); } else if ( request.getCanonicalId() != null ) { accountIdentifiers = Collections.singleton( principalProvider.lookupAccountIdentifiersByCanonicalId( request.getCanonicalId() ) ); } else if ( request.getEmail() != null ) { accountIdentifiers = Collections.singleton( principalProvider.lookupAccountIdentifiersByEmail( request.getEmail() ) ); } else if ( request.getAliasLike() != null ) { accountIdentifiers = principalProvider.listAccountIdentifiersByAliasMatch( request.getAliasLike() ); } else { accountIdentifiers = null; } final ArrayList<Account> accounts = Lists.newArrayList( ); if ( accountIdentifiers != null ) { Iterables.addAll( accounts, Iterables.transform( accountIdentifiers, TypeMappers.lookup( AccountIdentifiers.class, Account.class ) ) ); } result.setAccounts( accounts ); } catch ( AuthException e ) { throw handleException( e ); } response.setDescribeAccountsResult( result ); return response; } public DescribeInstanceProfileResponseType describeInstanceProfile( final DescribeInstanceProfileType request ) throws IdentityServiceException { final DescribeInstanceProfileResponseType response = request.getReply( ); final DescribeInstanceProfileResult result = new DescribeInstanceProfileResult( ); try { final InstanceProfile instanceProfile = principalProvider.lookupInstanceProfileByName( request.getAccountId( ), request.getInstanceProfileName( ) ); result.setInstanceProfile( TypeMappers.transform( instanceProfile, com.eucalyptus.auth.euare.common.identity.InstanceProfile.class ) ); result.setRole( TypeMappers.transform( instanceProfile.getRole(), com.eucalyptus.auth.euare.common.identity.Role.class ) ); } catch ( AuthException e ) { throw handleException( e ); } response.setDescribeInstanceProfileResult( result ); return response; } public DescribeRoleResponseType describeRole( final DescribeRoleType request ) throws IdentityServiceException { final DescribeRoleResponseType response = request.getReply( ); final DescribeRoleResult result = new DescribeRoleResult( ); try { final Role role = principalProvider.lookupRoleByName( request.getAccountId(), request.getRoleName() ); result.setRole( TypeMappers.transform( role, com.eucalyptus.auth.euare.common.identity.Role.class ) ); } catch ( AuthException e ) { throw handleException( e ); } response.setDescribeRoleResult( result ); return response; } public DescribeOidcProviderResponseType describeOidcProvider( final DescribeOidcProviderType request ) throws IdentityServiceException { final DescribeOidcProviderResponseType response = request.getReply( ); final DescribeOidcProviderResult result = new DescribeOidcProviderResult( ); try { final OpenIdConnectProvider oidcProvider = principalProvider.lookupOidcProviderByUrl( request.getAccountId(), request.getProviderUrl() ); result.setOidcProvider( TypeMappers.transform( oidcProvider, OidcProvider.class ) ); } catch ( AuthException e ) { throw handleException( e ); } response.setDescribeOidcProviderResult( result ); return response; } public DecodeSecurityTokenResponseType decodeSecurityToken( final DecodeSecurityTokenType request ) throws IdentityServiceException { final DecodeSecurityTokenResponseType response = request.getReply( ); final DecodeSecurityTokenResult result = new DecodeSecurityTokenResult( ); try { final SecurityTokenContent securityTokenContent = principalProvider.decodeSecurityToken( request.getAccessKeyId(), request.getSecurityToken() ); result.setSecurityToken( TypeMappers.transform( securityTokenContent, SecurityToken.class ) ); } catch ( AuthException e ) { throw handleException( e ); } response.setDecodeSecurityTokenResult( result ); return response; } public ReserveNameResponseType reserveName( final ReserveNameType request ) throws IdentityServiceException { final ReserveNameResponseType response = request.getReply( ); final ReserveNameResult result = new ReserveNameResult( ); try { principalProvider.reserveGlobalName( request.getNamespace(), request.getName(), request.getDuration(), request.getClientToken() ); } catch ( AuthException e ) { if ( AuthException.CONFLICT.equals( e.getMessage( ) ) ) { throw new IdentityServiceSenderException( "Conflict", "Name not available: " + request.getName( ) ); } throw handleException( e ); } response.setReserveNameResult( result ); return response; } public DescribeCertificateResponseType describeCertificate( final DescribeCertificateType request ) { final DescribeCertificateResponseType response = request.getReply( ); final DescribeCertificateResult result = new DescribeCertificateResult( ); result.setPem( new String( PEMFiles.getBytes( SystemCredentials.lookup( Euare.class ).getCertificate( ) ), StandardCharsets.UTF_8 ) ); response.setDescribeCertificateResult( result ); return response; } public SignCertificateResponseType signCertificate( final SignCertificateType request ) throws IdentityServiceException { final SignCertificateResponseType response = request.getReply( ); final SignCertificateResult result = new SignCertificateResult( ); final String pubkey = request.getKey( ); final String principal = request.getPrincipal( ); final Integer expirationDays = request.getExpirationDays( ); if( Strings.isNullOrEmpty( pubkey ) ) throw new IdentityServiceSenderException( "InvalidValue", "No public key is provided"); if( Strings.isNullOrEmpty( principal ) ) throw new IdentityServiceSenderException( "InvalidValue", "No principal is provided"); try { final KeyFactory keyFactory = KeyFactory.getInstance( "RSA", "BC"); final X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec( B64.standard.dec( pubkey ) ); final PublicKey publicKey = keyFactory.generatePublic( publicKeySpec ); final X509Certificate vmCert = EuareServerCertificateUtil.generateVMCertificate( (RSAPublicKey) publicKey, principal, MoreObjects.firstNonNull( expirationDays, 180 ) ); final String certPem = new String( PEMFiles.getBytes( vmCert ) ); result.setPem( certPem ); } catch( final Exception ex ) { throw new IdentityServiceReceiverException( "InternalError", String.valueOf( ex.getMessage( ) )); } response.setSignCertificateResult( result ); return response; } public TunnelActionResponseType tunnelAction( final TunnelActionType request ) throws IdentityServiceException { final TunnelActionResponseType response = request.getReply( ); final TunnelActionResult result = new TunnelActionResult( ); try { final String content = request.getContent( ); final Binding binding = BindingManager.getDefaultBinding( ); final BaseMessage euareRequest; try ( final LockResource lock = LockResource.lock( HoldMe.canHas ) ) { final StAXOMBuilder omBuilder = HoldMe.getStAXOMBuilder( HoldMe.getXMLStreamReader( content ) ); final OMElement message = omBuilder.getDocumentElement( ); final Class<?> messageType = binding.getElementClass( message.getLocalName() ); euareRequest = (BaseMessage) binding.fromOM( message, messageType ); //TODO:STEVE: allow for (subminor?) version differences } final BaseMessage euareResponse = AsyncRequests.sendSync( Euare.class, euareRequest ); final StringWriter writer = new StringWriter( ); try ( final LockResource lock = LockResource.lock( HoldMe.canHas ) ) { final OMElement message = binding.toOM( euareResponse ); final OMOutputFormat format = new OMOutputFormat( ); format.setIgnoreXMLDeclaration( true ); message.serialize( writer ); } result.setContent( writer.toString( ) ); } catch ( Exception e ) { @SuppressWarnings( "ThrowableResultOfMethodCallIgnored" ) final EuareException euareException = Exceptions.findCause( e, EuareException.class ); if ( euareException != null ) { throw new IdentityServiceStatusException( euareException.getError( ), euareException.getStatus( ).getCode( ), euareException.getMessage( ) ); } throw handleException( e ); } response.setTunnelActionResult( result ); return response; } private static boolean isNotFoundError( final AuthException e ) { switch ( Strings.nullToEmpty( e.getMessage( ) ) ) { case AuthException.NO_SUCH_ACCOUNT: case AuthException.NO_SUCH_CERTIFICATE: case AuthException.NO_SUCH_INSTANCE_PROFILE: case AuthException.NO_SUCH_ROLE: case AuthException.NO_SUCH_USER: return true; } return false; } /** * Method always throws, signature allows use of "throw handleException ..." */ private static IdentityServiceException handleException( final Exception e ) throws IdentityServiceException { final IdentityServiceException cause = Exceptions.findCause( e, IdentityServiceException.class ); if ( cause != null ) { throw cause; } AuthException auth = Exceptions.findCause( e, AuthException.class ); if ( auth == null || !isNotFoundError( auth ) ) { logger.error( e, e ); } final IdentityServiceException exception = new IdentityServiceReceiverException( "InternalError", String.valueOf( e.getMessage( ) ) ); if ( Contexts.lookup( ).hasAdministrativePrivileges( ) ) { exception.initCause( e ); } throw exception; } @TypeMapper public enum PolicyVersionToPolicyTransform implements Function<PolicyVersion,Policy> { INSTANCE; @Nullable @Override public Policy apply( final PolicyVersion policyVersion ) { final Policy policy = new Policy(); policy.setVersionId( policyVersion.getPolicyVersionId() ); policy.setName( policyVersion.getPolicyName() ); policy.setScope( policyVersion.getPolicyScope().toString() ); policy.setPolicy( policyVersion.getPolicy() ); policy.setHash( PolicyVersions.hash( policyVersion.getPolicy( ) ) ); return policy; } } @TypeMapper public enum AccountIdentifiersToAccountTransform implements Function<AccountIdentifiers,Account> { INSTANCE; @Nullable @Override public Account apply( final AccountIdentifiers accountIdentifiers ) { final Account account = new Account( ); account.setAccountNumber( accountIdentifiers.getAccountNumber() ); account.setAlias( accountIdentifiers.getAccountAlias() ); account.setCanonicalId( accountIdentifiers.getCanonicalId() ); return account; } } @TypeMapper public enum InstanceProfileToInstanceProfileTransform implements Function<InstanceProfile,com.eucalyptus.auth.euare.common.identity.InstanceProfile> { INSTANCE; @Nullable @Override public com.eucalyptus.auth.euare.common.identity.InstanceProfile apply( final InstanceProfile authProfile ) { final com.eucalyptus.auth.euare.common.identity.InstanceProfile profile = new com.eucalyptus.auth.euare.common.identity.InstanceProfile( ); profile.setInstanceProfileArn( authProfile.getInstanceProfileArn() ); profile.setInstanceProfileId( authProfile.getInstanceProfileId() ); return profile; } } @TypeMapper public enum RoleToRoleTransform implements Function<Role,com.eucalyptus.auth.euare.common.identity.Role> { INSTANCE; @Nullable @Override public com.eucalyptus.auth.euare.common.identity.Role apply( final Role authRole ) { final com.eucalyptus.auth.euare.common.identity.Role role = new com.eucalyptus.auth.euare.common.identity.Role( ); role.setRoleArn( authRole.getRoleArn( ) ); role.setRoleId( authRole.getRoleId( ) ); role.setSecret( authRole.getSecret() ); role.setAssumeRolePolicy( TypeMappers.transform( authRole.getPolicy(), Policy.class ) ); return role; } } @TypeMapper public enum OpenIdConnectProviderToOidcProviderTransform implements Function<OpenIdConnectProvider,OidcProvider> { INSTANCE; @Nullable @Override public OidcProvider apply( final OpenIdConnectProvider authProvider ) { final OidcProvider provider = new OidcProvider( ); provider.setProviderArn( authProvider.getArn( ) ); provider.setPort( authProvider.getPort( ) ); provider.setClientIds( Lists.newArrayList( authProvider.getClientIds( ) ) ); provider.setThumbprints( Lists.newArrayList( authProvider.getThumbprints( ) ) ); return provider; } } @TypeMapper public enum SecurityTokenContentToSecurityTokenTransform implements Function<SecurityTokenContent,SecurityToken> { INSTANCE; @Nullable @Override public SecurityToken apply( final SecurityTokenContent securityTokenContent ) { final SecurityToken securityToken = new SecurityToken( ); securityToken.setOriginatingAccessKeyId( securityTokenContent.getOriginatingAccessKeyId( ).orNull( ) ); securityToken.setOriginatingUserId( securityTokenContent.getOriginatingUserId( ).orNull( ) ); securityToken.setOriginatingRoleId( securityTokenContent.getOriginatingRoleId( ).orNull( ) ); securityToken.setNonce( securityTokenContent.getNonce( ) ); securityToken.setCreated( securityTokenContent.getCreated( ) ); securityToken.setExpires( securityTokenContent.getExpires( ) ); securityToken.setAttributes( Lists.newArrayList( securityTokenContent.getAttributes( ).entrySet( ) .stream( ) .map( entry -> new SecurityTokenAttribute( entry.getKey( ), entry.getValue( ) ) ) .collect( Collectors.toList( ) ) ) ); return securityToken; } } }