Friday, April 28, 2017

Extracting active logged-in users and active authentication tickets from Alfresco


Sometimes an Alfresco Administrator might need to track active logged-in users in the system for audit purpose or for planning maintenance activities. There can be many other use-cases depending of type of usage or organization policies.

Alfresco doesn't provide this kind of feature for admins out of the box as of now. However Alfresco 5.2 version on-wards they are providing Support Tools feature which can provide various options including this particular use-case.

Support Tools is an Add-On, which is available on GitHub as alfresco-support-tools which can be installed as a module as well. Alfresco 5.2 (enterprise) on-wards alfresco has integrated this add-on out of the box.

See here: Support Tools

Here we are going to use Alfresco's TicketComponent service to get the active user details and active ticket details.
We will create a java backed web-script which will return the basic details about the active users, total active user count, total no. of active tickets etc.

Follow the below given steps:

  • Create following folder structure under "/alfresco/extension/templates/webscripts" directory
    • Create a folder named "com" under "webscripts"
    • Under folder "com", create a folder named "github"
    • Under folder "github", create a folder named "abhinavmishra14"
    • Under folder "abhinavmishra14", create a folder named "audit"
<webscript>
	<shortname>Active Users</shortname>
	<description>This webscript returns the active users logged-in into
		Alfresco.
		Sample response:
		{
		activeTicketsCount: 2,
		activeUsers: "["admin","abhinav@gmail.com "]",
		activeUsersCount: 2,
		_comment: "Active user count may be lower than the ticket count, since a user
		can have more than one ticket/session. Ticket count may be higher than
		the active user count, since a user can have more than one
		ticket/session."
		}
	</description>
	<url>/audit/getActiveUsers</url>
	<format default="json" />
	<authentication>admin</authentication>
	<family>Audit</family>
</webscript>
 
  • Create a freemarker template “getActiveUsers.get.json.ftl” which is used to generate the view under "com > github > abhinavmishra14 > audit" folder.
<#escape x as jsonUtils.encodeJSONString(x)> ${response} </#escape>

  • Create a java package "com.github.abhinavmishra14.audit.webscript"
  • Create java class "GetActiveUsersWebscript" under the "com.github.abhinavmishra14.audit.webscript" package.
/*
 * Created By: Abhinav Kumar Mishra
 * Copyright &copy; 2017. Abhinav Kumar Mishra. 
 * All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.abhinavmishra14.audit.webscript;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.security.authentication.TicketComponent;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.extensions.webscripts.Cache;
import org.springframework.extensions.webscripts.DeclarativeWebScript;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * The Class GetActiveUsersWebscript.
 */
public class GetActiveUsersWebscript extends DeclarativeWebScript {

	/** The Constant LOGGER. */
	private static final Logger LOGGER = LoggerFactory.getLogger(GetActiveUsersWebscript.class);

	/** The Constant ACTIVE_USERS. */
	private static final String ACTIVE_USERS = "activeUsers";

	/** The Constant ACTIVE_USERS_COUNT. */
	private static final String ACTIVE_USERS_COUNT = "activeUsersCount";

	/** The Constant ACTIVE_TICKETS_COUNT. */
	private static final String ACTIVE_TICKETS_COUNT = "activeTicketsCount";

	/** The Constant COMMENT_DATA. */
	private static final String COMMENT_DATA = "_comment";

	/** The Constant RESPONSE. */
	private static final String RESPONSE = "response";

	/** The ticket component. */
	private final TicketComponent ticketComponent;

	/**
	 * The Constructor.
	 *
	 * @param ticketComponent the ticket component
	 */
	public GetActiveUsersWebscript(final TicketComponent ticketComponent) {
		super();
		this.ticketComponent = ticketComponent;
	}

