从 AWS Credentials Provider 获取安全令牌
Get security token from AWS Credentials Provider
有人可以解释一下吗,我需要如何实施 blog 的第一步?
我在 AWS 文档中找不到它。
也就是说,我需要翻译一个命令:
curl --cert eeb81a0eb6-certificate.pem.crt --key eeb81a0eb6-private.pem.key -H "x-amzn-iot-thingname: myThingName" --cacert AmazonRootCA1.pem https://<prefix>.credentials.iot.us-west-2.amazonaws.com/role-aliases/MyAlias/credentials
到JAVA。我该怎么做?我需要 AWS SDK(我更喜欢没有“自定义客户端发出 HTTPS 请求”的解决方案)
更新:
我尝试使用自定义客户端发出 HTTPS 请求,但在计划将我的密钥导出到 Java KeyStore 时我卡住了(但 curl 命令对我来说很好):
$ winpty openssl pkcs12 -export -in eeb81a0eb6-certificate.pem.crt -inkey eeb81a0eb6-private.pem.key -chain -CAfile AmazonRootCA1.pem -name mycompany.com -out my.p12
Error unable to get local issuer certificate getting chain.
另一个更新(我已经尝试过的)
将 myPrivateKey 和 deviceCertificate 转换为 JKS:
winpty openssl pkcs12 -export -in eeb81a0eb6-certificate.pem.crt -inkey eeb81a0eb6-private.pem.key -name mycompany.com -out my.p12
keytool -importkeystore -destkeystore mycompany.jks -srckeystore my.p12 -srcstoretype PKCS12
在我的代码中使用这个 JKS:
System.setProperty("deployment.security.TLSv1.2", "true");
System.setProperty("https.protocols", "TLSv1.2");
System.setProperty("javax.net.debug", "ssl");
HttpPost request = new HttpPost(clientEndpoint);
request.setHeader("x-amzn-iot-thingname", "0ad16050-d974-4f78-88ea-c6ee2b0a551e");
KeyStore keyStore;
try (InputStream keyStoreStream = this.getClass().getResourceAsStream(KEYSTOREPATH)) {
keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(keyStoreStream, KEYSTOREPASS.toCharArray());
}
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(keyStore, KEYPASS.toCharArray()) // use null as second param if you don't have a separate key password
.loadTrustMaterial(null, new TrustSelfSignedStrategy())
.build();
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext);
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("https", sslConnectionSocketFactory)
.register("http", new PlainConnectionSocketFactory())
.build();
BasicHttpClientConnectionManager manager = new BasicHttpClientConnectionManager(registry);
try (CloseableHttpClient httpClient = HttpClients
.custom()
.setSSLSocketFactory(sslConnectionSocketFactory)
.setConnectionManager(manager)
.build();
CloseableHttpResponse response = httpClient.execute(request)) {
System.out.println();
} catch (IOException e) {
System.err.println(e);
}
我得到异常:
javax.net.ssl.SSLHandshakeException:收到致命警报:bad_certificate
AWS SDK 提供了多个 SdkHttpClient
的实现,您可以使用它们与您的亚马逊服务进行同步或异步交互。
例如,您可以使用 ApacheHttpClient
class.
所有这些 HTTP 客户端都是为 ApacheHttpClient
创建和配置的 Builder
s, ApacheHttpClient.Builder
。
ApacheHttpClient.Builder
提供允许您为客户端、远程对等或相互身份验证配置安全 HTTP 连接的方法。
如果必须对客户端进行身份验证,则需要提供必须用于此目的的证书和私钥,对应于您的 [=26] 的 --cert
和 --key
参数=]调用。
通常,此证书和私钥存储在一个受密码保护的 KeyStore
中,通常采用 PKCS#12 格式(.p12
或 .pfx
文件)。
ApacheHttpClient.Builder
可以通过两种方式访问此信息。
首先,通过设置一系列System
属性:
import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE;
import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE_PASSWORD;
import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE_TYPE;
//...
Path clientKeyStore = Paths.get(...);
System.setProperty(SSL_KEY_STORE.property(), clientKeyStore.toAbsolutePath().toString());
System.setProperty(SSL_KEY_STORE_TYPE.property(), "pkcs12");
System.setProperty(SSL_KEY_STORE_PASSWORD.property(), "password");
注意:static
导入只是标准 JSSE 属性 javax.net.ssl.keyStore
、javax.net.ssl.keyStorePassword
和 javax.net.ssl.keyStoreType
.
的常量
其次,通过为 ApacheHttpClient.Builder
的 tlsKeyManagersProvider
方法提供 TlsKeyManagersProvider
实现。例如:
Path clientKeyStore = ...
TlsKeyManagersProvider keyManagersProvider = FileStoreTlsKeyManagersProvider.create(clientKeyStore, "pkcs12", "password");
事实上,在幕后,上面提到的 System
基于属性的配置被 SystemPropertyTlsKeyManagersProvider
另一个 TlsKeyManagersProvider
实现使用。
如果你需要验证服务器,你也有两个选择。
首先,再次设置几个 System
属性:
Path serverKeyStore = Paths.get(...);
System.setProperty("javax.net.ssl.trustStore", serverKeyStore.toAbsolutePath().toString());
System.setProperty("javax.net.ssl.trustStorePassword", "password");
System.setProperty("javax.net.ssl.trustStoreType", "jks");
如您所见,为了简单起见,这次我们使用了另一种KeyStore
、jks
。您可以从您的 AWS 服务器证书 PEM 文件(与您的 curl
命令中的 --cacert
相关联的文件)构建这样一个 KeyStore
,如下所示:
Path pemPath = ...;
try(final InputStream is = Files.newInputStream(pemPath) {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(is);
String alias = cert.getSubjectX500Principal().getName();
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
keyStore.setCertificateEntry(alias, cert);
}
在相互身份验证的情况下,虽然您可以重复使用相同的 KeyStore
,但最佳做法是维护两个,一个包含客户端私钥和证书,另一个包含您信任的服务器证书(信任库)。
或者,您也可以通过定义需要使用的 TrustManager
来配置服务器端身份验证。
对于此任务,ApacheHttpClient.Builder
提供了方法 tlsTrustManagersProvider
。此方法需要实现 TlsTrustManagersProvider 接口。
此接口定义了一个方法,trustManagers
,即 returns 必须用于检查 SSL 通信中的远程对等点的 TrustManager
数组。
很遗憾,AWS SDK 不提供此接口的实现,您需要自己实现(如果您需要更多信息,请告诉我)。
初始化和配置后,您可以使用 httpClient
或 httpClientBuilder
方法,分别。
如果您只需要像使用 curl
命令一样测试 TLS 连接,您可以尝试这样的操作:
Path clientKeyStore = Paths.get(...);
System.setProperty("javax.net.ssl.keyStore", clientKeyStore.toAbsolutePath().toString());
System.setProperty("javax.net.ssl.keyStoreType", "pkcs12");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
Path serverKeyStore = Paths.get(...);
System.setProperty("javax.net.ssl.trustStore", serverKeyStore.toAbsolutePath().toString());
System.setProperty("javax.net.ssl.trustStorePassword", "password");
System.setProperty("javax.net.ssl.trustStoreType", "jks");
SdkHttpClient client = ApacheHttpClient.builder().build();
SdkHttpRequest httpRequest = SdkHttpFullRequest.builder()
.method(SdkHttpMethod.GET)
.uri(new URI("https://<prefix>.credentials.iot.us-west-2.amazonaws.com/role-aliases/MyAlias/credentials"))
.putHeader("x-amzn-iot-thingname", "myThingName")
.build();
HttpExecuteRequest request = HttpExecuteRequest.builder()
.request(httpRequest)
.build();
HttpExecuteResponse response = client.prepareRequest(request).call();
请查看 AWS Java SDK 中的 this test,它也很有帮助。
最后,还有async HTTP clients可以用在你的项目中。在这些客户端中配置安全 HTTP 通信的方式与以上段落中描述的方式非常相似。
您可以在 AWS Java SDK v2 GitHub repository.
中找到所有这些资源
您可以在您的项目中导入整个 SDK(我假设您使用的是 Maven):
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>aws-sdk-java</artifactId>
<version>2.15.7</version>
</dependency>
虽然,为了测试 Apache HTTP 客户端,我认为以下依赖项是唯一必要的:
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>apache-client</artifactId>
<version>2.15.7</version>
</dependency>
虽然我试图将答案集中在 AWS SDK 提供的代码上,但据我所知,这是必要的,要获得这些临时凭证,也可以使用任何允许安全连接到 AWS 的机制,例如作为 Apache HttpClient,如您的示例中的 OkHttp 等。
这些临时凭证可用于签署任何 AWS 请求并根据假定的 IAM 角色在 AWS 服务上执行操作。例如,按照您指定的 blog 中的示例,您可以在 DynamoDB table:
中插入一个项目
AwsSessionCredentials credentials = AwsSessionCredentials.create(
"the_returned_access_key_id",
"the_returned_secret_key_id",
"the_returned_session_token"
);
DynamoDbClient ddb = DynamoDbClient.builder()
.region(Region.US_EAST_1)
.credentialsProvider(StaticCredentialsProvider.create(credentials))
.build();
HashMap<String,AttributeValue> itemValues = new HashMap<String,AttributeValue>();
itemValues.put("serial_number", AttributeValue.builder().s("123456789").build());
itemValues.put("timestamp", AttributeValue.builder().s("2017-11-20T06:00:00.000Z").build());
itemValues.put("current_temp", AttributeValue.builder().n("65").build());
itemValues.put("target_temp", AttributeValue.builder().n("70").build());
itemValues.put("humidity", AttributeValue.builder().n("45").build());
PutItemRequest request = PutItemRequest.builder()
.tableName("MyHomeThermostat")
.item(itemValues)
.build();
try {
ddb.putItem(request);
} catch (ResourceNotFoundException e) {
//...
} catch (DynamoDbException e) {
//...
}
关于您在上面的评论中提出的如何更新获得的令牌的问题,我必须承认我无法给您一个答案。
在我看来,恐怕无法刷新上述调用返回的临时凭证,至少 AWS SDK 没有为此提供任何机制:此凭证提供程序是一个非常具体的用例,专为您引用的博客和 official AWS documentation.
中指出的物联网
AWS SDK 提供了不同的 AWSCredentialsProvider
支持令牌续订,例如 StsAssumeRoleCredentialsProvider
or StsGetSessionTokenCredentialsProvider
,但没有针对此用例的特定提供商。
如果有帮助,您可以查看基础class StsCredentialsProvider
的源代码,特别是其构造函数中与CachedSupplier
设置相关的代码以及相关东西。
有人可以解释一下吗,我需要如何实施 blog 的第一步? 我在 AWS 文档中找不到它。
也就是说,我需要翻译一个命令:
curl --cert eeb81a0eb6-certificate.pem.crt --key eeb81a0eb6-private.pem.key -H "x-amzn-iot-thingname: myThingName" --cacert AmazonRootCA1.pem https://<prefix>.credentials.iot.us-west-2.amazonaws.com/role-aliases/MyAlias/credentials
到JAVA。我该怎么做?我需要 AWS SDK(我更喜欢没有“自定义客户端发出 HTTPS 请求”的解决方案)
更新:
我尝试使用自定义客户端发出 HTTPS 请求,但在计划将我的密钥导出到 Java KeyStore 时我卡住了(但 curl 命令对我来说很好):
$ winpty openssl pkcs12 -export -in eeb81a0eb6-certificate.pem.crt -inkey eeb81a0eb6-private.pem.key -chain -CAfile AmazonRootCA1.pem -name mycompany.com -out my.p12
Error unable to get local issuer certificate getting chain.
另一个更新(我已经尝试过的)
将 myPrivateKey 和 deviceCertificate 转换为 JKS:
winpty openssl pkcs12 -export -in eeb81a0eb6-certificate.pem.crt -inkey eeb81a0eb6-private.pem.key -name mycompany.com -out my.p12
keytool -importkeystore -destkeystore mycompany.jks -srckeystore my.p12 -srcstoretype PKCS12
在我的代码中使用这个 JKS:
System.setProperty("deployment.security.TLSv1.2", "true"); System.setProperty("https.protocols", "TLSv1.2"); System.setProperty("javax.net.debug", "ssl"); HttpPost request = new HttpPost(clientEndpoint); request.setHeader("x-amzn-iot-thingname", "0ad16050-d974-4f78-88ea-c6ee2b0a551e"); KeyStore keyStore; try (InputStream keyStoreStream = this.getClass().getResourceAsStream(KEYSTOREPATH)) { keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(keyStoreStream, KEYSTOREPASS.toCharArray()); } SSLContext sslContext = SSLContexts.custom() .loadKeyMaterial(keyStore, KEYPASS.toCharArray()) // use null as second param if you don't have a separate key password .loadTrustMaterial(null, new TrustSelfSignedStrategy()) .build(); SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext); Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() .register("https", sslConnectionSocketFactory) .register("http", new PlainConnectionSocketFactory()) .build(); BasicHttpClientConnectionManager manager = new BasicHttpClientConnectionManager(registry); try (CloseableHttpClient httpClient = HttpClients .custom() .setSSLSocketFactory(sslConnectionSocketFactory) .setConnectionManager(manager) .build(); CloseableHttpResponse response = httpClient.execute(request)) { System.out.println(); } catch (IOException e) { System.err.println(e); }
我得到异常:
javax.net.ssl.SSLHandshakeException:收到致命警报:bad_certificate
AWS SDK 提供了多个 SdkHttpClient
的实现,您可以使用它们与您的亚马逊服务进行同步或异步交互。
例如,您可以使用 ApacheHttpClient
class.
所有这些 HTTP 客户端都是为 ApacheHttpClient
创建和配置的 Builder
s, ApacheHttpClient.Builder
。
ApacheHttpClient.Builder
提供允许您为客户端、远程对等或相互身份验证配置安全 HTTP 连接的方法。
如果必须对客户端进行身份验证,则需要提供必须用于此目的的证书和私钥,对应于您的 [=26] 的 --cert
和 --key
参数=]调用。
通常,此证书和私钥存储在一个受密码保护的 KeyStore
中,通常采用 PKCS#12 格式(.p12
或 .pfx
文件)。
ApacheHttpClient.Builder
可以通过两种方式访问此信息。
首先,通过设置一系列System
属性:
import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE;
import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE_PASSWORD;
import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE_TYPE;
//...
Path clientKeyStore = Paths.get(...);
System.setProperty(SSL_KEY_STORE.property(), clientKeyStore.toAbsolutePath().toString());
System.setProperty(SSL_KEY_STORE_TYPE.property(), "pkcs12");
System.setProperty(SSL_KEY_STORE_PASSWORD.property(), "password");
注意:static
导入只是标准 JSSE 属性 javax.net.ssl.keyStore
、javax.net.ssl.keyStorePassword
和 javax.net.ssl.keyStoreType
.
其次,通过为 ApacheHttpClient.Builder
的 tlsKeyManagersProvider
方法提供 TlsKeyManagersProvider
实现。例如:
Path clientKeyStore = ...
TlsKeyManagersProvider keyManagersProvider = FileStoreTlsKeyManagersProvider.create(clientKeyStore, "pkcs12", "password");
事实上,在幕后,上面提到的 System
基于属性的配置被 SystemPropertyTlsKeyManagersProvider
另一个 TlsKeyManagersProvider
实现使用。
如果你需要验证服务器,你也有两个选择。
首先,再次设置几个 System
属性:
Path serverKeyStore = Paths.get(...);
System.setProperty("javax.net.ssl.trustStore", serverKeyStore.toAbsolutePath().toString());
System.setProperty("javax.net.ssl.trustStorePassword", "password");
System.setProperty("javax.net.ssl.trustStoreType", "jks");
如您所见,为了简单起见,这次我们使用了另一种KeyStore
、jks
。您可以从您的 AWS 服务器证书 PEM 文件(与您的 curl
命令中的 --cacert
相关联的文件)构建这样一个 KeyStore
,如下所示:
Path pemPath = ...;
try(final InputStream is = Files.newInputStream(pemPath) {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(is);
String alias = cert.getSubjectX500Principal().getName();
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
keyStore.setCertificateEntry(alias, cert);
}
在相互身份验证的情况下,虽然您可以重复使用相同的 KeyStore
,但最佳做法是维护两个,一个包含客户端私钥和证书,另一个包含您信任的服务器证书(信任库)。
或者,您也可以通过定义需要使用的 TrustManager
来配置服务器端身份验证。
对于此任务,ApacheHttpClient.Builder
提供了方法 tlsTrustManagersProvider
。此方法需要实现 TlsTrustManagersProvider 接口。
此接口定义了一个方法,trustManagers
,即 returns 必须用于检查 SSL 通信中的远程对等点的 TrustManager
数组。
很遗憾,AWS SDK 不提供此接口的实现,您需要自己实现(如果您需要更多信息,请告诉我)。
初始化和配置后,您可以使用 httpClient
或 httpClientBuilder
方法,分别。
如果您只需要像使用 curl
命令一样测试 TLS 连接,您可以尝试这样的操作:
Path clientKeyStore = Paths.get(...);
System.setProperty("javax.net.ssl.keyStore", clientKeyStore.toAbsolutePath().toString());
System.setProperty("javax.net.ssl.keyStoreType", "pkcs12");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
Path serverKeyStore = Paths.get(...);
System.setProperty("javax.net.ssl.trustStore", serverKeyStore.toAbsolutePath().toString());
System.setProperty("javax.net.ssl.trustStorePassword", "password");
System.setProperty("javax.net.ssl.trustStoreType", "jks");
SdkHttpClient client = ApacheHttpClient.builder().build();
SdkHttpRequest httpRequest = SdkHttpFullRequest.builder()
.method(SdkHttpMethod.GET)
.uri(new URI("https://<prefix>.credentials.iot.us-west-2.amazonaws.com/role-aliases/MyAlias/credentials"))
.putHeader("x-amzn-iot-thingname", "myThingName")
.build();
HttpExecuteRequest request = HttpExecuteRequest.builder()
.request(httpRequest)
.build();
HttpExecuteResponse response = client.prepareRequest(request).call();
请查看 AWS Java SDK 中的 this test,它也很有帮助。
最后,还有async HTTP clients可以用在你的项目中。在这些客户端中配置安全 HTTP 通信的方式与以上段落中描述的方式非常相似。
您可以在 AWS Java SDK v2 GitHub repository.
中找到所有这些资源您可以在您的项目中导入整个 SDK(我假设您使用的是 Maven):
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>aws-sdk-java</artifactId>
<version>2.15.7</version>
</dependency>
虽然,为了测试 Apache HTTP 客户端,我认为以下依赖项是唯一必要的:
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>apache-client</artifactId>
<version>2.15.7</version>
</dependency>
虽然我试图将答案集中在 AWS SDK 提供的代码上,但据我所知,这是必要的,要获得这些临时凭证,也可以使用任何允许安全连接到 AWS 的机制,例如作为 Apache HttpClient,如您的示例中的 OkHttp 等。
这些临时凭证可用于签署任何 AWS 请求并根据假定的 IAM 角色在 AWS 服务上执行操作。例如,按照您指定的 blog 中的示例,您可以在 DynamoDB table:
中插入一个项目AwsSessionCredentials credentials = AwsSessionCredentials.create(
"the_returned_access_key_id",
"the_returned_secret_key_id",
"the_returned_session_token"
);
DynamoDbClient ddb = DynamoDbClient.builder()
.region(Region.US_EAST_1)
.credentialsProvider(StaticCredentialsProvider.create(credentials))
.build();
HashMap<String,AttributeValue> itemValues = new HashMap<String,AttributeValue>();
itemValues.put("serial_number", AttributeValue.builder().s("123456789").build());
itemValues.put("timestamp", AttributeValue.builder().s("2017-11-20T06:00:00.000Z").build());
itemValues.put("current_temp", AttributeValue.builder().n("65").build());
itemValues.put("target_temp", AttributeValue.builder().n("70").build());
itemValues.put("humidity", AttributeValue.builder().n("45").build());
PutItemRequest request = PutItemRequest.builder()
.tableName("MyHomeThermostat")
.item(itemValues)
.build();
try {
ddb.putItem(request);
} catch (ResourceNotFoundException e) {
//...
} catch (DynamoDbException e) {
//...
}
关于您在上面的评论中提出的如何更新获得的令牌的问题,我必须承认我无法给您一个答案。
在我看来,恐怕无法刷新上述调用返回的临时凭证,至少 AWS SDK 没有为此提供任何机制:此凭证提供程序是一个非常具体的用例,专为您引用的博客和 official AWS documentation.
中指出的物联网AWS SDK 提供了不同的 AWSCredentialsProvider
支持令牌续订,例如 StsAssumeRoleCredentialsProvider
or StsGetSessionTokenCredentialsProvider
,但没有针对此用例的特定提供商。
如果有帮助,您可以查看基础class StsCredentialsProvider
的源代码,特别是其构造函数中与CachedSupplier
设置相关的代码以及相关东西。