Author Archives: shafiulcse

Install Let’sEncrypt ssl certificate in nginx hosted on Amazon Linux

If you need ssl certificate for only single EC2 instance, you need to use ELB to use AWS issued free certificate manager, which incurs ~$20 monthly cost. An alternative is to install free certificate by Let’sEncrypt on the nginx hosted on EC2 instance.

Here are the steps for that:

  1. Get letsencrypt

    git clone https://github.com/letsencrypt/letsencrypt && cd letsencrypt/

  2. Create a config file  for certificate request, so you can reuse that


    # File: /etc/letsencrypt/config.ini
    # the domain we want to get the cert for
    domains = yourdomain.com

    # key size
    rsa-key-size = 4096

    # this address will receive renewal reminders, IIRC
    email = email@yourdomain.com

    # turn off the ncurses UI, we want this to be run as a cronjob
    text = True

    # authenticate by placing a file in the webroot (under .well-known/acme-challenge/) and then letting
    # LE fetch it
    authenticator = webroot
    webroot-path = /var/www/html

  3. Now create the certificate by requesting to letsencrypt, it will validate your domain by placing some file in your webroot-path, that has been mentioned in config file

sudo ./letsencrypt-auto --config /etc/letsencrypt/config.ini certonly -d yourdomain.com"

4 files will be created in location: /etc/letsencrypt/live/yourdomain.com/

  • cert.pem: Your domain’s certificate
  • chain.pem: The Let’s Encrypt chain certificate
  • fullchain.pem: cert.pem and chain.pem combined
  • privkey.pem: Your certificate’s private key

You shall need the last two file for your nginx configuration.

4. Now change your nginx config file

# File: /etc/nginx/nginx.conf

# Settings for a TLS enabled server.

  server {

        listen *:443 ssl;

        server_name yourdomain.com;

        root         /var/www/html;

        ssl on;

        ssl_certificate  "/etc/letsencrypt/live/yourdomain.com/fullchain.pem";

        ssl_certificate_key "/etc/letsencrypt/live/yourdomain.com/privkey.pem";

      # It is *strongly* recommended to generate unique DH parameters

      # Generate them with: openssl dhparam -out /etc/letsencrypt/dhparams.pem 2048

      ssl_dhparam "/etc/letsencrypt/dhparams.pem";

      ssl_session_cache shared:SSL:1m;

      ssl_session_timeout  10m;

      ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

      ssl_ciphers HIGH:SEED:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!RSAPSK:!aDH:!aECDH:!EDH-DSS-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!SRP;

      ssl_prefer_server_ciphers on;

      # Load configuration files for the default server block.

      include /etc/nginx/default.d/*.conf;

      location / {

        root /var/www/html/yourdomain;

      }

      error_page 404 /404.html;

          location = /40x.html {

      }

      error_page 500 502 503 504 /50x.html;

          location = /50x.html {

      }

  }

And don't forget to forward http request to https:

server {
listen 80 default_server;
listen [::]:80 default_server;
rewrite ^ https://$server_name$request_uri? permanent;

5. Reload nginx

sudo service nginx reload

now you should be ok with https://yourdomain.com

Adding custom metadata on AWS S3 pre-signed url generation

While uploading file to S3, we sometime need to store some metadata associated with a file, such as Content-Type, custom metadata etc. Content-Type  metadata can be easily added as header while uploading to S3, but custom metadata needs a bit more work.

Here is the file upload flow for a mobile app:

S3Upload (1)

Figure: File Upload flow from mobile app to S3

After uploading file, we have a lambda which scales the image to a specific dimension determined by a custom metadata: ‘imageType’ (business type, not content-type) associated with file.

I have not found any useful documentation on how to put the metadata programmatically while signing the url request. After trying out, I found the following solution.

While asking for pre-signed url from app, app sends the ‘imageType’ (profile/other etc) input. Backend takes that field and sends as user-metadata to S3 for signing in the following way using field: “x-amz-meta-image-type”

private URL getSignedUri(final String objectKey, final HttpMethod httpMethod, final Map<String, String> customHeaders) {
    Instant expirationTime = Instant.now().plus(LINK_EXPIRATION_TIME, ChronoUnit.MILLIS); //1 hour.

    GeneratePresignedUrlRequest generatePresignedUrlRequest =
            new GeneratePresignedUrlRequest(s3StorageConfiguration.getBucketName(), objectKey)
                .withExpiration(Date.from(expirationTime))
                .withMethod(httpMethod);

    customHeaders.forEach((key, value) -> generatePresignedUrlRequest.putCustomRequestHeader(key, value));

    return amazonS3.generatePresignedUrl(generatePresignedUrlRequest);
}

The generated url will have something like: X-Amz-SignedHeaders=host%3Bx-amz-meta-image-type

Now from app, the file can be uploaded with setting header: x-amz-meta-image-type=profile or something

If the header x-amz-meta-* was not signed before, the upload with the header will throw the following exception:


There were headers present in the request which were not signed
x-amz-meta-image-type
...

Finally one caveat, in the lambda code, we need to get the meatadata without the x-amz-meta- part,

objectMetadata.getUserMetaDataOf("image-type"); //not using x-amz-meta-image-type

Simplest possible Java State Machine

Simplest possible Java State Machine

This simple state machine consists of few dead simple contracts for defining constraints and actions along with single engine file of ~80 lines.

This code base also includes few example file to show how to implement the contracts.

Transition Rule

Transition rules are defined in the state enum which implements a simple interface. Look at the example code to see how to define the rules.

Every state in the state enum defines from which states this state can be reached and what are the preconditions(a list) to statisfy for the transition and it allows to define a set of actions while doing the transition after satisfying all the preconditions.

Below is an example to define rule

public enum BlogState implements State {

DRAFT {
@Override
public Map<State, ConstraintActionPair> getTransitions() {
return null;
}
},
DELETED {
@Override
public Map<State, ConstraintActionPair> getTransitions() {
Set stateActions = new HashSet<>();
stateActions.add(new PublishAction());

List stateConstraints = new ArrayList<>();
stateConstraints.add(new RoleConstraint());

ConstraintActionPair constraintActionPair =
new ConstraintActionPair<>(stateConstraints, stateActions);

Map<State, ConstraintActionPair> constraintActionPairMap = new HashMap<>();
//here the key is the state from which this state can be reached and
//the value is a pair of constraints and actions for that state
constraintActionPairMap.put(BlogState.DRAFT, constraintActionPair);

return constraintActionPairMap;
}
}
}

It indicates that state DELETED can only be reached from DRAFT, satisfying RoleConstraint and if satisfied, it executes Publish Action (just for example).

Transition Constraints

Constrains are classes implementing below simple contract. Constraints have access to old value, new value, old state, new state; making it easy to validate the business logic.

public interface StateConstraint {
boolean validate(T oldValue, T newValue, State fromState, State toState);

ConstraintViolationException getViolationException(T oldValue, T newValue, State fromState, State toState);
}

Transition Actions

Actions are class implementing below simple contract. The only method required to implement is execute(), which have access to old value, new value, old state, new state.

public interface StateAction {

int DEFAULT_ORDER = 100;

T execute(T oldValue, T newValue, State fromState, State toState) throws ActionFailedException;

/**
* Determines if this action will halt the transition.
* If false, other actions will continue to execute.
* @return
*/
default boolean isBlocking() {
return true;
}

/**
* Low means higher priority.
* @return
*/
default int getOrder() {
return DEFAULT_ORDER;
}

String toString();
}