	/* (non-Javadoc)
	 * @see org.springframework.extensions.webscripts.DeclarativeWebScript#executeImpl(org.springframework.extensions.webscripts.WebScriptRequest, org.springframework.extensions.webscripts.Status, org.springframework.extensions.webscripts.Cache)
	 */
	@Override
	public Map<String, Object> executeImpl(final WebScriptRequest req,
			final Status status, final Cache cache) {
		if(LOGGER.isDebugEnabled()) {
			LOGGER.debug("Extracting active users..");
		}
		final Map<String, Object> model = new ConcurrentHashMap<String, Object>(3);
		try {
			//get nonExpiredOnly users with tickets
			final Set<String> activeUsers = ticketComponent.getUsersWithTickets(true);
			final ObjectMapper objMapper = new ObjectMapper();
			objMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);

			if (activeUsers != null && !activeUsers.isEmpty()) {
				final JSONObject activeUsersJson = new JSONObject();
				//This may be lower than the ticket count, since a user can have more than one
				// ticket/session
				activeUsersJson.put(ACTIVE_USERS, objMapper.writeValueAsString(activeUsers));
				activeUsersJson.put(ACTIVE_USERS_COUNT, activeUsers.size());

				//This may be higher than the user count, since a user can have more than one   
				// ticket/session
				//get nonExpiredOnly ticket count
				activeUsersJson.put(ACTIVE_TICKETS_COUNT, ticketComponent.countTickets(true));

				activeUsersJson.put(COMMENT_DATA, "Active user count may be lower than the ticket count, since a user can have more than one ticket/session. Ticket count may be higher than the active user count, since a user can have more than one ticket/session.");
				model.put(RESPONSE, activeUsersJson);
			}
		} catch (JsonProcessingException | JSONException excp) {
			LOGGER.error("Exception occurred while preparing json for active users ", excp);
			throw new WebScriptException(
					Status.STATUS_INTERNAL_SERVER_ERROR, excp.getMessage(), excp);
		} catch (AlfrescoRuntimeException alfErr) {
			LOGGER.error("Unexpected error occurred while getting active users ", alfErr);
			throw new WebScriptException(
					Status.STATUS_INTERNAL_SERVER_ERROR, alfErr.getMessage(), alfErr);
		}
		if(LOGGER.isDebugEnabled()) {
			LOGGER.debug("Extracted active users.");
		}
		return model;
	}
}

<bean id="webscript.com.github.abhinavmishra14.audit.getActiveUsers.get"
	  class="com.github.abhinavmishra14.audit.webscript.GetActiveUsersWebscript"
	  parent="webscript">
  <constructor-arg ref="ticketComponent" />
</bean>

  • Build and start the alfresco instance.
  • It will return following type of response (example):
{
  "activeTicketsCount": 3,
  "activeUsersCount": 3,
  "_comment": "Active user count may be lower than the ticket count, since a user can have more than one ticket/session. Ticket count may be higher than the active user count, since a user can have more than one ticket/session.",
  "activeUsers": "[\"test2\",\"admin\",\"guest\"]"
}



Refer this github project in case you need to refer to the codehttps://github.com/abhinavmishra14/active-users-report

Alternatively, you can apply "active-users-report-1.0-SNAPSHOT.amp" to your alfresco installation as an add-on.



Tuesday, April 18, 2017

S3 Static Website Hosting and Route 53 DNS Failover to redirect to maintenance page


Assuming that you have a set of EC2 instances running behind a load balancer. You want to setup maintenance page whenever there is deployment or maintenance is planned for the instances.


So, let’s begin setting up maintenance page. We will be setting up everything from scratch for demonstration purpose. 

We will do following activities:
  1. Setup S3 Bucket and host static maintenance page.
  2. Launch 2 EC2 instances and setup a small web app on both instances.
  3. Launch and ELB and attach the EC2 instances with it. 
  4. Configure Route 53 DNS failover to redirect to maintenance page when instances are unhealthy/down.

Setup S3 Bucket and host static maintenance page:

Follow the below given steps:
v  Create a S3 bucket e.g. ‘web.abhinav.com’.
To create an S3 follow the steps below:
a-      Sign in to the AWS Management Console and open the Amazon S3 console at https://console.aws.amazon.com/s3

b-      Select ‘Create Bucket’ from the menu.
c-      On the create bucket form provide a name for your bucket (e.g. web.abhinav.com) and choose the AWS Region (e.g. US West) where you want the bucket to reside.
d-      Click ‘Next’ to go to next section. It will take you to set properties page.
e-      You will see options such as Versioning, Logging and Tags etc. At this point we don’t have to set these properties, so click ‘Next’ to go to next page.
f-       You will land to next page ‘Permissions’. Just leave the default permissions as is and click ‘Next’.
g-      You will land to a review page to review the setup you did in previous steps. If everything looks good then Click ‘Create Bucket’.


v  Once the bucket is created, it will appear in the list of buckets.
v  Click on the bucket and you will see Objects, Properties, Permissions and Management tabs.










v  Create a static html page called ‘maintenance.html’ and keep maintenance message in it.

<html>
  <head><title>App Under Maintenance</title></head>
  <body>
    <h2>This site is under maintenance, it will be available shortly.</h2>
  </body>
</html>

