/*************************************************************************
* Copyright 2009-2014 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.tokens;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.annotation.Nonnull;
import org.apache.log4j.Logger;
import com.eucalyptus.configurable.ConfigurableClass;
import com.eucalyptus.configurable.ConfigurableField;
import com.eucalyptus.configurable.ConfigurableProperty;
import com.eucalyptus.configurable.ConfigurablePropertyException;
import com.eucalyptus.configurable.PropertyChangeListener;
import com.eucalyptus.configurable.PropertyChangeListeners;
import com.eucalyptus.util.Strings;
import com.google.common.base.CharMatcher;
import com.google.common.base.MoreObjects;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
/**
*
*/
@SuppressWarnings( { "WeakerAccess", "unused" } )
@ConfigurableClass(root = "tokens", description = "Parameters controlling tokens service")
public class TokensServiceConfiguration {
private static final Logger logger = Logger.getLogger( TokensServiceConfiguration.class );
private static final Pattern NONE = Pattern.compile( "-" ); // invalid alias / algorithm
private static final String DEFAULT_ROLE_ARN_ALIAS = "eucalyptus";
private static final String DEFAULT_WEB_ID_SIG_ALGORITHMS = "RS512";
private static final String DEFAULT_OIDC_DISCOVERY_CACHE = "maximumSize=20, expireAfterWrite=15m";
@ConfigurableField(
description = "Actions to enable (ignored if empty)",
changeListener = TokensServicePropertyChangeListener.class )
public static volatile String enabledActions = "";
@ConfigurableField(
description = "Actions to disable",
changeListener = TokensServicePropertyChangeListener.class )
public static volatile String disabledActions = "";
@ConfigurableField(
initial = DEFAULT_ROLE_ARN_ALIAS,
description = "List of account aliases to allow in role ARNs",
changeListener = TokensServiceRoleArnPropertyChangeListener.class )
public static volatile String roleArnAliasWhitelist = DEFAULT_ROLE_ARN_ALIAS;
@ConfigurableField(
initial = "60",
description = "Web identity token maximum time skew (seconds)",
changeListener = PropertyChangeListeners.IsNonNegativeInteger.class )
public static volatile Long webIdentityTokenSkew = 60L;
@ConfigurableField(
initial = DEFAULT_WEB_ID_SIG_ALGORITHMS,
description = "List of JSON Web Signature algorithms to allow in web identity tokens",
changeListener = TokensServiceWebIdentitySignatureAlgorithmPropertyChangeListener.class )
public static volatile String webIdentitySignatureAlgorithmWhitelist = DEFAULT_WEB_ID_SIG_ALGORITHMS;
@ConfigurableField(
description = "OpenID Connect discovery cache configuration, for provider metadata",
initial = DEFAULT_OIDC_DISCOVERY_CACHE,
changeListener = PropertyChangeListeners.CacheSpecListener.class )
public static volatile String webIdentityOidcDiscoveryCache = DEFAULT_OIDC_DISCOVERY_CACHE;
@ConfigurableField(
description = "OpenID Connect discovery cache refresh expiry (seconds)",
initial = "60",
changeListener = PropertyChangeListeners.IsNonNegativeInteger.class )
public static volatile Long webIdentityOidcDiscoveryRefresh = 60L;
private static final AtomicReference<Set<String>> enabledActionsSet =
new AtomicReference<>( Collections.emptySet() );
private static final AtomicReference<Set<String>> disabledActionsSet =
new AtomicReference<>( Collections.emptySet() );
private static final AtomicReference<Pattern> roleArnAliasPattern =
new AtomicReference<>( Pattern.compile( DEFAULT_ROLE_ARN_ALIAS ) );
private static final AtomicReference<Pattern> webIdSignatureAlgorithmPattern =
new AtomicReference<>( Pattern.compile( DEFAULT_WEB_ID_SIG_ALGORITHMS ) );
public static Set<String> getEnabledActions( ) {
return enabledActionsSet.get( );
}
public static Set<String> getDisabledActions( ) {
return disabledActionsSet.get( );
}
public static Pattern getRoleArnAliasPattern( ) {
return roleArnAliasPattern.get( );
}
public static long getWebIdentityTokenTimeSkew( ) { return MoreObjects.firstNonNull( webIdentityTokenSkew, 0L ); };
public static Pattern getWebIdSignatureAlgorithmPattern( ) {
return webIdSignatureAlgorithmPattern.get( );
}
@SuppressWarnings( "StaticPseudoFunctionalStyleMethod" )
public static final class TokensServicePropertyChangeListener implements PropertyChangeListener {
@SuppressWarnings( "unchecked" )
@Override
public void fireChange( final ConfigurableProperty configurableProperty,
final Object newValue ) throws ConfigurablePropertyException {
try {
final Splitter splitter =
Splitter.on( CharMatcher.WHITESPACE.or( CharMatcher.anyOf( ",;|" ) ) ).trimResults( ).omitEmptyStrings( );
final String fieldName = configurableProperty.getField().getName() + "Set";
final Field field = TokensServiceConfiguration.class.getDeclaredField( fieldName );
field.setAccessible( true );
final Set<String> value =
ImmutableSet.copyOf( Iterables.transform( splitter.split( String.valueOf( newValue ) ), Strings.lower( ) ) );
logger.info( "Tokens service configuration updated " + configurableProperty.getDisplayName( ) + ": " + value );
((AtomicReference<Set<String>>)field.get( null )).set( value );
} catch ( NoSuchFieldException | IllegalAccessException e ) {
logger.error( "Error updating token service configuration for " + configurableProperty.getDisplayName( ), e );
}
}
}
@SuppressWarnings( "StaticPseudoFunctionalStyleMethod" )
public static final class TokensServiceRoleArnPropertyChangeListener implements PropertyChangeListener {
@Override
public void fireChange( final ConfigurableProperty configurableProperty,
final Object newValue ) throws ConfigurablePropertyException {
setPatternFromPropertyValueList( roleArnAliasPattern, configurableProperty, newValue, "[0-9\\p{javaLowerCase}*-]+" );
}
}
@SuppressWarnings( "StaticPseudoFunctionalStyleMethod" )
public static final class TokensServiceWebIdentitySignatureAlgorithmPropertyChangeListener
implements PropertyChangeListener {
@Override
public void fireChange( final ConfigurableProperty configurableProperty,
final Object newValue ) throws ConfigurablePropertyException {
setPatternFromPropertyValueList( webIdSignatureAlgorithmPattern, configurableProperty, newValue, "[\\w*-]+" );
}
}
private static void setPatternFromPropertyValueList(
final AtomicReference<Pattern> reference,
final ConfigurableProperty configurableProperty,
final Object newValue,
final String itemRegex
) throws ConfigurablePropertyException {
try {
final String strValue = com.google.common.base.Strings.emptyToNull( Objects.toString( newValue, "" ) );
if ( strValue != null ) {
reference.set( buildPatternFromWildcardList( strValue, itemRegex ) );
} else {
reference.set( NONE );
}
} catch ( final PatternSyntaxException e ) {
logger.error( "Error updating token service configuration for " + configurableProperty.getDisplayName( ), e );
reference.set( NONE );
}
}
private static Pattern buildPatternFromWildcardList(
@Nonnull final String strValue,
@Nonnull final String itemRegex
) throws ConfigurablePropertyException {
final Splitter splitter =
Splitter.on( CharMatcher.WHITESPACE.or( CharMatcher.anyOf( ",;|" ) ) ).trimResults( ).omitEmptyStrings( );
final StringBuilder builder = new StringBuilder( );
builder.append( "(-|" );
for ( final String aliasWildcard : splitter.split( strValue ) ) {
builder.append( toPattern( aliasWildcard, itemRegex ) );
builder.append( '|' );
}
builder.append( "-)" );
return Pattern.compile( builder.toString( ) );
}
private static String toPattern( final String aliasWildcard, final String itemRegex ) throws ConfigurablePropertyException {
if ( !aliasWildcard.matches( itemRegex ) ) {
throw new ConfigurablePropertyException( "Invalid alias : " + aliasWildcard );
}
return aliasWildcard.replace( "*", ".*" );
}
}