AWS - Connecting from java applications

Reading material

  1. https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/migration-client-credentials.html

In deployed environments

  1. They will assign roles to the application.

  2. Create a custom role for the application.

  3. Create one or more custom policies for the application. If the application needs access to s3, sqs, elasticsearch, opensearch, and other services from aws, we will have to create the policies accordingly. e.g. develop-application-name-s3-policy

    {
       "Version":"2012-10-17",
       "Statement":[
          {
             "Effect":"Allow",
             "Action": "s3:ListAllMyBuckets",
             "Resource":"*"
          },
          {
             "Effect":"Allow",
             "Action":["s3:ListBucket","s3:GetBucketLocation"],
             "Resource":"arn:aws:s3:::amzn-s3-demo-bucket1"
          },
          {
             "Effect":"Allow",
             "Action":[
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:GetObject",
                "s3:GetObjectAcl",
                "s3:DeleteObject"
             ],
             "Resource":"arn:aws:s3:::amzn-s3-demo-bucket1/*"
          }
       ]
    }
    

    or

    {
       "Version":"2012-10-17",
       "Statement":[
          {
             "Action": "s3:*",
             "Effect":"Allow",
             "Resource":"*"
          }
       ]
    }
    
  4. Add all the policies necessary for the application to that role. Permission Policies: develop-application-name-s3-policy (and whatever other policies the application needs).

  5. ProfileCredentialsProvider might work great on your computer but won’t work anywhere that you’re getting AWS access through an IAM role (ex. Lambda, Docker, EC2 instance, etc).

  6. For applications deployed in AWS environments,

    1. the applications use some kind of credentials provider.
    2. On newer versions of the SDK, you’ll need to use following code
    3. This code will correctly pick up the active IAM role.
    // To be used in deployed environments
    @Bean
    @Profile("!default")
    public AmazonS3 amazonS3() {
            return AmazonS3ClientBuilder.standard()
                .withCredentials(DefaultAWSCredentialsProviderChain.getInstance())
                .build();
    }
    

For local development

We cannot use IAM roles for application development in local computers. We have to IAM users.

  1. Create a custom user
  2. Create one or more custom policies for the user (similar to the way it is done for roles in the deployed environments). If the application needs access to s3, sqs, elasticsearch, opensearch, and other services from aws, we will have to create the policies accordingly.

How to set up AWS access credentials in your local?

https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html

If you want to access AWS resources from your local machine using AWS SDK, you can’t do it by default.

You need to configure AWS access key id and AWS secret access key in your local.

You can do this using aws cli

Here is how to do it:

Run aws configure command in command prompt.

Open a command prompt and run aws configure command:

$ aws configure
AWS Access Key ID [None]: your-access-id
AWS Secret Access Key [None]: your-access-key
Default region name [None]: us-east-1
Default output format [None]:

Enter the AWS access key id and AWS secret access key when you are prompted for.

Also specify the default region you chose when you created an account in AWS.

That’s it!

Now two files get created in your home folder:

 ╭─explorer436@explorer436 in ~ on  main [!?] on  (us-east-1) took 1m9s
╰─λ cd ~/.aws

╭─explorer436@explorer436 in ~/.aws on  main [!?] on  (us-east-1)
╰─λ ls
.rw-------  29 explorer436 29 Jan 13:34  config
.rw------- 116 explorer436 29 Jan 13:34  credentials

You are all set now to access AWS resources from your local app!

Using AWSStaticCredentialsProvider

Avoid this if you can. Hard coding credentials in the application is a bad idea.

If you haven’t configured aws-cli in your local computer yet, and if you want to insert the aws access key and aws secret key in the application.properties file, the application can connect to AWS using this approach.

For this, the application.properties file has to look something like this:

bucket:
  name: your-bucket-name
  uploadExpirationTime: 3600000
  downloadExpirationTime: 3600000