v  Upload this html page into the newly created bucket ‘web.abhinav.com’
v  Now, go to ‘Properties’ tab. You will see options such as Versioning, Logging, Static website hosting etc. Here we are interested in ‘Static website hosting’.
v  Click on ‘Static website hosting’, you will see a form asking for index document, error page, redirection rules etc. Here we will just provide our maintenance page file name, note the end-point address and click ‘Save’.























v  Now, you need to allow the page to be accessed over internet. Go to ‘Permissions’ tab which is next to ‘Properties’
v  You will see Access Controlled List, Bucket Policy and CROS Configuration. We need to allow the file to be accessed publicly. In order to do that we need to setup bucket policy.
v  Click on ‘Bucket Policy’, you will see bucket policy editor.
v  Copy and paste below given JSON configuration. You can also generate the policy using a GUI provided by Amazon. 




{
  "Version": "2012-10-17",
 "Statement": [
     {
       "Sid": "PublicReadForGetBucketObjects",
       "Effect": "Allow",
       "Principal": "*",
       "Action": "s3:GetObject",
        "Resource": "arn:aws:s3:::web.abhinav.com/*"
      }
   ]
}

v  Click ‘Save’ to save the bucket policy. Now you are all set.
v  To test whether you are able to access the maintenance page uses the endpoint which we noted down while setting static website hosting configuration. In this case the end-point was: 


If you forgot to note down the end point then you can simply do this:
http://<your-bucket-name>.s3-website-<region>.amazonaws.com (Note the highlighted areas)

v  Let’s open the maintenance page. Access the following url:



You will land to your maintenance page.  
Note: The bucket name should exactly match the domain name of the primary instance (e.g. ELB which is in front of web app instances will have a domain name, in route53 configuration we will be configuring primary and secondary domains. Static S3 site will be treated as secondary in case of failover, that’s why bucket should match the domain name)

For more details refer: 


Launch 2 EC2 instances and setup a small web app on both instances:

Follow the below given steps to launch instance and setup a web page:
v  Logon to AWS Console and go to EC2 dashboard.
v  On the dashboard you will see ‘Resources’ and ‘Create Instance’ sections. Resources section shows how many instances are running already, how many volumes are available, how many snapshots are available etc.
v  I already have a set of instances running so I am not going to create them here, but let me document the high level steps here.
a-      Click on ‘Launch Instance’ button under ‘Create Instance’ section.
b-      It will take you to a page where you will be asked to select an AMI (Amazon Machine Image). These are images with pre-installed software. You may have your custom AMI so you can launch the instance using that as well (can be found under ‘My AMIs’ section). Deepening on your account access (e.g. free tier) choice you can select Linux or Windows version of AMI
c-      Select an AMI e.g. "Amazon Linux AMI 2017.03.0 (HVM), SSD Volume Type - ami-8ca83fec".
d-      It will ask to select the ‘Instance Type’. Select the instance type based on your need.

Refer for details on instance types: https://aws.amazon.com/ec2/instance-types/

Note: If you have free account then make sure you select the Instance type applicable for free tier. Or else you will end up being charged. Notice the label as given below.



a-      You can click ‘Review and Launch’ button if you want the default settings provided by Amazon else click on ‘Next: Configure Instance Details’
b-      It is step 3, in this step you will be asked to ‘Configure Instance Details’ such as Number of Instances, Purchasing options, select Network (VPC), etc. Depending on need change the requested configuration or leave it as default (if you have launched your instance using custom AMI then these steps are not required to setup again, AMI will auto configure based on previous setup).  
c-      Click ‘Next: Add Storage’ to add storage details. It is Step 4 in the sequence.
d-      Change/Update/Add volumes as per your need.
e-      Click ‘Next: Add Tags’, It is Step 5 in the sequence. You can add some tag keywords which you can use to search the instances using these tags.
f-       Click ‘Next: Configure Security Group’. It is Step 6 in the sequence, here you can choose an existing security group or create new. In case of new SG, provide the name and description. Add rules (select type of rule, source etc.) and click ‘Review and Launch’. Refer here for more details on Security Groups: 
 

g-      Review the launch configuration and click on ‘Launch’ if all looks good or cancel.
h-      Once you click on ‘Launch’, your instance will be available for use within a minute.
i-        If you want to launch similar instance from EC2 instances section. Select the healthy and running instance and ‘Right click’, you will see below given options. Select ‘Launch more like this’ and in just few clicks another similar instance will be up and running.


Refer here for learning more about launching instances:     




