com.eucalyptus.auth.euare.identity.region.RegionConfigurationManager.java Source code

Java tutorial

Introduction

Here is the source code for com.eucalyptus.auth.euare.identity.region.RegionConfigurationManager.java

Source

/*************************************************************************
 * 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.identity.region;

import static com.eucalyptus.auth.RegionService.regionName;
import static com.eucalyptus.auth.RegionService.serviceType;
import static com.eucalyptus.auth.euare.identity.region.RegionInfo.RegionService;
import static com.eucalyptus.util.CollectionUtils.propertyContainsPredicate;
import static com.eucalyptus.util.CollectionUtils.propertyPredicate;
import java.net.InetAddress;
import java.net.URI;
import java.security.MessageDigest;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.eucalyptus.auth.*;
import com.eucalyptus.auth.util.Identifiers;
import com.eucalyptus.component.ComponentId;
import com.eucalyptus.component.ComponentIds;
import com.eucalyptus.component.Components;
import com.eucalyptus.component.ServiceConfiguration;
import com.eucalyptus.component.ServiceConfigurations;
import com.eucalyptus.component.groups.ApiEndpointServicesGroup;
import com.eucalyptus.crypto.Digest;
import com.eucalyptus.util.Cidr;
import com.eucalyptus.util.CollectionUtils;
import com.eucalyptus.util.NonNullFunction;
import com.eucalyptus.util.TypeMapper;
import com.eucalyptus.util.TypeMappers;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.google.common.io.BaseEncoding;
import com.google.common.net.InetAddresses;

/**
 *
 */
public class RegionConfigurationManager {

    private static final Supplier<Optional<RegionConfiguration>> regionConfigurationSupplier = Suppliers
            .memoizeWithExpiration(new Supplier<Optional<RegionConfiguration>>() {
                @Override
                public Optional<RegionConfiguration> get() {
                    return RegionConfigurations.getRegionConfiguration();
                }
            }, 1, TimeUnit.MINUTES);

    private static final Cache<String, byte[]> certificateDigestCache = CacheBuilder
            .<String, X509Certificate>newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build();

    /**
     * Get information for a region based on an IAM identifier (AKI, etc)
     *
     * @param identifier The identifier to use
     * @return The region info optional
     */
    @Nonnull
    public Optional<RegionInfo> getRegionByIdentifier(@Nullable final String identifier) {
        Optional<RegionInfo> regionInfoOptional = Optional.absent();
        final Optional<RegionConfiguration> configurationOptional = regionConfigurationSupplier.get();
        if (configurationOptional.isPresent() && identifier != null && identifier.length() > 5) {
            final RegionConfiguration configuration = configurationOptional.get();
            final String regionIdPartition = identifier.substring(3, 5);
            for (final Region region : configuration) {
                if (Iterables.contains(
                        Iterables.transform(region.getIdentifierPartitions(), PartitionFunctions.IDENTIFIER),
                        regionIdPartition)) {
                    regionInfoOptional = Optional
                            .of(regionToRegionInfoTransform(configurationOptional).apply(region));
                }
            }
        }
        return regionInfoOptional;
    }

    /**
     * Get information for a region based on an account identifier
     *
     * @param accountNumber The account number to use
     * @return The region info optional
     */
    @Nonnull
    public Optional<RegionInfo> getRegionByAccountNumber(@Nullable final String accountNumber) {
        Optional<RegionInfo> regionInfoOptional = Optional.absent();
        final Optional<RegionConfiguration> configurationOptional = regionConfigurationSupplier.get();
        if (configurationOptional.isPresent() && accountNumber != null && accountNumber.length() == 12) {
            final RegionConfiguration configuration = configurationOptional.get();
            final String regionIdPartition = accountNumber.substring(0, 3);
            for (final Region region : configuration) {
                if (Iterables.contains(
                        Iterables.transform(region.getIdentifierPartitions(), PartitionFunctions.ACCOUNT_NUMBER),
                        regionIdPartition)) {
                    regionInfoOptional = Optional
                            .of(regionToRegionInfoTransform(configurationOptional).apply(region));
                }
            }
        }
        return regionInfoOptional;
    }

