1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */

15
16 package software.amazon.awssdk.auth.credentials;
17
18 import static java.util.Collections.singletonMap;
19 import static java.util.Collections.unmodifiableSet;
20
21 import java.io.IOException;
22 import java.net.URI;
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.HashSet;
26 import java.util.Map;
27 import java.util.Set;
28 import software.amazon.awssdk.annotations.SdkPublicApi;
29 import software.amazon.awssdk.annotations.SdkTestInternalApi;
30 import software.amazon.awssdk.auth.credentials.internal.ContainerCredentialsRetryPolicy;
31 import software.amazon.awssdk.core.SdkSystemSetting;
32 import software.amazon.awssdk.core.exception.SdkClientException;
33 import software.amazon.awssdk.regions.util.ResourcesEndpointProvider;
34 import software.amazon.awssdk.regions.util.ResourcesEndpointRetryPolicy;
35 import software.amazon.awssdk.utils.StringUtils;
36 import software.amazon.awssdk.utils.ToString;
37
38 /**
39  * {@link AwsCredentialsProvider} implementation that loads credentials from a local metadata service.
40  *
41  * Currently supported containers:
42  * <ul>
43  *     <li>Amazon Elastic Container Service (ECS)</li>
44  *     <li>AWS Greengrass</li>
45  * </ul>
46  *
47  * <p>The URI path is retrieved from the environment variable "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" or
48  * "AWS_CONTAINER_CREDENTIALS_FULL_URI" in the container's environment. If the environment variable is not set, this credentials
49  * provider will throw an exception.</p>
50  *
51  * @see <a href="http://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html">Amazon Elastic Container
52  * Service (ECS)</a>
53  */

54 @SdkPublicApi
55 public final class ContainerCredentialsProvider extends HttpCredentialsProvider {
56     private final ResourcesEndpointProvider credentialsEndpointProvider;
57
58     /**
59      * @see #builder()
60      */

61     private ContainerCredentialsProvider(BuilderImpl builder) {
62         super(builder);
63         this.credentialsEndpointProvider = builder.credentialsEndpointProvider;
64     }
65
66     /**
67      * Create a builder for creating a {@link ContainerCredentialsProvider}.
68      */

69     public static Builder builder() {
70         return new BuilderImpl();
71     }
72
73     @Override
74     protected ResourcesEndpointProvider getCredentialsEndpointProvider() {
75         return credentialsEndpointProvider;
76     }
77
78     @Override
79     public String toString() {
80         return ToString.create("ContainerCredentialsProvider");
81     }
82
83     static final class ContainerCredentialsEndpointProvider implements ResourcesEndpointProvider {
84         private static final Set<String> ALLOWED_HOSTS = unmodifiableSet(new HashSet<>(Arrays.asList("localhost""127.0.0.1")));
85
86         @Override
87         public URI endpoint() throws IOException {
88
89             if (!SdkSystemSetting.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI.getStringValue().isPresent() &&
90                 !SdkSystemSetting.AWS_CONTAINER_CREDENTIALS_FULL_URI.getStringValue().isPresent()) {
91                 throw SdkClientException.builder()
92                         .message(String.format("Cannot fetch credentials from container - neither %s or %s " +
93                                  "environment variables are set.",
94                                  SdkSystemSetting.AWS_CONTAINER_CREDENTIALS_FULL_URI.environmentVariable(),
95                                  SdkSystemSetting.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI.environmentVariable()))
96                         .build();
97             }
98
99             try {
100                 return SdkSystemSetting.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI.getStringValue()
101                                                                               .map(this::createUri)
102                                                                               .orElseGet(this::createGenericContainerUrl);
103             } catch (SdkClientException e) {
104                 throw e;
105             } catch (Exception e) {
106                 throw SdkClientException.builder()
107                                         .message("Unable to fetch credentials from container.")
108                                         .cause(e)
109                                         .build();
110             }
111         }
112
113         @Override
114         public ResourcesEndpointRetryPolicy retryPolicy() {
115             return new ContainerCredentialsRetryPolicy();
116         }
117
118         @Override
119         public Map<String, String> headers() {
120             return SdkSystemSetting.AWS_CONTAINER_AUTHORIZATION_TOKEN.getStringValue()
121                                                                      .filter(StringUtils::isNotBlank)
122                                                                      .map(t -> singletonMap("Authorization", t))
123                                                                      .orElseGet(Collections::emptyMap);
124         }
125
126         private URI createUri(String relativeUri) {
127             return URI.create(SdkSystemSetting.AWS_CONTAINER_SERVICE_ENDPOINT.getStringValueOrThrow() + relativeUri);
128         }
129
130         private URI createGenericContainerUrl() {
131             URI uri = URI.create(SdkSystemSetting.AWS_CONTAINER_CREDENTIALS_FULL_URI.getStringValueOrThrow());
132             if (!ALLOWED_HOSTS.contains(uri.getHost())) {
133
134                 throw SdkClientException.builder()
135                                         .message(String.format("The full URI (%s) contained within environment " +
136                                                  "variable %s has an invalid host. Host can only be one of [%s].",
137                                                  uri,
138                                                  SdkSystemSetting.AWS_CONTAINER_CREDENTIALS_FULL_URI
139                                                                  .environmentVariable(),
140                                                                String.join(",", ALLOWED_HOSTS)))
141                                         .build();
142             }
143             return uri;
144         }
145     }
146
147     /**
148      * A builder for creating a custom a {@link ContainerCredentialsProvider}.
149      */

150     public interface Builder extends HttpCredentialsProvider.Builder<ContainerCredentialsProvider, Builder> {
151
152         /**
153          * Build a {@link ContainerCredentialsProvider} from the provided configuration.
154          */

155         @Override
156         ContainerCredentialsProvider build();
157     }
158
159     static final class BuilderImpl extends HttpCredentialsProvider.BuilderImpl<ContainerCredentialsProvider, Builder>
160         implements Builder {
161
162         private ResourcesEndpointProvider credentialsEndpointProvider = new ContainerCredentialsEndpointProvider();
163
164         BuilderImpl() {
165             super.asyncThreadName("container-credentials-provider");
166         }
167
168         @SdkTestInternalApi
169         Builder credentialsEndpointProvider(ResourcesEndpointProvider credentialsEndpointProvider) {
170             this.credentialsEndpointProvider = credentialsEndpointProvider;
171             return this;
172         }
173
174         @Override
175         public ContainerCredentialsProvider build() {
176             return new ContainerCredentialsProvider(this);
177         }
178     }
179 }
180