v  Now, instances are up and running. We will setup a small app on both instances.
v  Connect to the instance where you want to setup the web app. Refer here for more details on connecting to various types of instances: 


I am connecting to instance via Putty.

v  Here we will install Apache2 and create a static html page for testing.
Follow the below given steps:
a-      Install apache2
sudo apt-get update
sudo apt-get install apache2

b-      Check the status of installation. Below command will show the status of apache2 as active. If it is active then your instance is installed and working fine.
sudo service apache2 status

c-      Now stop the apache2 service and increate a static html page under ‘/var/www/html’ directory. Rename the existing ‘index.html’ as a backup.
sudo service apache2 stop

cd /var/www/html

sudo mv index.html index.html.bak

sudo vi index.html

d-      Edit the index.html and add following static html code.
<html>
  <head><title>My App</title></head>
  <body>
      <h2>I am instance 1</h2>
  </body>
</html>

e-      Test the setup on instance one, Copy your Public IP or Public DNS from instance details page and copy to browser URL and hit enter.
f-       If works then repeat the same steps for another instances. For example I setup another instance where I have following html code.
<html>
<head><title>My App</title></head>
<body>
<h2>I am instance 2</h2>
</body>
</html>

g-      Test the setup on instance two, Copy your Public IP or Public DNS from instance details page and copy to browser URL and hit enter.
h-      Now both instances are ready.

Launch and ELB and attach the EC2 instances with it:


Follow the below given steps to Launch ELB and attach existing instances with it:
v  Logon to AWS Console and go to EC2 dashboard at https://console.aws.amazon.com/ec2/
v  On the navigation bar, choose a region for your load balancer. Be sure to select the same region that you selected for your EC2 instances.
v  On the navigation pane and under Load Balancing, choose Load Balancers.
v  Choose Application Load Balancer. You can choose between “Application Load Balancer” & “Classic Load Balancer”. Here we are working with web apps so we are selecting “Application Load Balancer”.
v  You will be landed to ‘Configure Load Balancer’ page.
v  Under “Basic Configuration Section”, provide the Name, Scheme (here we will choose scheme as Internet-Facing as our target users for the ELB are from internet and it is publicly accessible) and IP address type (usually IPV4).











v  Under “Listeners” section select Load Balancer Protocol and Load Balancer Port based on your need. You can also add more listeners e.g. if you want your ELB to use SSL then add HTTPS listener. Here we will go with http listener.









v  Select the VPC from the list and select the availability zones depending on need. For example if your EC2 instances are in different-2 zone then you can select those target AZs. 





v  In the tags section add tags for the ELB.

   

v  Click “Next: Configure Security Settings”, you will land to security configuration page. Since we have not opted for HTTPS you will see following warning. You can click next to continue.








v  Click “Next: Configure Security Groups”, you will land to SG config page. You can select from an existing Security Group. Here let’s create a new SG specific to ELB.















v  Click “Next: Configure Routing”, you will be landed to routing config page where you need to map the routing path and health check configs for your apps with ELB.
v   Provide the name of Group and health check path mapping. Here we will keep path as ‘/’ which is default as we have configured index.html which is at root path ‘/’.
You can change the advance settings of health check but let’s keep default values for now here. Remember to change the target port if your web app is running on different port than 80.



















v  Click “Next: Register Targets” to tag target EC2 instances.
v  You can search and select the no. of instances you want to REGISTER to this ELB which will listen to user’s request in front of web apps at port 80. Note, you app may be running on different ports which you would have configured at step 13 above. Select the instances and click “Add to registered” button.















v  Click “Next: Review” to review and finish the configuration of ELB.
v  Click “Create” to finish. Your ELB will be up and running in few minutes. Initially it will show the status as “Provisioning” but when it changes to “active”, then it is ready to use. 
v  Now, let’s test whether the ELB and Webapps are talking each other or not. Go to Description page and find the DNS name of the ELB. Copy the DNS name and paste in Browser URL field and hit enter. It should take you automatically to your webapps depending on load and health status.
v  When I hit the URL first time it takes to instance 1 and next time it takes me to instance 2 based on “round robin routing algorithm for TCP listeners”.

















Configure Route 53 DNS failover to redirect to maintenance page when instances are unhealthy/down:

Follow the below given steps:
v  Log on to AWS Console and go to https://console.aws.amazon.com/route53


v  Route 53 provides two types of health checking.
a-      Availability and performance monitoring           
b-      DNS failover

Here we are interested in DNS Failover; you can refer here for more details:



