ch.cyberduck.core.kms.KMSEncryptionFeature.java Source code

Java tutorial

Introduction

Here is the source code for ch.cyberduck.core.kms.KMSEncryptionFeature.java

Source

package ch.cyberduck.core.kms;

/*
 * Copyright (c) 2002-2016 iterate GmbH. All rights reserved.
 * https://cyberduck.io/
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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.
 */

import ch.cyberduck.core.Host;
import ch.cyberduck.core.KeychainLoginService;
import ch.cyberduck.core.LocaleFactory;
import ch.cyberduck.core.LoginCallback;
import ch.cyberduck.core.LoginOptions;
import ch.cyberduck.core.PasswordStoreFactory;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.PathContainerService;
import ch.cyberduck.core.PreferencesUseragentProvider;
import ch.cyberduck.core.UseragentProvider;
import ch.cyberduck.core.exception.AccessDeniedException;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.exception.LoginFailureException;
import ch.cyberduck.core.features.Location;
import ch.cyberduck.core.iam.AmazonServiceExceptionMappingService;
import ch.cyberduck.core.preferences.Preferences;
import ch.cyberduck.core.preferences.PreferencesFactory;
import ch.cyberduck.core.proxy.Proxy;
import ch.cyberduck.core.proxy.ProxyFactory;
import ch.cyberduck.core.s3.S3EncryptionFeature;
import ch.cyberduck.core.s3.S3PathContainerService;
import ch.cyberduck.core.s3.S3Session;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;

import com.amazonaws.AmazonClientException;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.kms.AWSKMSClient;
import com.amazonaws.services.kms.model.AliasListEntry;
import com.amazonaws.services.kms.model.KeyListEntry;

public class KMSEncryptionFeature extends S3EncryptionFeature {
    private static final Logger log = Logger.getLogger(KMSEncryptionFeature.class);

    private final Host host;
    private final S3Session session;

    private final Preferences preferences = PreferencesFactory.get();

    private final PathContainerService containerService = new S3PathContainerService();

    private final ClientConfiguration configuration;

    public KMSEncryptionFeature(final S3Session session) {
        this(session, PreferencesFactory.get().getInteger("connection.timeout.seconds") * 1000);
    }

    public KMSEncryptionFeature(final S3Session session, final int timeout) {
        super(session);
        host = session.getHost();
        this.session = session;
        configuration = new ClientConfiguration();
        configuration.setConnectionTimeout(timeout);
        configuration.setSocketTimeout(timeout);
        final UseragentProvider ua = new PreferencesUseragentProvider();
        configuration.setUserAgent(ua.get());
        configuration.setMaxErrorRetry(0);
        configuration.setMaxConnections(1);
        configuration.setUseGzip(PreferencesFactory.get().getBoolean("http.compression.enable"));
        final Proxy proxy = ProxyFactory.get().find(host);
        switch (proxy.getType()) {
        case HTTP:
        case HTTPS:
            configuration.setProxyHost(proxy.getHostname());
            configuration.setProxyPort(proxy.getPort());
        }
    }

    private interface Authenticated<T> extends Callable<T> {
        T call() throws BackgroundException;
    }

    private <T> T authenticated(final Authenticated<T> run, final LoginCallback prompt) throws BackgroundException {
        final LoginOptions options = new LoginOptions();
        try {
            final KeychainLoginService login = new KeychainLoginService(prompt, PasswordStoreFactory.get());
            login.validate(host, LocaleFactory.localizedString("AWS Key Management Service", "S3"), options);
            return run.call();
        } catch (LoginFailureException failure) {
            prompt.prompt(host, host.getCredentials(), LocaleFactory.localizedString("Login failed", "Credentials"),
                    failure.getMessage(), options);
            return this.authenticated(run, prompt);
        }
    }