    /**
     * Get the region information for the local region (if any)
     *
     * @return The optional region information
     */
    public Optional<RegionInfo> getRegionInfo() {
        final Optional<RegionConfiguration> regionConfigurationOptional = regionConfigurationSupplier.get();
        return Iterables
                .tryFind(Iterables.concat(regionConfigurationOptional.asSet()),
                        propertyPredicate(RegionConfigurations.getRegionName().asSet(),
                                RegionNameTransform.INSTANCE))
                .transform(regionToRegionInfoTransform(regionConfigurationOptional));
    }

    /**
     * Get the region information for the local region (if any)
     *
     * @return The optional region information
     */
    public Optional<RegionInfo> getRegionInfoByHost(final String host) {
        final Optional<RegionConfiguration> regionConfigurationOptional = regionConfigurationSupplier.get();
        return Iterables
                .tryFind(Iterables.concat(regionConfigurationOptional.asSet()),
                        propertyContainsPredicate(host, RegionServiceHostTransform.INSTANCE))
                .transform(regionToRegionInfoTransform(regionConfigurationOptional));
    }

    /**
     * Get all region information (if any)
     *
     * @return The region information
     */
    public Iterable<RegionInfo> getRegionInfos() {
        final Optional<RegionConfiguration> regionConfigurationOptional = regionConfigurationSupplier.get();
        return Iterables.transform(Iterables.concat(regionConfigurationOptional.asSet()),
                regionToRegionInfoTransform(regionConfigurationOptional));
    }

    public boolean isRegionSSLCertificate(final String host, final X509Certificate certificate) {
        boolean valid = false;
        final Optional<RegionInfo> hostRegion = getRegionInfoByHost(host);
        if (hostRegion.isPresent()) {
            if (hostRegion.get().getSslCertificateFingerprint() != null) {
                valid = digestMatches(hostRegion.get().getSslCertificateFingerprintDigest(),
                        hostRegion.get().getSslCertificateFingerprint(), certificate);
            } else {
                valid = digestMatches(hostRegion.get().getCertificateFingerprintDigest(),
                        hostRegion.get().getCertificateFingerprint(), certificate);
            }
        }
        return valid;
    }

    public boolean isRegionCertificate(final X509Certificate certificate) {
        boolean found = false;
        final Optional<RegionConfiguration> configurationOptional = regionConfigurationSupplier.get();
        if (configurationOptional.isPresent()) {
            final RegionConfiguration configuration = configurationOptional.get();
            for (final Region region : configuration) {
                if (digestMatches(region.getCertificateFingerprintDigest(), region.getCertificateFingerprint(),
                        certificate)) {
                    found = true;
                    break;
                }
            }
        }
        return found;
    }

    private boolean digestMatches(final String fingerprintDigest, final String fingerprint,
            final X509Certificate certificate) {
        try {
            final Digest digest = Digest.forAlgorithm(fingerprintDigest).or(Digest.SHA256);
            final byte[] regionCertificateFingerprint = certificateDigestCache.get(fingerprint,
                    new Callable<byte[]>() {
                        @Override
                        public byte[] call() throws Exception {
                            return BaseEncoding.base16().withSeparator(":", 2).decode(fingerprint);
                        }
                    });
            return MessageDigest.isEqual(regionCertificateFingerprint,
                    digest.digestBinary(certificate.getEncoded()));
        } catch (ExecutionException | CertificateEncodingException e) {
            // skip the certificate
            return false;
        }
    }

    public boolean isValidRemoteAddress(final InetAddress inetAddress) {
        return isValidAddress(inetAddress, RegionInfoToCidrSetTransform.REMOTE);
    }

    public boolean isValidForwardedForAddress(final String address) {
        boolean valid = false;
        try {
            valid = isValidAddress(InetAddresses.forString(address), RegionInfoToCidrSetTransform.FORWARDED_FOR);
        } catch (final IllegalArgumentException e) {
            // invalid
        }
        return valid;
    }

    private boolean isValidAddress(final InetAddress inetAddress,
            final NonNullFunction<RegionInfo, Set<Cidr>> cidrTransform) {
        final Optional<RegionInfo> regionInfoOptional = getRegionInfo();
        final Predicate<InetAddress> addressPredicate = Predicates
                .or(Iterables.concat(regionInfoOptional.transform(cidrTransform).asSet()));
        return addressPredicate.apply(inetAddress);
    }