# https://cloud.spring.io/spring-cloud-static/spring-cloud-aws/2.2.0.M2/reference/html/
# https://docs.awspring.io/spring-cloud-aws/docs/current/reference/html/appendix.html
# AWS properties
cloud:
  aws:
    region:
      static: us-east-1
    stack:
      auto: false
    credentials:
      accessKey: your-access-key
      secretKey: your-secret-key

And then, using these in the configuration files in the application.

Sample config file:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;

@Configuration
public class AmazonS3ClientConfig {

        @Value("${cloud.aws.credentials.accessKey}")
        private String awsId;

        @Value("${cloud.aws.credentials.secretKey}")
        private String awsKey;

        @Value("${cloud.aws.region.static}")
        private String region;

        @Bean
        public AmazonS3 amazonS3Client() {

                BasicAWSCredentials awsCredentials = new BasicAWSCredentials(awsId, awsKey);
                AmazonS3 amazonS3Client = AmazonS3ClientBuilder.standard()
                                .withRegion(Regions.fromName(region))
                                .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)).build();

                return amazonS3Client;
        }

}

Needless to say, this is not preferrable because it increases the risk of exposing your keys.

Reference:

https://github.com/explorer436/programming-playground/blob/main/java-playground/spring-cloud-examples/spring-cloud-aws-s3/src/main/java/com/example/awss3/config/AmazonS3ClientConfig.java

ProfileCredentialsProvider

When the application uses ProfileCredentialsProvider, it looks in ~/.aws/credentials for a list of profiles. Using aws-cli (or manually), configure the user’s accessKey and secretKey for the default profile.

ProfileCredentialsProvider will read the two files ~/.aws/config and ~/.aws/credentials and use the parameters from these files to connect to aws.

The default credential profiles file - typically located at ~/.aws/credentials (location can vary per platform), is shared by many of the AWS SDKs and by the AWS CLI. The AWS SDK for Java uses the ProfileCredentialsProvider to load these credentials.

NOTE:

If you are using ProfileCredentialsProvider (see notes above), you don’t have to put credentials in property files. AWS SDK will read the credentials from environment files.

default profile

Configure aws-cli in your local computer and use the credentials from ~/.aws to connect to your AWS account from your applications.

If you have your credentials configured using aws-cli, you can use this config:

For this to work, the file /home/explorer436/.aws/credentials should have

[default]
aws_access_key_id = <my_aws_access_key_id>
aws_secret_access_key = <my_access_key>

The config class in java application should look like this:

import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AmazonS3ClientConfig {

        @Bean
        public AmazonS3 amazonS3Client() {

                AmazonS3 amazonS3Client = AmazonS3ClientBuilder
                                .standard()
                                .withCredentials(new ProfileCredentialsProvider("default"))
                                .build();


                return amazonS3Client;
        }

}

saml profile

When working in a corporate environment, we may have to use a combination of saml profile and proxy credentials as opposed to default profile. The details vary by clients.

import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AmazonS3ClientConfig {

        @Bean
        @Profile("default")
        public AmazonS3 amazonS3() {
               amazonS3 = AmazonS3ClientBuilder
                          .standard()
                          .withClientCredentials(new ProfileCredentialsProvider("saml"))
                          .withRegion(Regions.fromName(awsRegion))
                          .withClientConfiguration(
                               PredefinedClientConfigurations.defaultConfig()
                                   .withProxyHost(proxyHost)
                                   .withProxyPort(Integer.parseInt(proxyPort))
                                   .withProxyUsername(proxyUserName)
                                   .withProxyPassword(proxyPassword)
                           )
                          .build();

                return amazonS3Client;
        }

}

Using EndpointConfiguration

This is an example using Localstack:

https://github.com/explorer436/programming-playground/blob/main/java-playground/spring-cloud-examples/spring-cloud-localstack-s3/src/main/java/com/example/springcloudlocalstacks3/config/AmazonS3ClientConfig.java


Links to this note