001/**
002 * Copyright 2004-2014 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.common.aws.auth;
017
018import java.util.ArrayList;
019import java.util.List;
020
021import org.kuali.common.aws.model.ImmutableAWSCredentials;
022import org.kuali.common.aws.model.ImmutableAWSSessionCredentials;
023import org.kuali.common.util.Assert;
024import org.kuali.common.util.enc.EncUtils;
025import org.kuali.common.util.enc.EncryptionService;
026import org.kuali.common.util.spring.env.EnvironmentService;
027
028import com.amazonaws.auth.AWSCredentials;
029import com.amazonaws.auth.AWSCredentialsProvider;
030import com.amazonaws.auth.AWSCredentialsProviderChain;
031import com.amazonaws.auth.AWSSessionCredentials;
032import com.amazonaws.auth.InstanceProfileCredentialsProvider;
033import com.google.common.base.Optional;
034import com.google.common.collect.ImmutableList;
035
036/**
037 * This chain searches for AWS credentials in system properties -> environment variables -> Amazon's EC2 Instance Metadata Service
038 */
039public final class DefaultProviderChain extends AWSCredentialsProviderChain {
040
041        private final Optional<AWSCredentials> optionalCredentials;
042        private final Optional<EncryptionService> enc;
043        private final Optional<EnvironmentService> env;
044        private final boolean instanceCredentialsOverride;
045        private final List<AWSCredentialsProvider> providers;
046
047        public static class Builder {
048
049                // Optional
050                private Optional<EncryptionService> enc = Optional.absent();
051                private Optional<EnvironmentService> env = Optional.absent();
052                private Optional<AWSCredentials> optionalCredentials = Optional.absent();
053                private boolean instanceCredentialsOverride = false; // If true, EC2 instance credentials take precedence over optionalCredentials
054
055                // Filled in by the build() method
056                private List<AWSCredentialsProvider> providers;
057
058                private static final String INSTANCE_CREDENTIALS_OVERRIDE_KEY = "aws.instanceCredentialsOverride";
059
060                public Builder enc(EncryptionService enc) {
061                        this.enc = Optional.of(enc);
062                        return this;
063                }
064
065                public Builder env(EnvironmentService env) {
066                        this.env = Optional.of(env);
067                        return this;
068                }
069
070                public Builder credentials(AWSCredentials credentials) {
071                        this.optionalCredentials = Optional.of(credentials);
072                        return this;
073                }
074
075                public Builder instanceCredentialsOverride(boolean instanceCredentialsOverride) {
076                        this.instanceCredentialsOverride = instanceCredentialsOverride;
077                        return this;
078                }
079
080                private void override() {
081                        if (env.isPresent()) {
082                                instanceCredentialsOverride(env.get().getBoolean(INSTANCE_CREDENTIALS_OVERRIDE_KEY, instanceCredentialsOverride));
083                        }
084                }
085
086                private void validate(DefaultProviderChain provider) {
087                        Assert.noNulls(provider.getCredentials(), provider.getEnc(), provider.getEnv(), provider.getProviders());
088                        Assert.isTrue(provider.getProviders().size() > 0, "Must supply at least one provider");
089                }
090
091                public DefaultProviderChain build() {
092                        override();
093                        this.providers = getProviders();
094                        DefaultProviderChain provider = new DefaultProviderChain(this);
095                        validate(provider);
096                        return provider;
097                }
098
099                private List<AWSCredentialsProvider> getProviders() {
100                        // Set up some storage
101                        List<AWSCredentialsProvider> providers = new ArrayList<AWSCredentialsProvider>();
102
103                        // If we are examining the environment, system properties / environment variables always win
104                        if (env.isPresent()) {
105                                providers.add(new EnvCredentialsProvider.Builder(env.get()).build());
106                        }
107
108                        // Then fall through to "other" providers
109                        providers.addAll(getOther());
110
111                        // Make the list immutable
112                        return ImmutableList.copyOf(providers);
113                }
114
115                protected List<AWSCredentialsProvider> getOther() {
116                        // Amazon's EC2 Instance Metadata Service
117                        // http://docs.aws.amazon.com/AWSSdkDocsJava/latest/DeveloperGuide/java-dg-roles.html
118                        // AWS allows you to setup an IAM role and attach that role to an EC2 Instance at launch time
119                        // This allows you to automatically authorize java code running on an EC2 instance
120                        AWSCredentialsProvider ipcp = new InstanceProfileCredentialsProvider();
121
122                        // No optional credentials
123                        if (!optionalCredentials.isPresent()) {
124                                return ImmutableList.of(ipcp);
125                        }
126
127                        // Setup a provider that returns the optional credentials they supplied
128                        AWSCredentialsProvider simple = new SimpleCredentialsProvider(optionalCredentials.get());
129
130                        if (instanceCredentialsOverride) {
131                                // EC2 instance credentials first, then the credentials they supplied
132                                return ImmutableList.of(ipcp, simple);
133                        } else {
134                                // Credentials they supplied first, then the EC2 instance credentials
135                                return ImmutableList.of(simple, ipcp);
136                        }
137                }
138        }
139
140        public DefaultProviderChain(Builder builder) {
141                super(toArray(builder.providers));
142                this.optionalCredentials = builder.optionalCredentials;
143                this.enc = builder.enc;
144                this.env = builder.env;
145                this.instanceCredentialsOverride = builder.instanceCredentialsOverride;
146                this.providers = builder.providers;
147        }
148
149        private static AWSCredentialsProvider[] toArray(List<AWSCredentialsProvider> providers) {
150                return providers.toArray(new AWSCredentialsProvider[providers.size()]);
151        }
152
153        public Optional<AWSCredentials> getOptionalCredentials() {
154                return optionalCredentials;
155        }
156
157        public boolean isInstanceCredentialsOverride() {
158                return instanceCredentialsOverride;
159        }
160
161        @Override
162        public AWSCredentials getCredentials() {
163                AWSCredentials creds = super.getCredentials();
164                String accessKey = creds.getAWSAccessKeyId();
165                String secretKey = EncUtils.decrypt(enc, creds.getAWSSecretKey());
166                if (creds instanceof AWSSessionCredentials) {
167                        AWSSessionCredentials sessionCreds = (AWSSessionCredentials) creds;
168                        return ImmutableAWSSessionCredentials.build(accessKey, secretKey, sessionCreds.getSessionToken());
169                } else {
170                        return ImmutableAWSCredentials.build(accessKey, secretKey);
171                }
172        }
173
174        public List<AWSCredentialsProvider> getProviders() {
175                return providers;
176        }
177
178        public Optional<EncryptionService> getEnc() {
179                return enc;
180        }
181
182        public Optional<EnvironmentService> getEnv() {
183                return env;
184        }
185
186}