    private enum PartitionFunctions implements NonNullFunction<Integer, String> {
        ACCOUNT_NUMBER {
            @Nonnull
            @Override
            public String apply(final Integer integer) {
                return Strings.padStart(String.valueOf(integer), 3, '0');
            }
        },
        IDENTIFIER {
            private final char[] characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".toCharArray();

            @Nonnull
            @Override
            public String apply(final Integer integer) {
                return new String(new char[] { characters[integer / 32], characters[integer % 32] });
            }
        },
    }

    private enum RegionInfoToCidrSetTransform implements NonNullFunction<RegionInfo, Set<Cidr>> {
        FORWARDED_FOR {
            @Nonnull
            @Override
            public Set<Cidr> apply(final RegionInfo regionInfo) {
                return regionInfo.getForwardedForCidrs();
            }
        },
        REMOTE {
            @Nonnull
            @Override
            public Set<Cidr> apply(final RegionInfo regionInfo) {
                return regionInfo.getRemoteCidrs();
            }
        },
    }

    private enum RegionConfigurationToCidrListTransform
            implements NonNullFunction<RegionConfiguration, List<String>> {
        FORWARDED_FOR {
            @Nonnull
            @Override
            public List<String> apply(final RegionConfiguration regionConfiguration) {
                final List<String> cidrs = regionConfiguration.getForwardedForCidrs();
                return cidrs == null ? Collections.<String>emptyList() : cidrs;
            }
        },
        REMOTE {
            @Nonnull
            @Override
            public List<String> apply(final RegionConfiguration regionConfiguration) {
                final List<String> cidrs = regionConfiguration.getRemoteCidrs();
                return cidrs == null ? Collections.<String>emptyList() : cidrs;
            }
        },
    }

    private enum RegionNameTransform implements NonNullFunction<Region, String> {
        INSTANCE;

        @Nonnull
        @Override
        public String apply(final Region region) {
            return region.getName();
        }
    }

    private enum RegionServiceHostTransform implements NonNullFunction<Region, Set<String>> {
        INSTANCE;

        @Nonnull
        @Override
        public Set<String> apply(final Region region) {
            final Set<String> hosts = Sets.newHashSet();
            if (region.getServices() != null)
                for (final Service service : region.getServices()) {
                    if (service.getEndpoints() != null)
                        for (final String uri : service.getEndpoints()) {
                            hosts.add(URI.create(uri).getHost());
                        }
                }
            return hosts;
        }
    }

    private enum RegionInfoPartitionsTransform implements NonNullFunction<RegionInfo, Set<Integer>> {
        INSTANCE;

        @Nonnull
        @Override
        public Set<Integer> apply(final RegionInfo region) {
            return region.getPartitions();
        }
    }

    private static NonNullFunction<Region, RegionInfo> regionToRegionInfoTransform(
            final Optional<RegionConfiguration> regionConfigurationOptional) {
        return new NonNullFunction<Region, RegionInfo>() {
            @Nonnull
            @Override
            public RegionInfo apply(final Region region) {
                return new RegionInfo(region.getName(), region.getIdentifierPartitions(),
                        Collections2.transform(region.getServices(),
                                TypeMappers.lookup(Service.class, RegionService.class)),
                        buildCidrs(regionConfigurationOptional
                                .transform(RegionConfigurationToCidrListTransform.REMOTE).orNull(),
                                region.getRemoteCidrs()),
                        buildCidrs(
                                regionConfigurationOptional
                                        .transform(RegionConfigurationToCidrListTransform.FORWARDED_FOR).orNull(),
                                region.getForwardedForCidrs()),
                        region.getCertificateFingerprintDigest(), region.getCertificateFingerprint(),
                        region.getSslCertificateFingerprintDigest(), region.getSslCertificateFingerprint());
            }
        };
    }

    private static Set<Cidr> buildCidrs(final List<String> cidrList, final List<String> regionCidrList) {
        final Set<String> cidrs = Sets.newLinkedHashSet();
        if (cidrList != null)
            cidrs.addAll(cidrList);
        if (regionCidrList != null)
            cidrs.addAll(regionCidrList);
        if (cidrs.isEmpty())
            cidrs.add("0.0.0.0/0");
        return Sets.newLinkedHashSet(Iterables.transform(cidrs, Cidr.parseUnsafe()));
    }

    @TypeMapper
    private enum ServiceToRegionServiceTransform implements Function<Service, RegionService> {
        INSTANCE;

        @Nullable
        @Override
        public RegionService apply(@Nullable final Service service) {
            return service == null ? null : new RegionService(service.getType(), service.getEndpoints());
        }
    }