    @Override
    public Algorithm getDefault(final Path file) {
        final String setting = preferences.getProperty("s3.encryption.algorithm");
        if (StringUtils.equals(KMSEncryptionFeature.SSE_KMS_DEFAULT.algorithm, setting)) {
            final String key = String.format("s3.encryption.key.%s", containerService.getContainer(file).getName());
            if (StringUtils.isNotBlank(preferences.getProperty(key))) {
                return Algorithm.fromString(preferences.getProperty(key));
            }
            return KMSEncryptionFeature.SSE_KMS_DEFAULT;
        }
        return super.getDefault(file);
    }

    @Override
    public Algorithm getEncryption(final Path file) throws BackgroundException {
        if (containerService.isContainer(file)) {
            final String key = String.format("s3.encryption.key.%s", containerService.getContainer(file).getName());
            if (StringUtils.isNotBlank(preferences.getProperty(key))) {
                return Algorithm.fromString(preferences.getProperty(key));
            }
        }
        return super.getEncryption(file);
    }

    @Override
    public void setEncryption(final Path file, final Algorithm setting) throws BackgroundException {
        if (containerService.isContainer(file)) {
            final String key = String.format("s3.encryption.key.%s", containerService.getContainer(file).getName());
            preferences.setProperty(key, setting.toString());
        }
        super.setEncryption(file, setting);
    }

    /**
     * @return List of IDs of KMS managed keys
     */
    @Override
    public Set<Algorithm> getKeys(final Path container, final LoginCallback prompt) throws BackgroundException {
        final Set<Algorithm> keys = super.getKeys(container, prompt);
        try {
            keys.addAll(this.authenticated(new Authenticated<Set<Algorithm>>() {
                @Override
                public Set<Algorithm> call() throws BackgroundException {
                    // Create new IAM credentials
                    final AWSKMSClient client = new AWSKMSClient(new com.amazonaws.auth.AWSCredentials() {
                        @Override
                        public String getAWSAccessKeyId() {
                            return host.getCredentials().getUsername();
                        }

                        @Override
                        public String getAWSSecretKey() {
                            return host.getCredentials().getPassword();
                        }
                    }, configuration);
                    final Location feature = session.getFeature(Location.class);
                    final Location.Name region = feature.getLocation(containerService.getContainer(container));
                    client.setRegion(Region.getRegion(Regions.fromName(region.getIdentifier())));
                    try {
                        final Map<String, String> aliases = new HashMap<String, String>();
                        for (AliasListEntry entry : client.listAliases().getAliases()) {
                            aliases.put(entry.getTargetKeyId(), entry.getAliasName());
                        }
                        final Set<Algorithm> keys = new HashSet<Algorithm>();
                        for (KeyListEntry entry : client.listKeys().getKeys()) {
                            keys.add(new AliasedAlgorithm(entry, aliases.get(entry.getKeyId()), region));
                        }
                        return keys;
                    } catch (AmazonClientException e) {
                        throw new AmazonServiceExceptionMappingService().map("Cannot read AWS KMS configuration",
                                e);
                    } finally {
                        client.shutdown();
                    }
                }
            }, prompt));
        } catch (AccessDeniedException e) {
            log.warn(String.format("Ignore failure reading keys from KMS. %s", e.getMessage()));
            keys.add(SSE_KMS_DEFAULT);
        }
        return keys;
    }

    /**
     * Default KMS Managed SSE with default key
     */
    public static final Algorithm SSE_KMS_DEFAULT = new Algorithm("aws:kms", null) {
        @Override
        public String getDescription() {
            return "SSE-KMS";
        }
    };

    private static class AliasedAlgorithm extends Algorithm {
        private final KeyListEntry entry;
        private final String alias;
        private final Location.Name region;

        public AliasedAlgorithm(final KeyListEntry entry, final String alias, final Location.Name region) {
            super(KMSEncryptionFeature.SSE_KMS_DEFAULT.algorithm, entry.getKeyArn());
            this.entry = entry;
            this.alias = alias;
            this.region = region;
        }

        @Override
        public String getDescription() {
            if (StringUtils.isBlank(alias)) {
                return String.format("SSE-KMS (%s)", entry.getKeyArn());
            }
            return String.format("SSE-KMS (%s - %s)", alias, entry.getKeyArn());
        }

        public Location.Name getRegion() {
            return region;
        }
    }
}