Action Order

Actions are executed in order. You can override getOrder() method to set order of action. Lower the order number means higher prioriy. But you are not required to override this method, by default all actions have same priority(100).

Blocking & Non-blocking

Some actions may be blocking i.e if exception is thrown, no other actions is executed. In case of non-blocking actions, if it thows exception, next actions are executed sequentially. You can define a task blocking/non-blocking by simply overriding isBlocking() method in the action class. Be default all actions are blocking.

Spring data mongo generates wrong query for geo query with $near

While working with Spring data mongo,  I had to search a collection using geo spatial query. I needed to find all items from a point within a radius.

Method in repository interface looks like:

Slice findByLocationNear(Point source, Distance distance, Pageable pageable);

Generated query filter is:

{ location: { $near: { x: 56.6474, y: 28.63752 }, $maxDistance: 0.01799856011519078 } }

But mongo expect longitude as the first property in $near query value, without considering property name(!).

And the query does not get expected result.

To solve the problem, right now I am using a way around. While constructing Point object, setting longitude first. Then the query returns expected result.

Point source = new Point(lng, lat);
repository.findByLocationNear(source, distance, pageable);

In-place Reversing a LinkedList effificiently

A LinkedList can be reversed in different ways. But reversing in-place, i.e changing the direction is the most efficient way in my opinion. Because it does not take extra space to store the nodes.

Below the code how to achieve it:

class Node {
  Node next = null;
  int data;
  
  public Node (int data) {
    this.data = data;
  }
  
  public void addToTail(int data) {
    Node newNode = new Node(data);
    
    Node n = this;
    
    while (n.next != null) {
      n = n.next;
    }
    n.next = newNode;
  }
  
  public String toString() {
    return data + "";
  }
}

public class LinkedList {
    /**
    * Print the linked list
    */
    private static void printLL(Node n) {
        while (n != null) {
            System.out.print(n.data);
            if (n.next != null)
                System.out.print(" -> ");

            n = n.next;
        }
    }

     /**
     * Reverse the linked list
     * @param head
     * @return new head
     */ 
    public static Node reverse(Node head) {
        Node prev = null;
        Node current = head;

        while (current != null) {
            Node temp = current.next;
            current.next = prev;

            prev = current;
            current = temp;
        }

        return prev;
    }

    public static void main(String[] s) {
        Node ll = new Node(3);
        ll.next = new Node(4);
        ll.next.next = new Node(5);
        ll.next.next.next = new Node(3);
        ll.next.next.next.next = new Node(1);
        Node reverseList = reverse(ll);
        printLL(reverseList);
    }
}

Output will be:

1 -> 3 -> 5 -> 4 -> 3

Google Map mousemove event cancels click event for polygon

While working with google map, there is a requirement to show info window while hovering on polygon and enable click event to select the polygon.

I added two event listener for info window show and close: mousemove and mouseout and a click event to select the polygon. But after some weeks of the initial development, click event stopped working.

After some research, identified that mousemove event cancels click event. So what is the solution? ‘mouseover’. Used mouseover event to show info window and it started working again.

 //Show info of hovered polygon
 google.maps.event.addListener(polygon, 'mouseover', function (event) {
    marker.setPosition(event.latLng);
    marker.setVisible(true);
    $scope.hoverRegion(this.id);
});
//Hide info of hovered polygon
google.maps.event.addListener(polygon, 'mouseout', function () {
    marker.setVisible(false);
    $scope.unhoverRegion(this.id);
});

//Select/De-select region
polygon.addListener('click', function () {
    $scope.togglePolygonSelection(this);
});