Java tutorial
/************************************************************************* * (c) Copyright 2017 Hewlett Packard Enterprise Development Company LP * * 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/. ************************************************************************/ package com.eucalyptus.ws.handlers; import java.io.InputStream; import java.net.URI; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import com.amazonaws.ReadLimitInfo; import com.amazonaws.SignableRequest; import com.amazonaws.auth.AWS4Signer; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.http.HttpMethodName; import com.eucalyptus.auth.Accounts; import com.eucalyptus.auth.principal.AccountIdentifiers; import com.eucalyptus.auth.principal.User; import com.eucalyptus.auth.tokens.SecurityTokenAWSCredentialsProvider; import com.eucalyptus.util.Exceptions; import com.eucalyptus.util.LockResource; import com.eucalyptus.util.Pair; import com.eucalyptus.ws.IoMessage; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import io.netty.buffer.ByteBufInputStream; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelOutboundHandlerAdapter; import io.netty.channel.ChannelPromise; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaders; /** * Outbound hmac signature v4 for internal use. * * If we switch to netty 4.x for inbound then this could extend HmacHandler / * IoHmacHandler and also cover inbound. */ @Sharable public class IoInternalHmacHandler extends ChannelOutboundHandlerAdapter { private static final long SESSION_DURATION = TimeUnit.MINUTES.toMillis(15); private static final long PRE_EXPIRY = TimeUnit.MINUTES.toMillis(5); private static final long EXPIRY_OFFSET = TimeUnit.MINUTES.toMillis(1); private final Lock credentialsRefreshLock = new ReentrantLock(); private final AtomicReference<Pair<Long, AWSCredentials>> credentialsRef = new AtomicReference<>(); private final Supplier<User> systemUserSupplier = Suppliers.memoizeWithExpiration(systemUserSupplier(), 1, TimeUnit.MINUTES); private final SecurityTokenAWSCredentialsProvider provider = new SecurityTokenAWSCredentialsProvider( systemUserSupplier, (int) SESSION_DURATION / 1000, 0); @Override public void write(final ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise) throws Exception { if (msg instanceof IoMessage && ((IoMessage) msg).isRequest()) { final IoMessage ioMessage = (IoMessage) msg; final FullHttpRequest httpRequest = (FullHttpRequest) ioMessage.getHttpMessage(); sign(httpRequest); } super.write(ctx, msg, promise); } private void sign(final FullHttpRequest request) { final AWS4Signer signer = new AWS4Signer(); signer.setRegionName("eucalyptus"); signer.setServiceName("internal"); signer.sign(wrapRequest(request), credentials()); } private SignableRequest<?> wrapRequest(final FullHttpRequest request) { return new SignableRequest() { @Override public Map<String, String> getHeaders() { return request.headers().entries().stream() .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } @Override public String getResourcePath() { return request.getUri(); } @Override public Map<String, List<String>> getParameters() { return Collections.emptyMap(); } @Override public URI getEndpoint() { return URI.create("http://" + request.headers().get(HttpHeaders.Names.HOST)); } @Override public HttpMethodName getHttpMethod() { return HttpMethodName.fromValue(request.getMethod().name()); } @Override public int getTimeOffset() { return 0; } @Override public InputStream getContent() { return new ByteBufInputStream(request.content().slice()); } @Override public InputStream getContentUnwrapped() { return getContent(); } @Override public ReadLimitInfo getReadLimitInfo() { return () -> request.content().readableBytes(); } @Override public Object getOriginalRequestObject() { throw new RuntimeException("Not supported"); } @Override public void addHeader(final String s, final String s1) { request.headers().set(s, s1); } @Override public void addParameter(final String s, final String s1) { throw new RuntimeException("Not supported"); } @Override public void setContent(final InputStream inputStream) { throw new RuntimeException("Not supported"); } }; } private AWSCredentials credentials() { final Pair<Long, AWSCredentials> credentialsPair = credentialsRef.get(); if (credentialsPair == null || credentialsPair.getLeft() < expiry(EXPIRY_OFFSET)) { // no credentials or they have expired, must wait for new try (final LockResource lock = LockResource.lock(credentialsRefreshLock)) { return perhapsRefreshCredentials(); } } else if (credentialsPair.getLeft() < expiry(PRE_EXPIRY)) { // credentials pre-expired, refresh if no one else is doing so try (final LockResource lock = LockResource.tryLock(credentialsRefreshLock)) { if (lock.isLocked()) { return perhapsRefreshCredentials(); } else { return credentialsPair.getRight(); } } } else { return credentialsPair.getRight(); } } /** * Caller must hold credentials lock. */ private AWSCredentials perhapsRefreshCredentials() { final Pair<Long, AWSCredentials> credentialsPair = credentialsRef.get(); if (credentialsPair != null && credentialsPair.getLeft() > expiry(EXPIRY_OFFSET)) { return credentialsPair.getRight(); } else { provider.refresh(); final Pair<Long, AWSCredentials> newCredentialsPair = Pair .of(System.currentTimeMillis() + SESSION_DURATION, provider.getCredentials()); credentialsRef.set(newCredentialsPair); return newCredentialsPair.getRight(); } } private long expiry(final long offset) { return System.currentTimeMillis() + offset; } private Supplier<User> systemUserSupplier() { return () -> { try { return Accounts.lookupSystemAccountByAlias(AccountIdentifiers.SYSTEM_ACCOUNT); } catch (final Exception e) { throw Exceptions.toUndeclared(e); } }; } }