    private enum RegionInfoToRegionServiceTransform
            implements NonNullFunction<RegionInfo, Iterable<com.eucalyptus.auth.RegionService>> {
        INSTANCE;

        @Nonnull
        @Override
        public Iterable<com.eucalyptus.auth.RegionService> apply(final RegionInfo region) {
            final Collection<com.eucalyptus.auth.RegionService> services = Lists.newArrayList();
            for (final RegionService service : region.getServices()) {
                for (final String endpoint : service.getEndpoints()) {
                    services.add(
                            new com.eucalyptus.auth.RegionService(region.getName(), service.getType(), endpoint));
                }
            }
            return services;
        }
    }

    public static class ConfiguredIdentifierPartitionSupplier implements Identifiers.IdentifierPartitionSupplier {
        private final RegionConfigurationManager regionConfigurationManager = new RegionConfigurationManager();

        @Override
        public Iterable<String> getIdentifierPartitions() {
            return Iterables.transform(
                    Iterables.concat(regionConfigurationManager.getRegionInfo()
                            .transform(RegionInfoPartitionsTransform.INSTANCE).asSet()),
                    PartitionFunctions.IDENTIFIER);
        }

        @Override
        public Iterable<String> getAccountNumberPartitions() {
            return Iterables.transform(
                    Iterables.concat(regionConfigurationManager.getRegionInfo()
                            .transform(RegionInfoPartitionsTransform.INSTANCE).asSet()),
                    PartitionFunctions.ACCOUNT_NUMBER);
        }
    }

    public static class ConfiguredRegionProvider implements Regions.RegionProvider {
        private final RegionConfigurationManager regionConfigurationManager = new RegionConfigurationManager();
        private final Supplier<RegionInfo> generatedRegionInfo = regionGeneratingSupplier();

        @Override
        public List<com.eucalyptus.auth.RegionService> getRegionServicesByType(final String serviceType) {
            final Optional<RegionInfo> configuredRegionInfo = regionConfigurationManager.getRegionInfo();
            final RegionInfo localRegionInfo = configuredRegionInfo.or(generatedRegionInfo);
            final Ordering<com.eucalyptus.auth.RegionService> ordering = Ordering.natural().onResultOf(regionName())
                    .compound(Ordering.natural().onResultOf(serviceType()));
            final NonNullFunction<RegionInfo, Iterable<com.eucalyptus.auth.RegionService>> transformer = RegionInfoToRegionServiceTransform.INSTANCE;
            final Set<com.eucalyptus.auth.RegionService> services = Sets.newTreeSet(ordering);
            Iterables.addAll(services, transformer.apply(localRegionInfo));
            Iterables.addAll(services, Iterables
                    .concat(Iterables.transform(regionConfigurationManager.getRegionInfos(), transformer)));
            return Lists.newArrayList(
                    Iterables.filter(services, CollectionUtils.propertyPredicate(serviceType, serviceType())));
        }

        private static Supplier<RegionInfo> regionGeneratingSupplier() {
            return new Supplier<RegionInfo>() {
                @Override
                public RegionInfo get() {
                    return new RegionInfo(
                            RegionConfigurations.getRegionName().or("eucalyptus"), Collections.singleton(0), Lists
                                    .newArrayList(
                                            Optional.presentInstances(
                                                    Iterables.transform(
                                                            Iterables.filter(ComponentIds.list(),
                                                                    ComponentIds.lookup(
                                                                            ApiEndpointServicesGroup.class)),
                                                            ComponentIdToRegionServiceTransform.INSTANCE))),
                            Collections.<Cidr>emptySet(), Collections.<Cidr>emptySet(), null, null, null, null);
                }
            };
        }

        private enum ComponentIdToRegionServiceTransform
                implements NonNullFunction<ComponentId, Optional<RegionService>> {
            INSTANCE;

            @Nonnull
            @Override
            public Optional<RegionService> apply(final ComponentId componentId) {
                final Iterable<ServiceConfiguration> serviceConfigurations = Components.lookup(componentId)
                        .services();
                return serviceConfigurations == null || Iterables.isEmpty(serviceConfigurations)
                        ? Optional.<RegionService>absent()
                        : Optional.of(new RegionService(componentId.name(), Sets.newTreeSet(Iterables
                                .transform(serviceConfigurations, ServiceConfigurations.remotePublicify()))));
            }
        }
    }
}