Получите токен безопасности от 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.

ДРУГОЕ ОБНОВЛЕНИЕ (ЧТО Я УЖЕ ПЫТАЛ)

  1. Преобразуйте 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

  2. Используйте этот 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);
     }
    
  3. Я получаю исключение:

    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 TrustManagers that need to be used.

Для этой задачи ApacheHttpClient.Builder предоставляет метод tlsTrustManagersProvider. Для этого метода требуется реализация интерфейса TlsTrustManagersProvider.

Этот интерфейс определяет единственный метод, trustManagers, который возвращает массив TrustManagers, которые необходимо использовать для проверки удаленного однорангового узла в связи 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 предоставляет различные AWSCredentialsProviders, который поддерживает обновление токена, например StsAssumeRoleCredentialsProvider или же StsGetSessionTokenCredentialsProvider, среди прочего, но для этого варианта использования нет конкретного поставщика.

Если это поможет, вы можете просмотреть исходный код базового класса. StsCredentialsProvider, особенно код в его конструкторе, связанный с настройкой CachedSupplier и тому подобное.

Другие вопросы по тегам