Получите токен безопасности от AWS Credentials Provider
Может кто-нибудь объяснить мне, как мне реализовать первый шаг из этого блога? Я не могу найти его в документации 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
1 ответ
AWS SDK предоставляет несколько реализаций SdkHttpClient
которые вы можете использовать для синхронного или асинхронного взаимодействия со своими сервисами Amazon.
Например, вы можете использовать ApacheHttpClient
класс.
Все эти HTTP-клиенты создаются и настраиваются с помощью Builder
с, ApacheHttpClient.Builder
за
ApacheHttpClient
.
ApacheHttpClient.Builder
provides methods that allows you to configure secure HTTP connections for client side, remote peer, or mutual authentication.
If the client must be authenticated, it is necessary to provide the certificate and private key that must be used for that purpose, corresponding to the
--cert
and
--key
arguments of your
curl
invocation.
Typically, this certificate and private key are stored in one password protected
KeyStore
, usually in PKCS#12 format (a
.p12
or
.pfx
file).
This information can be made accessible to
ApacheHttpClient.Builder
in two ways.
First, by setting a series of
System
properties:
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");
NOTE: The
static
imports are only constants for the standard JSSE properties
javax.net.ssl.keyStore
,
javax.net.ssl.keyStorePassword
, and
javax.net.ssl.keyStoreType
.
Second, by providing a TlsKeyManagersProvider
implementation to the
tlsKeyManagersProvider
method of
ApacheHttpClient.Builder
. For instance:
Path clientKeyStore = ...
TlsKeyManagersProvider keyManagersProvider = FileStoreTlsKeyManagersProvider.create(clientKeyStore, "pkcs12", "password");
In fact, under the hood, the above mentioned
System
properties based configuration is used by SystemPropertyTlsKeyManagersProvider
, another
TlsKeyManagersProvider
implementation.
If you need to authenticate the server, you also have two options.
First, again, by setting several
System
properties:
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");
As you can see, for simplicity, this time we are using a different kind of
KeyStore
,
jks
. You can build such a
KeyStore
from your AWS server certificate PEM file (the one associated with the
--cacert
in your
curl
command) with something like this:
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);
}
In the case of mutual authentication, although you can reuse the same
KeyStore
, it is a best practice maintain two, one with the client private key and certificate, and other with the server certificates you will trust (the trust store).
Alternatively, you can also configure server side authentication by defining the TrustManager
s that need to be used.
Для этой задачи
ApacheHttpClient.Builder
предоставляет метод
tlsTrustManagersProvider
. Для этого метода требуется реализация интерфейса TlsTrustManagersProvider.
Этот интерфейс определяет единственный метод,
trustManagers
, который возвращает массив
TrustManager
s, которые необходимо использовать для проверки удаленного однорангового узла в связи SSL.
К сожалению, AWS SDK не предоставляет реализацию этого интерфейса, вам необходимо реализовать свой собственный (дайте мне знать, если вам нужна дополнительная информация).
После инициализации и настройки вы можете предоставить это
SdkHttpClient
или его
SdkHttpClient.Builder
, клиенту настраиваемой службы, например IotClient
, используя
httpClient
или
httpClientBuilder
методы соответственно.
Если вам просто нужно протестировать подключение TLS, как с вашим
curl
команду, вы можете попробовать что-то вроде этого:
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, он также может быть полезен.
Наконец, есть также асинхронные HTTP-клиенты, которые вы можете использовать в своем проекте. Способ настройки защищенной связи HTTP в этих клиентах очень похож на тот, который описан в предыдущих абзацах.
Все эти ресурсы можно найти в репозитории AWS Java SDK v2 GitHub.
Вы можете импортировать в свой проект весь SDK (я предполагаю, что вы используете Maven):
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>aws-sdk-java</artifactId>
<version>2.15.7</version>
</dependency>
Хотя для тестирования HTTP-клиента Apache я думаю, что следующая зависимость будет единственной необходимой:
<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. Например, следуя примеру в указанном вами блоге, вы можете вставить элемент в таблицу DynamoDB:
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 не предоставляет для этого никакого механизма: этот поставщик учетных данных является очень специфическим вариантом использования, разработанным для IoT, как указано в блог, который вы цитировали, и в официальной документации AWS.
AWS SDK предоставляет различные
AWSCredentialsProvider
s, который поддерживает обновление токена, например StsAssumeRoleCredentialsProvider
или же StsGetSessionTokenCredentialsProvider
, среди прочего, но для этого варианта использования нет конкретного поставщика.
Если это поможет, вы можете просмотреть исходный код базового класса. StsCredentialsProvider
, особенно код в его конструкторе, связанный с настройкой
CachedSupplier
и тому подобное.