v  Click on “Create Health Check” button.
v  In the ‘Configure health check’ section provide the name and what to monitor. Here we will select “Endpoint” for monitoring.
v  In the ‘Monitor an endpoint’ section provide the details of the endpoint which we want to monitor. Keep the Advance configuration as is. You can choose between IP and Domain name. I have provided domain name of ELB but we can also provide the Public IP or Elastic IP of the ELB.










































v  In step 2 “Get notified when health check fails” of health check configuration you can chose to get notification when status is unhealthy. For now we are opting out.
v  Click on “Create”, it will create the health check in few minutes. Initially it shows status as ‘Unknown’ but once it’s created properly it shows ‘Healthy’ upon checking the status of endpoint configured. Here we have configured our ELB.








v  Now in the left side Panel, go to ‘Hosted Zone’ and create a new hosted zone.
Note: Failover cannot be configured in private hosted zones. It must be public hosted zones.
v  Click on ‘Create Hosted Zone’
v  Provide the Domain name e.g. ‘abhinav.com’ and Click ‘Create’. It will create a record set for the given domain.
Note: There should be a valid domain name in order to use DNS Failover service. e.g. ‘abhinav.com’ should be a valid domain name registered with either any domain name provider or directly via route 53.

Refer to register a domain: 


v  You will your newly created hosted zone.





v  Now, click on the newly created hosted zone ‘abhinav.com’. You will be landed to hosted zone record set page.  














v  You can see two record sets created by default. We need to create our record sets for DNS failover.
v  Click on “Create Record Set” button to create a new record set. We will create 2 record sets (one for primary site and one for secondary site).
v  You will see a form in right hand panel.
a.      Provide the name of the record. (We need to make sure that static web page domain name matches with the record name. e.g. if static web page bucket is “web.abhinav.com” then record name should be “web” which will be appended with “.abhinav.com” sub domain since we are creating the record in “abhinav.com” hosted zone).
b.      Keep the type as is (A-IPV4 Address). 
c.       Keep the ‘Alias’ as ‘No’.
d.      Change TTL to 60 seconds as recommended by Route 53. Or you can keep the default value as is.
e.      In the “Value” section provide the Elastic IP of your load balancer (you can use Public IP as well if don’t have Elastic IP but you may have to change the config again if Public IP changes next time). e.g. 56.153.54.11 is the Elastic IP in my case.
f.        Select the “Routing Policy” as “Failover”
g.      Keep the “Failover Record Type” as “Primary”
h.      Provide the “Set Id” as needed or keep it default e.g. web-Primary (recordsetname-Primary).
i.        Set “Evaluate Target Health” to “Yes”.
j.        Click on “Yes” for Associate with Health Check.
k.       Once you click ‘Yes’ for Health Check, you need to select the health check created above in Step 3.
l.        Click “Create” to save the record set.


a.      Now your primary record set is ready. We need to create another record, where in case of unhealthy instance Route 53 will redirect users to static maintenance page.
b.      Click on “Create Record Set” button to create a new record set.
c.       Provide the name of the record. (We need to make sure that static web page domain name matches with the record name. e.g. if static web page bucket is “web.abhinav.com”).
d.      Keep the type as is (A-IPV4 Address). 
e.      Select the ‘Alias’ as ‘Yes’, and select the “Alias Target” as S3 bucket static website.
f.        Select the “Routing Policy” as “Failover”
g.      Select the “Failover Record Type” as “Secondary”
h.      Provide the “Set Id” as needed or keep it default e.g. web-Secondary (recordsetname-Secondary).
i.        Set “Evaluate Target Health” to “No”.
j.        Keep on “No” for Associate with Health Check, as we have to redirect request to secondary hence health check is not required. We will assume that static website is always healthy.
k.       Click on “Create” to create the record set. Now we are all set with Route 53 configuration.



v  Now before testing failover check whether your primary site is responding or not.
v  Hip “http://web.abhinav.com” in browser URL and your primary site should respond.






v  You can see the ELB is responding and one after one we got response from both instances.
v  Now to test the failover we need to stop the apache2 service running on both EC2 instances which we launched earlier. Connect to each EC2 instance and run following command:

sudo service apache2 stop

v  Once you stop the service, in few seconds (based on health check configuration) the status will show as “Unhealthy”. We configured 30 seconds for checking the health status.







v  Now, hit “web.abhinav.com” in Browser URL and it will redirect you to S3 site where we did setup of maintenance page. Before that press “CTRL+F5” to clear the browser cache because sometimes some browser caches the old response. 







You can see Route 53 is redirecting to maintenance page when ELB is returning unhealthy response to health checks.