Repository: chef/automate
Pull request #6448 (2021-12-24 14:30:03)

Compliance command v1
Signed-off-by: MadhviA <matroliy@progress.com>

### :nut_and_bolt: Description: What code changed, and why?

This PR adds a new cli option under license.
The usages are:
- Get all the unique node counts running **Chef Compliance Scan** and reporting to Automate over a period of days
- Get all the Infra Client node reports generated by **Chef Compliance Scan** and reporting to Automate over a period of days

<!-- /!\ Please ensure that you are NOT disclosing any customer information without their consent /!\ -->

### :chains: Related Resources
https://github.com/chef/automate/issues/6375

### :+1: Definition of Done
- The cli outcome should have the location of the generated CSV
- The CSV should give the details of the runs and nodes

### :athletic_shoe: How to Build and Test the Change
```
hab studio enter
rebuild components/automate-cli
start_all_services
chef_load_compliance_nodes 50 // Load 50 nodes
chef-automate license complianceUniqNodeRunReport -s 2021-12-21 -e 2021-12-24
chef-automate license complianceNodeRunReport -s 2021-12-21 -e 2021-12-24
```
### :white_check_mark: Checklist

**All PRs** must tick these:

- [x] I have read the [CONTRIBUTING document](https://github.com/chef/automate/blob/master/CONTRIBUTING.md).
- [x] All commits signed-off for the [Developer Certification of Origin](https://github.com/chef/chef/blob/master/CONTRIBUTING.md#developer-certification-of-origin-dco).

With occasional exceptions, all PRs **from Progress employees** must tick these:

- [x] Is the code clear? *(complicated code or lots of comments--subdivide and use well-named methods, meaningful variable names, etc.)*
- [x] Consistency checked? *(user notifications, user prompts, visual patterns, code patterns, variable names)*
- [x] Repeated code blocks eliminated? *(adapt and reuse existing components, blocks, functions, etc.)*
- [x] Spelling, grammar, typos checked? *(at a minimum use `make spell` in any component directory)*
- [x] Code well-formatted? *(indents, line breaks, etc. improve rather than hinder readability)*

All PRs **from Progress employees** should tick these if appropriate:

- [ ] Tests added/updated? (all new code needs new tests)
- [ ] Docs added/updated? (all customer-facing changes)

*Please add a note next to any checkbox above if you are NOT ticking it.*

### :camera: Screenshots, if applicable


netlify[bot]: 👷 Deploy Preview for *chef-automate* processing.


🔨 Explore the source changes: 99896f74a90524c16c9c669597b00733597fa162

🔍 Inspect the deploy log: [https://app.netlify.com/sites/chef-automate/deploys/61c5d96c1336790007ed03ff](https://app.netlify.com/sites/chef-automate/deploys/61c5d96c1336790007ed03ff)

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; ![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=chef_automate&pullRequest=6448&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=chef_automate&pullRequest=6448&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=chef_automate&pullRequest=6448&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=chef_automate&pullRequest=6448&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=chef_automate&pullRequest=6448&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=chef_automate&pullRequest=6448&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=chef_automate&pullRequest=6448&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=chef_automate&pullRequest=6448&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=chef_automate&pullRequest=6448&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=chef_automate&pullRequest=6448&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=chef_automate&pullRequest=6448&resolved=false&types=CODE_SMELL) [14 Code Smells](https://sonarcloud.io/project/issues?id=chef_automate&pullRequest=6448&resolved=false&types=CODE_SMELL)

[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/0-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=chef_automate&pullRequest=6448&metric=new_coverage&view=list) [0.0% Coverage](https://sonarcloud.io/component_measures?id=chef_automate&pullRequest=6448&metric=new_coverage&view=list)  
[![34.5%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/20plus-16px.png '34.5%')](https://sonarcloud.io/component_measures?id=chef_automate&pullRequest=6448&metric=new_duplicated_lines_density&view=list) [34.5% Duplication](https://sonarcloud.io/component_measures?id=chef_automate&pullRequest=6448&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/.bldr.toml b/.bldr.toml
index 1774a985f..a12e74355 100644
--- a/.bldr.toml
+++ b/.bldr.toml
@@ -1187,6 +1187,12 @@ plan_path = "components/automate-cluster-ctl"
 paths = [
   "components/automate-cluster-ctl/*"
 ]
+
+[automate-netcat]
+plan_path = "components/automate-netcat"
+paths = [
+  "components/automate-netcat/*"
+]
 # GENERATED_HAB_PACKAGE_CONFIG_END
 
 [automate-workflow-ctl]
diff --git a/.expeditor/verify_private.pipeline.yml b/.expeditor/verify_private.pipeline.yml
index c54fe0cc7..fe4b32619 100644
--- a/.expeditor/verify_private.pipeline.yml
+++ b/.expeditor/verify_private.pipeline.yml
@@ -566,7 +566,7 @@ steps:
   - label: "a1migration"
     command:
       - integration/run_test integration/tests/a1migration.sh
-    timeout_in_minutes: 20
+    timeout_in_minutes: 30
     expeditor:
       executor:
         linux:
diff --git a/.license_scout.yml b/.license_scout.yml
index 56d29ed91..80cabb913 100644
--- a/.license_scout.yml
+++ b/.license_scout.yml
@@ -308,6 +308,9 @@ fallbacks:
     - name: core/ruby26
       license_id: BSD-2-Clause
       license_content: https://raw.githubusercontent.com/ruby/ruby/trunk/COPYING
+    - name: core/ruby27
+      license_id: BSD-2-Clause
+      license_content: https://raw.githubusercontent.com/ruby/ruby/trunk/COPYING
     - name: core/cacerts
       license_id: MPL-2.0
       license_content: https://curl.haxx.se/docs/caextract.html
@@ -459,6 +462,8 @@ exceptions:
       reason:  Exception made by Chef Legal
     - name: core/ruby30
       reason: Ruby license (previously shipped in Automate 1)
+    - name: core/ruby27
+      reason:  Exception made for chef server upgrade
 
 
   ruby:
diff --git a/HABITAT_PACKAGES b/HABITAT_PACKAGES
index 203a70628..92281e0ac 100644
--- a/HABITAT_PACKAGES
+++ b/HABITAT_PACKAGES
@@ -89,4 +89,5 @@ automate-ha-postgresql components/automate-backend-postgresql
 automate-ha-ctl components/automate-backend-ctl
 automate-ha-deployment components/automate-backend-deployment
 automate-ha-pgleaderchk components/automate-ha-pgleaderchk
-automate-ha-cluster-ctl components/automate-cluster-ctl
\ No newline at end of file
+automate-ha-cluster-ctl components/automate-cluster-ctl
+automate-netcat components/automate-netcat
\ No newline at end of file
diff --git a/components/authn-service/server/server.go b/components/authn-service/server/server.go
index 403ed0730..8fa768060 100644
--- a/components/authn-service/server/server.go
+++ b/components/authn-service/server/server.go
@@ -7,6 +7,7 @@ import (
 	"net/http"
 	"net/textproto"
 	"net/url"
+	"time"
 
 	"github.com/grpc-ecosystem/grpc-gateway/runtime"
 	"github.com/pkg/errors"
@@ -193,16 +194,31 @@ func newServer(ctx context.Context, c Config) (*Server, error) {
 			tokenID = existingID
 		}
 
-		_, err := policiesClient.AddPolicyMembers(ctx, &authz.AddPolicyMembersReq{
-			Id:      IngestPolicyID,
-			Members: []string{fmt.Sprintf("token:%s", tokenID)},
-		})
-		if err != nil {
+		/*
+			Below code is only execute when you add token in config at the time of deployment
+			Adding the sleep, is just the tempory solution, this might work
+		*/
+		var policyAssigned bool = false
+		for i := 4; i > 0; i-- {
+			_, err = policiesClient.AddPolicyMembers(ctx, &authz.AddPolicyMembersReq{
+				Id:      IngestPolicyID,
+				Members: []string{fmt.Sprintf("token:%s", tokenID)},
+			})
+			if err != nil {
+				policyAssigned = false
+			} else {
+				s.logger.Debug("exiting for loop")
+				policyAssigned = true
+				break
+			}
+			s.logger.Debug(fmt.Sprintf(" Adding the sleep for %d seconds ", i))
+			time.Sleep(time.Duration(3) * time.Second)
+		}
+		if !policyAssigned {
 			s.logger.Warn(errors.Wrap(err, "there was an error granting the legacy data collector token ingest access").Error())
 			s.logger.Warn(fmt.Sprintf("please manually add token with ID %q to the policy with ID %q", tokenID, IngestPolicyID))
 		}
 	}
-
 	return s, nil
 }
 
diff --git a/components/automate-backend-deployment/README.md b/components/automate-backend-deployment/README.md
index dc569aea9..610d734a3 100644
--- a/components/automate-backend-deployment/README.md
+++ b/components/automate-backend-deployment/README.md
@@ -5,6 +5,4 @@ This provides the `automate-backend-deployment` package.
 This package will build a package using terraform/a2ha-terraform, inspecs, test, certs and Makefile. 
 
 This is the heart of the a2ha because this component will set up a workspace for a2ha and all the a2ha command will get avilable after installing this package.
-bump the pkg 
- 
-
+bump the pkg
\ No newline at end of file
diff --git a/components/automate-backend-deployment/migration/A2HA-to-AutomateHA/migration.md b/components/automate-backend-deployment/migration/A2HA-to-AutomateHA/migration.md
new file mode 100644
index 000000000..82de05159
--- /dev/null
+++ b/components/automate-backend-deployment/migration/A2HA-to-AutomateHA/migration.md
@@ -0,0 +1,60 @@
+In some scenario we are required to migrate A2HA data to Automate HA cluster`(as we have new HA implementation in Automate)`. For that here are some steps that you'll have to follow.
+
+
+1) Take backup of chef-automate from A2HA `(Old)` using below command, this command can be executed from any of front-end (chef-automate) node  
+ in case of multiple frontends. 
+ Usually if you don't specify any location for backup in config.toml then that backup will be store on /var/opt/chef-automate/backup location 
+ if you hit below command.
+
+    `sudo chef-automate backup create`
+    
+
+2) Make a tar file of backup that we have taken in step 1. Here make sure that you are also taking backup of `.tmp` directory. Otherwise you'll face some issue related to metadata.json.
+
+    E.g. `tar -cvf backup.tar.gz path/to/backup/20201030172917/ /path/to/backup/automate-backup-elasticsearch/ /path/to/backup/.tmp/`
+   
+
+
+3) Create a abb file from any of chef-automate frontend node of A2HA. This will create a bundle of all necessary keys. Like pivotal.pem, secret key etc. Usually this will not be included in regular backup`(step 1)` so make sure you create a bundle for that.
+
+
+    `sudo chef-automate bootstrap bundle create bootstrap.abb`
+
+4) copy tar file and abb file that we created in step 2 and 3 respectively to any of the Automate HA chef-automate instance and extract it on specific location that you mentioned in config.toml file. Its is not necessary to extract that backup on below location. But make sure that restore command is able to read backup or not from your defined location on step1.
+
+
+    E.g  `/mnt/automate-backup`
+    
+5) Restore A2HA backup on Automate HA. Read this docs for [chef-automate restore](https://docs.chef.io/automate/restore/). In below command there is also a frontend-20210624095659.aib generated in new Automate HA file mention that's because while restoration we also keep in mind all the services habitat release version. Because during restoration time A2HA restoration will try to find A2HA habitat pkg version so there can be a scenario occure where all`(A2HA and automate HA  frontends (automate's))` packages version can't be the same. That's why we are passing current Automate HA packages. You can find frontend aib file in /var/tmp directory of your Automate HA chef-automate instance.
+
+
+    E.g. `sudo chef-automate backup restore /mnt/automate_backups/backups/20210622065515/ --patch-config /etc/chef-automate/config.toml --airgap-bundle /var/tmp/frontend-20210624095659.aib --skip-preflight`
+
+ 
+
+
+6) After that if you not do this step then you will might face warning when you'll try to open chef-automate UI `It looks like you do not have permission to access any data in automate` So make sure you have unpacked the. abb file. Otherwise you'll not see login page. To unpack the bootstrap file that we copied from A2HA chef-automate using below command
+
+
+    `sudo chef-automate bootstrap bundle unpack bootstrap.abb`
+    
+
+
+7) Copy the bootstrap.abb file to another automate node and chef node also if you are having multiple automate and chef instances. Because the secrets we have restored by unpacking the bootstrap file would be different for another automate instance. So we need to make that all the automate and chef instance would be in sync.
+
+
+
+Important command and notes
+
+Using the below command you can see what bootstrap includes in aib file and abb file. aib file will only include keys related data while abb file will include service packages also. You can use below command and can compare both the file's data
+
+
+    `tail -n +3 bootstrap.abb | tar -tf -`
+    
+
+After using above command, if you want to see the data of service like secret service then you would see those services into `/hab/svc ` directory. This'll be needed if you want to compare aib data between multiple FE(In respective chef-automate and chef-server) nodes.  
+
+     E.g For secret service `cat /hab/svc/secrets-service/data/secrets_key`
+    
+  
+
diff --git a/components/automate-backend-elasticsearch/habitat/cert.sh b/components/automate-backend-elasticsearch/habitat/cert.sh
index 05b8caad6..749f7ad51 100644
--- a/components/automate-backend-elasticsearch/habitat/cert.sh
+++ b/components/automate-backend-elasticsearch/habitat/cert.sh
@@ -2,7 +2,7 @@
  
 openssl genrsa -out MyRootCA.key 2048
 
-openssl req -x509 -new -key MyRootCA.key -sha256 -out MyRootCA.pem -subj '/C=US/ST=Washington/L=Seattle/O=Chef Software Inc/CN=chefrootca'
+openssl req -x509 -new -days 1095 -key MyRootCA.key -sha256 -out MyRootCA.pem -subj '/C=US/ST=Washington/L=Seattle/O=Chef Software Inc/CN=chefrootca'
 
 openssl genrsa -out admin-pkcs12.key 2048
  
@@ -10,7 +10,7 @@ openssl pkcs8 -v1 "PBE-SHA1-3DES" -in "admin-pkcs12.key" -topk8 -out "admin.key"
  
 openssl req -new -key admin.key -out admin.csr -subj '/C=US/ST=Washington/L=Seattle/O=Chef Software Inc/CN=chefadmin'
  
-openssl x509 -req -in admin.csr -CA MyRootCA.pem -CAkey MyRootCA.key -CAcreateserial -out admin.pem -sha256
+openssl x509 -days 1095 -req -in admin.csr -CA MyRootCA.pem -CAkey MyRootCA.key -CAcreateserial -out admin.pem -sha256
  
 openssl genrsa -out ssl-pkcs12.key 2048
  
@@ -18,7 +18,7 @@ openssl pkcs8 -v1 "PBE-SHA1-3DES" -in "ssl-pkcs12.key" -topk8 -out "ssl.key" -no
  
 openssl req -new -key ssl.key -out ssl.csr -subj '/C=US/ST=Washington/L=Seattle/O=Chef Software Inc/CN=chefnode'
  
-openssl x509 -req -in ssl.csr -CA MyRootCA.pem -CAkey MyRootCA.key -CAcreateserial -out ssl.pem -sha256
+openssl x509 -days 1095 -req -in ssl.csr -CA MyRootCA.pem -CAkey MyRootCA.key -CAcreateserial -out ssl.pem -sha256
  
 cat <<EOF >> habitat/default.toml
 # root pem cert that signed the two cert/key pairs below
diff --git a/components/automate-backend-elasticsearch/habitat/plan.sh b/components/automate-backend-elasticsearch/habitat/plan.sh
index 911b93d21..bb702533c 100644
--- a/components/automate-backend-elasticsearch/habitat/plan.sh
+++ b/components/automate-backend-elasticsearch/habitat/plan.sh
@@ -1,5 +1,5 @@
 # shellcheck disable=SC2148
-UPSTREAM_PKG_IDENT="chef/elasticsearch-odfe/0.10.1.2"
+UPSTREAM_PKG_IDENT="chef/elasticsearch-odfe/0.10.1.2/20200902162328"
 pkg_name="automate-ha-elasticsearch"
 pkg_description="Wrapper package for elasticsearch-odfe"
 pkg_origin="chef"
@@ -11,8 +11,8 @@ pkg_upstream_url="https://www.chef.io/automate"
 pkg_deps=(
   chef/mlsa
   core/bash
-  core/procps-ng
   core/openssl
+  core/procps-ng
   "${UPSTREAM_PKG_IDENT}"
 )
 pkg_lib_dirs=(lib)
diff --git a/components/automate-backend-postgresql/habitat/cert.sh b/components/automate-backend-postgresql/habitat/cert.sh
index 00a20ceef..8da643295 100644
--- a/components/automate-backend-postgresql/habitat/cert.sh
+++ b/components/automate-backend-postgresql/habitat/cert.sh
@@ -2,7 +2,7 @@
  
 openssl genrsa -out MyRootCA.key 2048
  
-openssl req -x509 -new -key MyRootCA.key -sha256 -out MyRootCA.pem -subj '/C=US/ST=Washington/L=Seattle/O=Chef Software Inc/CN=chefrootca'
+openssl req -x509 -new -days 1095 -key MyRootCA.key -sha256 -out MyRootCA.pem -subj '/C=US/ST=Washington/L=Seattle/O=Chef Software Inc/CN=chefrootca'
  
 openssl genrsa -out ssl-pkcs12.key 2048
  
@@ -10,7 +10,7 @@ openssl pkcs8 -v1 "PBE-SHA1-3DES" -in "ssl-pkcs12.key" -topk8 -out "ssl.key" -no
  
 openssl req -new -key ssl.key -out ssl.csr -subj '/C=US/ST=Washington/L=Seattle/O=Chef Software Inc/CN=chefadmin'
  
-openssl x509 -req -in ssl.csr -CA MyRootCA.pem -CAkey MyRootCA.key -CAcreateserial -out ssl.pem -sha256
+openssl x509 -days 1095 -req -in ssl.csr -CA MyRootCA.pem -CAkey MyRootCA.key -CAcreateserial -out ssl.pem -sha256
  
 cat <<EOF >> habitat/default.toml
 # server public cert used for ssl listener
diff --git a/components/automate-backend-postgresql/habitat/plan.sh b/components/automate-backend-postgresql/habitat/plan.sh
index 58b4b228f..ad74dd1c5 100644
--- a/components/automate-backend-postgresql/habitat/plan.sh
+++ b/components/automate-backend-postgresql/habitat/plan.sh
@@ -4,7 +4,7 @@ pkg_name="automate-ha-postgresql"
 pkg_description="Wrapper package for core/postgresql11"
 pkg_origin="chef"
 vendor_origin="chef"
-pkg_version="11.2"
+pkg_version="11.2.0"
 pkg_maintainer="Chef Software Inc. <support@chef.io>"
 pkg_license=("Chef-MLSA")
 pkg_upstream_url="https://www.chef.io/automate"
diff --git a/components/automate-cli/cmd/chef-automate/automateClusterCtlUtil.go b/components/automate-cli/cmd/chef-automate/automateClusterCtlUtil.go
index db6fac980..79e1a5384 100644
--- a/components/automate-cli/cmd/chef-automate/automateClusterCtlUtil.go
+++ b/components/automate-cli/cmd/chef-automate/automateClusterCtlUtil.go
@@ -1,21 +1,33 @@
 package main
 
 import (
+	"bufio"
 	"bytes"
+	"crypto/md5"
+	"encoding/hex"
 	"errors"
+	"fmt"
+	"html/template"
 	"io"
+	"io/ioutil"
 	"os"
 	"os/exec"
+	"path"
 	"path/filepath"
+	"regexp"
+	"strings"
 	"time"
 
 	dc "github.com/chef/automate/api/config/deployment"
+	api "github.com/chef/automate/api/interservice/deployment"
 	"github.com/chef/automate/components/automate-cli/pkg/status"
+	"github.com/chef/automate/components/automate-deployment/pkg/airgap"
 	"github.com/chef/automate/components/automate-deployment/pkg/client"
 	"github.com/chef/automate/components/automate-deployment/pkg/manifest"
 	mc "github.com/chef/automate/components/automate-deployment/pkg/manifest/client"
 	"github.com/chef/automate/lib/version"
 	"github.com/hpcloud/tail"
+	"github.com/sirupsen/logrus"
 )
 
 func executeAutomateClusterCtlCommand(command string, args []string, helpDocs string) error {
@@ -137,15 +149,46 @@ func bootstrapEnv(dm deployManager) error {
 			"Merging command flag overrides into Chef Automate config failed",
 		)
 	}
+
+	offlineMode := deployCmdFlags.airgap != ""
+	manifestPath := ""
+	var airgapMetadata airgap.UnpackMetadata
+	if offlineMode {
+		writer.Title("Installing artifact")
+		metadata, err := airgap.Unpack(deployCmdFlags.airgap)
+		if err != nil {
+			return status.Annotate(err, status.AirgapUnpackInstallBundleError)
+		}
+		airgapMetadata = metadata
+		manifestPath = api.AirgapManifestPath
+
+		// We need to set the path for the hab binary so that the deployer does not
+		// try to go to the internet to get it
+		pathEnv := os.Getenv("PATH")
+
+		err = os.Setenv("PATH", fmt.Sprintf("%s:%s", path.Dir(metadata.HabBinPath), pathEnv))
+		if err != nil {
+			return err
+		}
+	} else {
+		manifestPath = conf.Deployment.GetV1().GetSvc().GetManifestDirectory().GetValue()
+	}
+
 	manifestProvider := manifest.NewLocalHartManifestProvider(
-		mc.NewDefaultClient(conf.Deployment.GetV1().GetSvc().GetManifestDirectory().GetValue()),
+		mc.NewDefaultClient(manifestPath),
 		conf.Deployment.GetV1().GetSvc().GetHartifactsPath().GetValue(),
 		conf.Deployment.GetV1().GetSvc().GetOverrideOrigin().GetValue())
-	offlineMode := deployCmdFlags.airgap != ""
+
 	err := client.DeployHA(writer, conf, manifestProvider, version.BuildTime, offlineMode)
 	if err != nil && !status.IsStatusError(err) {
 		return status.Annotate(err, status.DeployError)
 	}
+	if offlineMode {
+		err := moveAirgapToTransferDir(airgapMetadata)
+		if err != nil {
+			return status.Annotate(err, status.DeployError)
+		}
+	}
 	err = dm.generateConfig()
 	if err != nil {
 		return status.Annotate(err, status.DeployError)
@@ -153,6 +196,222 @@ func bootstrapEnv(dm deployManager) error {
 	return nil
 }
 
+/*
+In airgap deployement of A2HA we need to have two AIB files in /hab/a2_deploy_workspace/terraform/transfer_files/
+first airgap file will automate generated airgap bundle,
+second file will be backend airgap bundle which is same as automate(frontend) airgap bundle but without header.
+also we need to keep MD5 hash files both frontend and backend bundles.
+we also need to have following files in /hab/a2_deploy_workspace/terraform dir
+1. a2ha_aib_fe.auto.tfvars --> it will contain info of frontend airgap bundle
+2. a2ha_aib_be.auto.tfvars --> it will contain info of backend airgap bundle
+1. a2ha_manifest.auto.tfvars --> it will contain info of required packages to deploy a2ha.
+*/
+func moveAirgapToTransferDir(airgapMetadata airgap.UnpackMetadata) error {
+	if len(deployCmdFlags.airgap) > 0 {
+		var bundleName string = filepath.Base(deployCmdFlags.airgap)
+		if strings.Contains(bundleName, "automate") {
+			bundleName = strings.ReplaceAll(bundleName, "automate", "frontend")
+		}
+		err := copyFileContents(deployCmdFlags.airgap, (AIRGAP_HA_TRANS_DIR_PATH + bundleName))
+		if err != nil {
+			return err
+		}
+		//generating md5 sum for frontend bundle
+		err = generateChecksumFile(AIRGAP_HA_TRANS_DIR_PATH+bundleName, AIRGAP_HA_TRANS_DIR_PATH+bundleName+".md5")
+		if err != nil {
+			return err
+		}
+		//generating frontend auto tfvars
+		frontendTfvars := getBytesFromTempalte("frontendTfvars", frontendAutotfvarsTemplate, map[string]interface{}{
+			"bundleName": bundleName,
+		})
+
+		err = ioutil.WriteFile(AUTOMATE_HA_TERRAFORM_DIR+"a2ha_aib_fe.auto.tfvars", frontendTfvars, AUTOMATE_HA_FILE_PERMISSION_0755) // nosemgrep
+		if err != nil {
+			return err
+		}
+		//generating backend bundle
+		backendBundleFile := AIRGAP_HA_TRANS_DIR_PATH + (strings.ReplaceAll(bundleName, "frontend", "backend"))
+		err = generateBackendAIB(deployCmdFlags.airgap, backendBundleFile)
+		if err != nil {
+			return err
+		}
+		err = generateChecksumFile(backendBundleFile, backendBundleFile+".md5")
+		if err != nil {
+			return err
+		}
+		//generating backend auto tfvars
+		backendTfvars := getBytesFromTempalte("backendTfvars", backendAutotfvarsTemplate, map[string]interface{}{
+			"backendBundleFile": filepath.Base(backendBundleFile),
+		})
+		err = ioutil.WriteFile(AUTOMATE_HA_TERRAFORM_DIR+"a2ha_aib_be.auto.tfvars", backendTfvars, AUTOMATE_HA_FILE_PERMISSION_0755) // nosemgrep
+		if err != nil {
+			return err
+		}
+
+		//generate manifest auto tfvars
+		err = generateA2HAManifestTfvars(airgapMetadata)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func getBytesFromTempalte(templateName string, templateContent string, placeholders map[string]interface{}) []byte {
+	t := template.Must(template.New(templateName).Parse(templateContent))
+	buf := &bytes.Buffer{}
+	if err := t.Execute(buf, placeholders); err != nil {
+		panic(err)
+	}
+	return buf.Bytes()
+}
+func getBldrSupportedPkgName(h string) string {
+	packageName := extractPackageNameFromHartifactPath(h)
+	ver, rel := extarctVersionAndRelease(packageName)
+	origin := extractOrigin(packageName)
+	pkg := extractPackageName(packageName)
+	return origin + "/" + pkg + "/" + ver + "/" + rel
+}
+func generateA2HAManifestTfvars(airgapMetadata airgap.UnpackMetadata) error {
+	var deployablePackages []string
+	for _, h := range airgapMetadata.HartifactPaths {
+		if strings.Contains(h, AUTOMATE_HA_PKG_PG_LDR_CHK) {
+			deployablePackages = append(deployablePackages, "pgleaderchk_pkg_ident = \""+getBldrSupportedPkgName(h)+"\"")
+		}
+		if strings.Contains(h, AUTOMATE_HA_PKG_PG) {
+			deployablePackages = append(deployablePackages, "postgresql_pkg_ident = \""+getBldrSupportedPkgName(h)+"\"")
+		}
+		if strings.Contains(h, AUTOMATE_HA_PKG_HA_PROXY) {
+			deployablePackages = append(deployablePackages, "proxy_pkg_ident = \""+getBldrSupportedPkgName(h)+"\"")
+		}
+		if strings.Contains(h, AUTOMATE_HA_PKG_JOURNALBEAT) {
+			deployablePackages = append(deployablePackages, "journalbeat_pkg_ident = \""+getBldrSupportedPkgName(h)+"\"")
+		}
+		if strings.Contains(h, AUTOMATE_HA_PKG_METRICBEAT) {
+			deployablePackages = append(deployablePackages, "metricbeat_pkg_ident = \""+getBldrSupportedPkgName(h)+"\"")
+		}
+		if strings.Contains(h, AUTOMATE_HA_PKG_KIBANA) {
+			deployablePackages = append(deployablePackages, "kibana_pkg_ident = \""+getBldrSupportedPkgName(h)+"\"")
+		}
+		if strings.Contains(h, AUTOMATE_HA_ES) {
+			deployablePackages = append(deployablePackages, "elasticsearch_pkg_ident = \""+getBldrSupportedPkgName(h)+"\"")
+		}
+		if strings.Contains(h, AUTOMATE_HA_ES_CAR) {
+			deployablePackages = append(deployablePackages, "elasticsidecar_pkg_ident = \""+getBldrSupportedPkgName(h)+"\"")
+		}
+		if strings.Contains(h, AUTOMATE_HA_CURATOR) {
+			deployablePackages = append(deployablePackages, "curator_pkg_ident = \""+getBldrSupportedPkgName(h)+"\"")
+		}
+	}
+	return ioutil.WriteFile(AUTOMATE_HA_TERRAFORM_DIR+"a2ha_manifest.auto.tfvars", []byte(strings.Join(deployablePackages[:], "\n")), AUTOMATE_HA_FILE_PERMISSION_0755) // nosemgrep
+}
+func extractPackageNameFromHartifactPath(path string) string {
+	path = strings.ReplaceAll(path, "/hab/cache/artifacts/", "")
+	return path
+}
+
+func copyFileContents(src, dst string) (err error) {
+	in, err := os.Open(src)
+	if err != nil {
+		return
+	}
+	defer in.Close()
+	out, err := os.Create(dst)
+	if err != nil {
+		return
+	}
+	defer func() {
+		cerr := out.Close()
+		if err == nil {
+			err = cerr
+		}
+	}()
+	if _, err = io.Copy(out, in); err != nil {
+		return
+	}
+	err = out.Sync()
+	return
+}
+
+func generateBackendAIB(filePath string, destPath string) error {
+	installBundleFile, err := os.Open(filePath)
+	if err != nil {
+		return errors.New(err.Error() + " Failed to open install bundle file " + filePath)
+	}
+	defer installBundleFile.Close() // nolint: errcheck
+
+	bufReader := bufio.NewReader(installBundleFile)
+	// Read the header:
+	// AIB-1\n\n
+	s, err := bufReader.ReadString('\n')
+	if err != nil {
+		return errors.New(err.Error() + " Could not read artifact file")
+	}
+	if s != "AIB-1\n" {
+		return errors.New("malformed install bundle file")
+	}
+
+	s, err = bufReader.ReadString('\n')
+	if err != nil {
+		return err
+	}
+
+	if s != "\n" {
+		return errors.New("malformed install bundle file")
+	}
+
+	fo, err := os.Create(destPath)
+	if err != nil {
+		panic(err)
+	}
+	defer func() {
+		if err := fo.Close(); err != nil {
+			panic(err)
+		}
+	}()
+
+	bufWriter := bufio.NewWriter(fo)
+	buf := make([]byte, 1024)
+	for {
+		n, err := bufReader.Read(buf)
+		if err != nil && err != io.EOF {
+			panic(err)
+		}
+		if n == 0 {
+			break
+		}
+		if _, err := bufWriter.Write(buf[:n]); err != nil {
+			panic(err)
+		}
+	}
+
+	if err = bufWriter.Flush(); err != nil {
+		panic(err)
+	}
+
+	return nil
+}
+
+func generateChecksumFile(sourceFileName string, checksumFileName string) error {
+	sfile, err := os.Open(sourceFileName)
+	if err != nil {
+		return err
+	}
+	defer sfile.Close()
+	hash := md5.New() // nosemgrep
+	if _, err := io.Copy(hash, sfile); err != nil {
+		return err
+	}
+	sum := hash.Sum(nil)
+	hashedFileContent := hex.EncodeToString(sum) + "  " + filepath.Base(sfile.Name())
+	err = ioutil.WriteFile(checksumFileName, []byte(hashedFileContent), AUTOMATE_HA_FILE_PERMISSION_0644) // nosemgrep
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
 func isA2HARBFileExist() bool {
 	if checkIfFileExist(filepath.Join(initConfigHabA2HAPathFlag.a2haDirPath, "a2ha.rb")) {
 		return true
@@ -175,26 +434,43 @@ func executeSecretsInitCommand(secretsKeyFilePath string) error {
 	return nil
 }
 
-func executeShellCommand(command string, args []string) error {
+func executeShellCommand(command string, args []string, workingDir string) error {
 	writer.Printf("%s command execution started \n\n\n", command)
 	c := exec.Command(command, args...)
 	c.Stdin = os.Stdin
-	var out bytes.Buffer
-	var stderr bytes.Buffer
-	c.Stdout = io.MultiWriter(&out)
-	c.Stderr = io.MultiWriter(&stderr)
+	if len(workingDir) > 0 {
+		c.Dir = workingDir
+	}
+	c.Stdout = io.MultiWriter(os.Stdout)
+	c.Stderr = io.MultiWriter(os.Stderr)
 	err := c.Run()
-	if err != nil {
-		writer.Printf(stderr.String())
-		return status.Wrap(err, status.CommandExecutionError, "")
+	writer.Printf("%s command execution done, exiting\n", command)
+	return err
+}
+
+func extarctVersionAndRelease(filename string) (string, string) {
+	r := regexp.MustCompile(RELEASE_AND_VERSION_PATTERN)
+	match := r.FindStringSubmatch(filename)
+	if match == nil {
+		logrus.Debugf("failed to parse version of hart %s", filename)
 	}
-	outStr, errStr := string(out.Bytes()), string(stderr.Bytes())
-	if len(outStr) > 0 {
-		writer.Printf("\nout:\n%s", outStr)
+	return match[1], match[2]
+}
+
+func extractOrigin(filename string) string {
+	r := regexp.MustCompile(ORIGIN_PATTERN)
+	match := r.FindStringSubmatch(filename)
+	if match == nil {
+		logrus.Debugf("failed to parse origin from of hart %s", filename)
 	}
-	if len(errStr) > 0 {
-		writer.Printf("\nerr:\n%s\n", errStr)
+	return match[0]
+}
+
+func extractPackageName(filename string) string {
+	r := regexp.MustCompile(PACKAGE_NAME_PATTERN)
+	match := r.FindStringSubmatch(filename)
+	if match == nil {
+		logrus.Debugf("failed to parse package name of hart %s", filename)
 	}
-	writer.Printf("%s command execution done, exiting\n", command)
-	return err
+	return match[0][1 : len(match[0])-(len(match[0])-(strings.LastIndexAny(match[0], "-")))]
 }
diff --git a/components/automate-cli/cmd/chef-automate/automateConstants.go b/components/automate-cli/cmd/chef-automate/automateConstants.go
index fd9ffb5d5..6b5027a75 100644
--- a/components/automate-cli/cmd/chef-automate/automateConstants.go
+++ b/components/automate-cli/cmd/chef-automate/automateConstants.go
@@ -11,3 +11,31 @@ const AUTOMATE_HA_RUN_LOG_DIR = "/hab/a2_deploy_workspace/logs"
 const AUTOMATE_HA_WORKSPACE_DIR = "/hab/a2_deploy_workspace"
 const AUTOMATE_HA_AUTOMATE_CONFIG_FILE = "/hab/a2_deploy_workspace/configs/automate.toml"
 const AUTOMATE_HA_INVALID_BASTION = "Invalid bastion, to run this command use automate bastion"
+const AIRGAP_HA_TRANS_DIR_PATH = "/hab/a2_deploy_workspace/terraform/transfer_files/"
+const AUTOMATE_HA_TERRAFORM_DIR = "/hab/a2_deploy_workspace/terraform/"
+const AUTOMATE_HA_FILE_PERMISSION_0755 = 0755
+const AUTOMATE_HA_FILE_PERMISSION_0644 = 0644
+
+const frontendAutotfvarsTemplate = `
+frontend_aib_dest_file = "/var/tmp/{{ .bundleName }}"
+frontend_aib_local_file = "{{ .bundleName }}"
+		`
+
+const backendAutotfvarsTemplate = `
+backend_aib_dest_file = "/var/tmp/{{ .backendBundleFile }}"
+backend_aib_local_file = "{{ .backendBundleFile }}"
+`
+
+const AUTOMATE_HA_PKG_PG_LDR_CHK = "automate-ha-pgleaderchk"
+const AUTOMATE_HA_PKG_PG = "automate-ha-postgresql"
+const AUTOMATE_HA_PKG_HA_PROXY = "automate-ha-haproxy"
+const AUTOMATE_HA_PKG_JOURNALBEAT = "automate-ha-journalbeat"
+const AUTOMATE_HA_PKG_METRICBEAT = "automate-ha-metricbeat"
+const AUTOMATE_HA_PKG_KIBANA = "automate-ha-kibana"
+const AUTOMATE_HA_ES = "automate-ha-elasticsearch"
+const AUTOMATE_HA_ES_CAR = "automate-ha-elasticsidecar"
+const AUTOMATE_HA_CURATOR = "automate-ha-curator"
+
+const ORIGIN_PATTERN = `[a-zA-z0-9]*`
+const PACKAGE_NAME_PATTERN = `-[a-zA-Z0-9]*-.*-\d+\.`
+const RELEASE_AND_VERSION_PATTERN = `.*-(\d+\.\d+\.*\d*)-(\d{14})-.*\.hart$`
diff --git a/components/automate-cli/cmd/chef-automate/haValidateInfra.go b/components/automate-cli/cmd/chef-automate/haValidateInfra.go
new file mode 100644
index 000000000..832ba0e3a
--- /dev/null
+++ b/components/automate-cli/cmd/chef-automate/haValidateInfra.go
@@ -0,0 +1,120 @@
+// Copyright © 2017 Chef Software
+
+package main
+
+import (
+	"errors"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/chef/automate/components/automate-cli/pkg/status"
+	"github.com/chef/automate/components/automate-deployment/pkg/airgap"
+	"github.com/spf13/cobra"
+)
+
+var validateFlags = struct {
+	airgapPath        string
+	habitatBundlePath string
+}{}
+
+const MISSING_BUNDLE = "for offline mode airgap bundle path and habitat bundle path are mandatory"
+const VALIDATION_SCRIPT_PATH = "/var/tmp/validateInfra.sh"
+
+func init() {
+	validateCmd.PersistentFlags().StringVar(
+		&validateFlags.airgapPath,
+		"airgap-bundle",
+		"",
+		"Path to airgap bundle")
+	RootCmd.AddCommand(validateCmd)
+
+	validateCmd.PersistentFlags().StringVar(
+		&validateFlags.habitatBundlePath,
+		"habitat-bundle",
+		"",
+		"Path to haitat tar bundle")
+	RootCmd.AddCommand(validateCmd)
+}
+
+var validateCmd = &cobra.Command{
+	Use:   "validate-ha-infrastructure",
+	Short: "Validate existing infrastructure of Automate HA",
+	Long:  "Validate existing infrastructure of automate HA",
+	Annotations: map[string]string{
+		NoCheckVersionAnnotation: NoCheckVersionAnnotation,
+	},
+	RunE: runValidateCmd,
+}
+
+func runValidateCmd(cmd *cobra.Command, args []string) error {
+	var offlineMode bool = false
+	var netcatPath string = ""
+	if len(args) < 0 {
+		return errors.New("config.toml is required as argument")
+	}
+	var deployer, derr = getDeployer(args[0])
+	if derr != nil || deployer == nil {
+		return status.Wrap(derr, status.ConfigError, invalidConfig)
+	}
+	if len(validateFlags.airgapPath) > 0 && len(validateFlags.habitatBundlePath) > 0 {
+		offlineMode = true
+		_, err := os.Stat(validateFlags.habitatBundlePath)
+		if err != nil {
+			return err
+		}
+		metadata, err := unpackAirgap()
+		if err != nil {
+			return err
+		}
+		for _, v := range metadata.HartifactPaths {
+			if strings.Contains(v, "automate-netcat") {
+				netcatPath = v
+				break
+			}
+		}
+	}
+
+	if (len(validateFlags.airgapPath) > 0 && len(validateFlags.habitatBundlePath) < 1) ||
+		(len(validateFlags.airgapPath) < 1 && len(validateFlags.habitatBundlePath) > 0) {
+		return errors.New(MISSING_BUNDLE)
+	}
+	err := generateValidationScript()
+	if err != nil {
+		return err
+	}
+	err = executeValidationScript(args, netcatPath, offlineMode)
+	if err != nil {
+		return err
+	}
+	err = removeValidationScript()
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func executeValidationScript(args []string, netcatHartFile string, offlineMode bool) error {
+	configFilePath, err := filepath.Abs(args[0])
+	if err != nil {
+		return err
+	}
+	if offlineMode {
+		return executeShellCommand("/bin/bash", []string{VALIDATION_SCRIPT_PATH, configFilePath, validateFlags.habitatBundlePath, netcatHartFile}, "")
+	} else {
+		return executeShellCommand("/bin/bash", []string{VALIDATION_SCRIPT_PATH, configFilePath}, "")
+	}
+}
+
+func unpackAirgap() (airgap.UnpackMetadata, error) {
+	return airgap.Unpack(validateFlags.airgapPath)
+}
+
+func generateValidationScript() error {
+	return ioutil.WriteFile(VALIDATION_SCRIPT_PATH, []byte(validationScript), 0755) // nosemgrep
+}
+
+func removeValidationScript() error {
+	return os.Remove(VALIDATION_SCRIPT_PATH)
+}
diff --git a/components/automate-cli/cmd/chef-automate/haValidationScriptTmpl.go b/components/automate-cli/cmd/chef-automate/haValidationScriptTmpl.go
new file mode 100644
index 000000000..b820543ae
--- /dev/null
+++ b/components/automate-cli/cmd/chef-automate/haValidationScriptTmpl.go
@@ -0,0 +1,1006 @@
+package main
+
+const validationScript = `
+#!/bin/bash
+
+CONFIG=$1
+HAB_PATH=$2
+NETCAT_PATH=$3
+
+
+RED='\033[0;31m'
+PURPLE='\033[0;35m'
+NC='\033[0m' # No Color
+#post list of from to where connection will be checked.
+bastion_to_automate_port=9631
+bastion_to_elasticsearch_port=9631
+bastion_to_postgresql_port=9631
+elasticsearch_port=(9200 9631)
+postgresql_port=(7432 9631)
+elasticsearch_to_elasticsearch_port=(9300 9638 9631)
+postgres_to_postgres_port=(5432 9638 9631)
+postgres_to_elasticsearch_port=(9200 9638 9631)
+elasticsearch_to_postgres_port=(9638 9631)
+kibana=5601
+
+#Taking and filtering ssh_key from config.toml file
+SSH_KEY=$(grep -E '(^|\s)ssh_key_file($|\s)' $CONFIG | cut -c13- | sed 's/"//g' | sed 's/=//g')
+
+#Taking and filtering ssh_user from config.toml file
+SSH_USER=$(grep -E '(^|\s)ssh_user($|\s)' $CONFIG | cut -c10- | sed 's/"//g' | sed 's/=//g')
+
+#This will take ips from config.toml of server's and convert "[]" this array format to bash array formate like ().
+automate_server_private_ip=$(grep -E '(^|\s)automate_private_ips($|\s)' $CONFIG | cut -c24-)
+automate_server_private_ip=${automate_server_private_ip// /}
+automate_server_private_ip=${automate_server_private_ip//,/ }
+automate_server_private_ip=${automate_server_private_ip##[}
+automate_server_private_ip=${automate_server_private_ip%]}
+eval automate_server_private_ip=($automate_server_private_ip)
+
+chef_server_private_ip=$(grep -E '(^|\s)chef_server_private_ips($|\s)' $CONFIG | cut -c27-)
+chef_server_private_ip=${chef_server_private_ip// /}
+chef_server_private_ip=${chef_server_private_ip//,/ }
+chef_server_private_ip=${chef_server_private_ip##[}
+chef_server_private_ip=${chef_server_private_ip%]}
+eval chef_server_private_ip=($chef_server_private_ip)
+
+elasticsearch_private_ip=$(grep -E '(^|\s)elasticsearch_private_ips($|\s)' $CONFIG | cut -c29-)
+elasticsearch_private_ip=${elasticsearch_private_ip// /}
+elasticsearch_private_ip=${elasticsearch_private_ip//,/ }
+elasticsearch_private_ip=${elasticsearch_private_ip##[}
+elasticsearch_private_ip=${elasticsearch_private_ip%]}
+eval elasticsearch_private_ip=($elasticsearch_private_ip)
+
+postgresql_private_ip=$(grep -E '(^|\s)postgresql_private_ips($|\s)' $CONFIG | cut -c26-)
+postgresql_private_ip=${postgresql_private_ip// /}
+postgresql_private_ip=${postgresql_private_ip//,/ }
+postgresql_private_ip=${postgresql_private_ip##[}
+postgresql_private_ip=${postgresql_private_ip%]}
+eval postgresql_private_ip=($postgresql_private_ip)
+
+#This will install hab in bastion server to verify port
+if [ -z ${HAB_PATH} ] && [ -z ${NETCAT_PATH} ]; then
+	sudo wget -o /tmp/hab.logs https://packages.chef.io/files/stable/habitat/latest/hab-x86_64-linux.tar.gz -P /tmp/
+	sudo tar -xvzf /tmp/hab-x86_64-linux.tar.gz -C /usr/local/bin --strip-components 1 2>/dev/null
+	export HAB_LICENSE=accept-no-persist
+	hab pkg install core/netcat -bf
+	HAB_PATH=$(ls -dtr1 /tmp/hab-x86_64-* | tail -1)
+	NC_PKG=$(ls -dtr1 /hab/cache/artifacts/core-netcat-* | tail -1)
+
+else 
+	sudo tar -xvzf $HAB_PATH -C /usr/local/bin --strip-components 1 2>/dev/null
+	export HAB_LICENSE=accept-no-persist
+	hab pkg install $NETCAT_PATH
+fi
+
+# Below 5 no of loop will install hab utilty from remote server.
+
+for i in ${automate_server_private_ip[@]};
+do 
+		scp -o StrictHostKeyChecking=no -i $SSH_KEY $HAB_PATH $SSH_USER@$i:/tmp/hab-x86_64-linux.tar.gz 
+		scp -o StrictHostKeyChecking=no -i $SSH_KEY $NC_PKG $SSH_USER@$i:/tmp/ 
+		ssh -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+
+		sudo su -
+		tar -xvzf /tmp/hab-x86_64-linux.tar.gz -C /usr/local/bin --strip-components 1 2>/dev/null
+		export HAB_LICENSE=accept-no-persist
+		ls -dtr1 /tmp/core-netcat-* | tail -1 | xargs hab pkg install -bf
+			
+EOF
+done
+
+for i in ${chef_server_private_ip[@]};
+do 
+		
+		scp -o StrictHostKeyChecking=no -i $SSH_KEY $HAB_PATH $SSH_USER@$i:/tmp/hab-x86_64-linux.tar.gz 
+		scp -o StrictHostKeyChecking=no -i $SSH_KEY $NC_PKG $SSH_USER@$i:/tmp/  
+		ssh -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+
+		sudo su -
+		tar -xvzf /tmp/hab-x86_64-linux.tar.gz -C /usr/local/bin --strip-components 1 2>/dev/null
+		export HAB_LICENSE=accept-no-persist
+		ls -dtr1 /tmp/core-netcat-* | tail -1 | xargs hab pkg install -bf
+			
+EOF
+done
+
+for i in ${elasticsearch_private_ip[@]};
+do 
+		
+		
+		scp -o StrictHostKeyChecking=no -i $SSH_KEY $HAB_PATH $SSH_USER@$i:/tmp/hab-x86_64-linux.tar.gz 
+		scp -o StrictHostKeyChecking=no -i $SSH_KEY $NC_PKG $SSH_USER@$i:/tmp/  
+		ssh -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+
+		sudo su -
+		tar -xvzf /tmp/hab-x86_64-linux.tar.gz -C /usr/local/bin --strip-components 1 2>/dev/null
+		export HAB_LICENSE=accept-no-persist
+		ls -dtr1 /tmp/core-netcat-* | tail -1 | xargs hab pkg install -bf
+			
+EOF
+done
+
+for i in ${postgresql_private_ip[@]};
+do 
+		
+		
+		scp -o StrictHostKeyChecking=no -i $SSH_KEY $HAB_PATH $SSH_USER@$i:/tmp/hab-x86_64-linux.tar.gz 
+		scp -o StrictHostKeyChecking=no -i $SSH_KEY $NC_PKG $SSH_USER@$i:/tmp/  
+		ssh -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+
+		sudo su -
+		tar -xvzf /tmp/hab-x86_64-linux.tar.gz -C /usr/local/bin --strip-components 1 2>/dev/null
+		export HAB_LICENSE=accept-no-persist
+		ls -dtr1 /tmp/core-netcat-* | tail -1 | xargs hab pkg install -bf
+			
+EOF
+done
+
+# Below 8 no. of loop will kill the process that is running on specific port.
+
+for i in ${automate_server_private_ip[@]};
+do
+
+		ssh -o StrictHostKeyChecking=no  -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			ps aux | grep 'nc -l -p $bastion_to_automate_port' | awk {'print $2'} | xargs kill -9 > /dev/null 2>&1
+
+EOF
+
+done
+
+for i in ${chef_server_private_ip[@]};
+do
+
+		ssh -o StrictHostKeyChecking=no  -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			ps aux | grep 'nc -l -p $bastion_to_automate_port' | awk {'print $2'} | xargs kill -9 > /dev/null 2>&1
+
+EOF
+
+done	
+
+for i in ${elasticsearch_private_ip[@]};
+do
+
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+
+			ps aux | grep 'nc -l -p $bastion_to_elasticsearch_port' | awk {'print $2'} | xargs kill -9 > /dev/null 2>&1
+
+EOF
+
+done
+
+for i in ${elasticsearch_private_ip[@]};
+do
+	for j in ${elasticsearch_port[@]};
+	do
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+
+			ps aux | grep 'nc -l -p $j' | awk {'print $2'} | xargs kill -9 > /dev/null 2>&1
+
+EOF
+
+	done
+
+done
+
+for i in ${elasticsearch_private_ip[@]};
+do
+	for j in ${elasticsearch_to_elasticsearch_port[@]};
+	do
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+
+			ps aux | grep 'nc -l -p $j' | awk {'print $2'} | xargs kill -9 > /dev/null 2>&1
+
+EOF
+
+	done
+
+done
+
+for i in ${postgresql_private_ip[@]};
+do
+	for j in ${postgres_to_postgres_port[@]};
+	do
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+
+			ps aux | grep 'nc -l -p $j' | awk {'print $2'} | xargs kill -9 > /dev/null 2>&1
+
+EOF
+
+	done
+
+done
+
+for i in ${elasticsearch_private_ip[@]};
+do
+	for j in ${postgres_to_elasticsearch_port[@]};
+	do
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+
+			ps aux | grep 'nc -l -p $j' | awk {'print $2'} | xargs kill -9 > /dev/null 2>&1
+
+EOF
+
+	done
+
+done
+
+for i in ${postgresql_private_ip[@]};
+do
+	for j in ${elasticsearch_to_postgres_port[@]};
+	do
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+
+			ps aux | grep 'nc -l -p $j' | awk {'print $2'} | xargs kill -9 > /dev/null 2>&1
+
+EOF
+
+	done
+
+done
+
+for i in ${elasticsearch_private_ip[@]};
+do
+
+		ssh -o StrictHostKeyChecking=no  -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+
+			ps aux | grep 'nc -l -p $kibana' | awk {'print $2'} | xargs kill -9 > /dev/null 2>&1
+EOF
+
+done	
+
+echo
+echo -e "${PURPLE}Port checking process is started. It will take around 7-10 min. Please wait for some time.${NC}"
+echo 
+
+#connection will be checked from bastion(provisioning) to automate. Ensuring that automate allow bastion connection on 9631 port 
+for i in ${automate_server_private_ip[@]};
+do 
+		
+		ssh -o StrictHostKeyChecking=no  -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+		(ls /tmp | nc -l -p $bastion_to_automate_port &) | sleep 2 && exit
+
+EOF
+done
+
+for i in ${automate_server_private_ip[@]};
+do 
+			
+			rm output.txt > /dev/null 2>&1
+			nc -zv -w 4 $i $bastion_to_automate_port > output.txt 2>&1
+			VAR1=$( cat output.txt | grep -ow "Connection refused")
+			VAR2=$(cat output.txt | grep -ow "Connection timed out")
+			if [[ ("$VAR1" = "Connection refused") ]]; then
+				echo -e "${RED}CONNECTION REFUSED ERROR OCCURRED. BASTION IS NOT ABLE TO CONNECT AUTOMATE $i ON $bastion_to_automate_port PORT. PLEASE CHECK${NC}"
+				echo
+			elif [[ ("$VAR2" = "Connection timed out") ]]; then
+				echo -e "${RED}CONNECTION TIMED OUT ERROR OCCURRED. BASTION IS NOT ABLE TO CONNECT AUTOMATE $i INSTANCE WHILE MAKING CONNECTION ON $bastion_to_automate_port PORT. PLEASE CHECK${NC}"
+				echo				
+			fi
+			
+
+				
+done
+
+
+for i in ${automate_server_private_ip[@]};
+do 
+	
+		ssh -n -o StrictHostKeyChecking=no  -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			
+			ps aux | grep 'nc -l -p $bastion_to_automate_port' | awk {'print $2'} | xargs kill -9 1>/dev/null 2>&1
+EOF
+			
+done
+
+#connection will be checked from bastion(provisioning) to chef-server. Ensuring that automate allow bastion connection on 9631 port 
+for i in ${chef_server_private_ip[@]};
+do 
+		
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+		(ls /tmp | nc -l -p $bastion_to_automate_port &) | sleep 2 && exit
+
+EOF
+done
+
+for i in ${chef_server_private_ip[@]};
+do 
+			
+			rm output.txt > /dev/null 2>&1
+			nc -zv -w 4 $i $bastion_to_automate_port > output.txt 2>&1
+			VAR1=$( cat output.txt | grep -ow "Connection refused" )
+			VAR2=$(cat output.txt | grep -ow "Connection timed out")
+			if [[ ("$VAR1" = "Connection refused") ]]; then
+				echo -e "${RED}CONNECTION REFUSED ERROR OCCURRED. BASTION IS NOT ABLE TO CONNECT CHEF_SERVER $i ON $bastion_to_automate_port PORT. PLEASE CHECK${NC}"
+				echo
+			elif [[ ("$VAR2" = "Connection timed out") ]]; then
+				echo -e "${RED}CONNECTION TIMED OUT ERROR OCCURRED. BASTION IS NOT ABLE TO CONNECT CHEF_SERVER $i INSTANCE WHILE MAKING CONNECTION ON $bastion_to_automate_port PORT. PLEASE CHECK${NC}"
+				echo				
+			fi
+
+				
+done
+
+
+for i in ${chef_server_private_ip[@]};
+do 
+	
+		ssh -n -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			
+			ps aux | grep 'nc -l -p $bastion_to_automate_port' | awk {'print $2'} | xargs kill -9 1>/dev/null 2>&1
+EOF
+			
+done
+
+#connection will be checked from bastion(provisioning) to elasticsearch. Ensuring that automate allow bastion connection on 9631 port 
+for i in ${elasticsearch_private_ip[@]};
+do
+
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			(ls /tmp | nc -l -p $bastion_to_elasticsearch_port &) | sleep 2 && exit
+
+
+EOF
+				
+done
+
+
+for i in ${elasticsearch_private_ip[@]};
+do 
+
+			
+			rm output.txt > /dev/null 2>&1
+			nc -zv -w 4 $i $bastion_to_elasticsearch_port > output.txt 2>&1
+			VAR1=$( cat output.txt | grep -ow "Connection refused" )
+			VAR2=$(cat output.txt | grep -ow "Connection timed out")
+
+			if [[ ("$VAR1" = "Connection refused")]]; then
+				echo -e "${RED}CONNECTION REFUSED ERROR OCCURRED. BASTION IS NOT ABLE TO CONNECT ELASTICSEARCH $i ON $bastion_to_elasticsearch_port PORT. PLEASE CHECK${NC}"
+				echo
+			elif [[ ("$VAR2" = "Connection timed out") ]]; then
+				echo -e "${RED}CONNECTION TIMED OUT ERROR OCCURRED. BASTION IS NOT ABLE TO CONNECT ELASTICSEARCH $i INSTANCE WHILE MAKING CONNECTION ON $bastion_to_elasticsearch_port PORT. PLEASE CHECK${NC}"
+				echo				
+			fi
+
+			
+done
+
+
+for i in ${elasticsearch_private_ip[@]};
+do 
+	
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			
+			ps aux | grep 'nc -l -p $bastion_to_elasticsearch_port' | awk {'print $2'} | xargs kill -9 1>/dev/null 2>&1
+
+EOF
+	
+done
+
+#connection will be checked from bastion(provisioning) to postgresql. Ensuring that automate allow bastion connection on 9631 port 
+for i in ${postgresql_private_ip[@]};
+do
+
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			(ls /tmp | nc -l -p $bastion_to_elasticsearch_port &) | sleep 2 && exit
+
+
+EOF
+				
+done
+
+
+for i in ${postgresql_private_ip[@]};
+do 
+
+			
+			rm output.txt > /dev/null 2>&1
+			nc -zv -w 4 $i $bastion_to_postgresql_port > output.txt 2>&1
+			VAR1=$( cat output.txt | grep -ow "Connection refused" )
+			VAR2=$(cat output.txt | grep -ow "Connection timed out")
+			if [[ ("$VAR1" = "Connection refused") ]]; then
+				echo -e "${RED}BASTION IS NOT ABLE TO CONNECT POSTGRESQL $i on $bastion_to_postgresql_port PORT. PLEASE CHECK${NC}"
+				echo
+			elif [[ ("$VAR2" = "Connection timed out") ]]; then
+				echo -e "${RED}CONNECTION TIMED OUT ERROR OCCURRED. BASTION IS NOT ABLE TO CONNECT POSTGRESQL $i INSTANCE WHILE MAKING CONNECTION ON $bastion_to_postgresql_port PORT. PLEASE CHECK${NC}"
+				echo				
+			fi
+			rm output.txt > /dev/null 2>&1
+
+			
+done
+
+
+for i in ${postgresql_private_ip[@]};
+do 
+	
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			
+			ps aux | grep 'nc -l -p $bastion_to_postgresq_port' | awk {'print $2'} | xargs kill -9 1>/dev/null 2>&1
+
+EOF
+	
+done
+
+#bastion will try to check connections on 5601 on elasticsearch node that is for kibana.
+for i in ${elasticsearch_private_ip[@]};
+do 
+			
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			
+			(ls /tmp | nc -l -p $kibana &) | sleep 2 && exit
+						
+EOF
+				
+done
+
+for i in ${elasticsearch_private_ip[@]};
+do 
+			rm output.txt > /dev/null 2>&1
+			nc -zv -w 4 $i $kibana > output.txt 2>&1
+			VAR1=$( cat output.txt | grep -ow "Connection refused")
+			VAR2=$(cat output.txt | grep -ow "Connection timed out")
+			if [ "$VAR1" = "Connection refused" ]; then
+				echo -e "${RED}CONNECTION REFUSED ERROR OCCURRED. BASTION IS NOT ABLE TO CONNECT ELASTICSEARCH $i ON $kibana(KIBANA) PORT. PLEASE CHECK${NC}"
+			elif [[ ("$VAR2" = "Connection timed out") ]]; then
+				echo -e "${RED}CONNECTION TIMED OUT ERROR OCCURRED. BASTION IS NOT ABLE TO CONNECT ELASTICSEARCH $i INSTANCE WHILE MAKING CONNECTION ON $kibana(KIBANA) PORT. PLEASE CHECK${NC}"
+				echo				
+			fi
+			
+done
+
+
+for i in ${elasticsearch_private_ip[@]};
+do 
+	
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			
+			ps aux | grep 'nc -l -p $kibana' | awk {'print $2'} | xargs kill -9 1>/dev/null 2>&1
+
+EOF
+	
+done
+
+#automate-frontends -> elasticsearch-backends connections will be checked. First loop will send some packets on secific port.
+#Second loop will try to establish connection using nc from automate to elasticseach.
+#Third loop will kill the process that we have started in first loop.
+for i in ${elasticsearch_private_ip[@]};
+do 
+	for j in ${elasticsearch_port[@]}; 
+	do 
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			(ls /tmp | nc -l -p $j &) | sleep 2 && exit
+		
+		
+EOF
+
+	done
+		
+done
+
+for i in ${automate_server_private_ip[@]};
+do 
+	for j in ${elasticsearch_port[@]}; 
+	do 
+		for k in ${elasticsearch_private_ip[@]};
+		do
+			ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash > output.txt 2>/dev/null << EOF
+				nc -zv -w 4 $k $j 2>&1
+
+
+
+EOF
+		VAR1=$(cat output.txt | grep -ow "Connection refused")
+		VAR2=$(cat output.txt | grep -ow "Connection timed out")
+		if [ "$VAR1" = "Connection refused" ]; then
+			echo -e "${RED}CONNECTION REFUSED ERROR OCCURRED. AUTOMATE $i IS NOT ABLE TO CONNECT ELASTICSEARCH $k  ON $j PORT. PLEASE CHECK${NC}"
+			echo
+		elif [ "$VAR2" = "Connection timed out" ]; then
+			echo -e "${RED}CONNECTION TIMED OUT ERROR OCCURRED. AUTOMATE $i IS NOT ABLE TO CONNECT ELASTICSEARCH $k INSTANCE WHILE MAKING CONNECTION ON $j PORT. PLEASE CHECK${NC}"
+			echo				
+		fi
+		rm output.txt > /dev/null 2>&1
+		done
+	done
+			
+done
+
+for i in ${elasticsearch_private_ip[@]};
+do 
+	for j in ${elasticsearch_port[@]}; 
+	do 
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			
+			ps aux | grep 'nc -l -p $j' | awk {'print $2'} | xargs kill -9 1>/dev/null 2>&1
+
+EOF
+	
+	done
+			
+done
+
+#chef-server-frontends -> elasticsearch-backends connections will be checked. First loop will send some packets on secific port.
+#Second loop will try to establish connection using nc from automate to elasticseach.
+#Third loop will kill the process that we have started in first loop.
+for i in ${elasticsearch_private_ip[@]};
+do 
+	for j in ${elasticsearch_port[@]}; 
+	do 
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			(ls /tmp | nc -l -p $j &) | sleep 2 && exit
+			
+			
+EOF
+	
+	done
+			
+done
+
+for i in ${chef_server_private_ip[@]};
+do 
+	for j in ${elasticsearch_port[@]}; 
+	do 
+		for k in ${elasticsearch_private_ip[@]};
+		do
+
+			ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash > output.txt 2>/dev/null << EOF
+				nc -zv -w 4 $k $j 2>&1
+
+
+EOF
+		VAR1=$(cat output.txt | grep -ow "Connection refused")
+		VAR2=$(cat output.txt | grep -ow "Connection timed out")
+		if [ "$VAR1" = "Connection refused" ]; then
+			echo -e "${RED}CONNECTION REFUSED ERROR OCCURRED. CHEF-SERVER $i IS NOT ABLE TO CONNECT ELASTICSEARCH $k  ON $j PORT. PLEASE CHECK${NC}"
+			echo
+		elif [ "$VAR2" = "Connection timed out" ]; then
+			echo -e "${RED}CONNECTION TIMED OUT ERROR OCCURRED. CHEF-SERVER $i IS NOT ABLE TO CONNECT ELASTICSEARCH $k INSTANCE WHILE MAKING CONNECTION ON $j PORT. PLEASE CHECK${NC}"
+			echo				
+		fi
+		rm output.txt > /dev/null 2>&1
+		done
+	done
+			
+done
+
+for i in ${elasticsearch_private_ip[@]};
+do 
+	for j in ${elasticsearch_port[@]}; 
+	do 
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			
+			ps aux | grep 'nc -l -p $j' | awk {'print $2'} | xargs kill -9 1>/dev/null 2>&1
+
+EOF
+	
+	done
+			
+done
+
+#chef-server-frontends -> postgres-backends connections will be checked. First loop will send some packets on secific port.
+#Second loop will try to establish connection using nc from automate to elasticseach.
+#Third loop will kill the process that we have started in first loop.
+for i in ${postgresql_private_ip[@]};
+do 
+	for j in ${postgresql_port[@]}; 
+	do 
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			(ls /tmp | nc -l -p $j &) | sleep 2 && exit
+			
+			
+EOF
+	
+	done
+			
+done
+
+for i in ${chef_server_private_ip[@]};
+do 
+	for j in ${postgresql_port[@]}; 
+	do 
+		for k in ${postgresql_private_ip[@]};
+		do
+
+			ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash > output.txt 2>/dev/null << EOF
+
+				nc -zv -w 4 $k $j 2>&1
+
+EOF
+		VAR1=$(cat output.txt | grep -ow "Connection refused")
+		VAR2=$(cat output.txt | grep -ow "Connection timed out")
+		if [ "$VAR1" = "Connection refused" ]; then
+			echo -e "${RED}CONNECTION REFUSED ERROR OCCURRED. CHEF-SERVER $i IS NOT ABLE TO CONNECT POSTGRESQL $k ON $j PORT. PLEASE CHECK${NC}"
+		elif [ "$VAR2" = "Connection timed out" ]; then
+			echo -e "${RED}CONNECTION TIMED OUT ERROR OCCURRED. CHEF-SERVER $i IS NOT ABLE TO CONNECT POSTGRESQL $k INSTANCE WHILE MAKING CONNECTION ON $j PORT. PLEASE CHECK${NC}"
+			echo				
+		fi
+		rm output.txt > /dev/null 2>&1
+		done
+	done
+			
+done
+
+for i in ${postgresql_private_ip[@]};
+do 
+	for j in ${postgresql_port[@]}; 
+	do 
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			
+			ps aux | grep 'nc -l -p $j' | awk {'print $2'} | xargs kill -9 1>/dev/null 2>&1
+
+EOF
+	
+	done
+			
+done
+
+#automate-frontends -> postgres-backends connections will be checked. First loop will send some packets on secific port.
+#Second loop will try to establish connection using nc from automate to elasticseach.
+#Third loop will kill the process that we have started in first loop.
+for i in ${postgresql_private_ip[@]};
+do 
+	for j in ${postgresql_port[@]}; 
+	do 
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			(ls /tmp | nc -l -p $j &) | sleep 2 && exit
+			
+			
+EOF
+	
+	done
+			
+done
+
+for i in ${automate_server_private_ip[@]};
+do 
+	for j in ${postgresql_port[@]}; 
+	do 
+		for k in ${postgresql_private_ip[@]};
+		do
+			ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash > output.txt 2>/dev/null << EOF
+
+				nc -zv -w 4 $k $j 2>&1
+
+EOF
+		VAR1=$( cat output.txt | grep -ow "Connection refused" )
+		VAR2=$(cat output.txt | grep -ow "Connection timed out")
+		if [ "$VAR1" = "Connection refused" ]; then
+			echo -e "${RED}CONNECTION REFUSED ERROR OCCURRED. AUTOMATE $i IS NOT ABLE TO CONNECT POSTGRESQL $k ON $j PORT. PLEASE CHECK${NC}"
+		elif [ "$VAR2" = "Connection timed out" ]; then
+			echo -e "${RED}CONNECTION TIMED OUT ERROR OCCURRED. AUTOMATE $i IS NOT ABLE TO CONNECT POSTGRESQL $k INSTANCE WHILE MAKING CONNECTION ON $j PORT. PLEASE CHECK${NC}"
+			echo				
+		fi
+		rm output.txt > /dev/null 2>&1
+		done
+	done
+			
+done
+
+for i in ${postgresql_private_ip[@]};
+do 
+	for j in ${postgresql_port[@]}; 
+	do 
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			
+			ps aux | grep 'nc -l -p $j' | awk {'print $2'} | xargs kill -9 1>/dev/null 2>&1
+
+EOF
+	
+	done
+			
+done
+
+#elasticsearch-backends -> elasticsearch-backends connections will be checked. First loop will send some packets on secific port.
+#Second loop will try to establish connection using nc from every elasticseach to elasticseach.
+#Third loop will kill the process that we have started in first loop.
+for i in ${elasticsearch_private_ip[@]};
+do 
+	for j in ${elasticsearch_to_elasticsearch_port[@]}; 
+	do 
+		echo "elasticsearch $i" >> output.txt
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			(ls /tmp | nc -l -p $j &) | sleep 2 && exit
+			
+			
+EOF
+	
+	done
+			
+done
+
+for i in ${elasticsearch_private_ip[@]};
+do 
+	for j in ${elasticsearch_to_elasticsearch_port[@]}; 
+	do 
+		for k in ${elasticsearch_private_ip[@]};
+		do
+
+			ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash > output.txt 2>/dev/null << EOF
+				nc -zv -w 4 $k $j 2>&1
+								
+EOF
+		VAR1=$(cat output.txt | grep -ow "Connection refused")
+		VAR2=$(cat output.txt | grep -ow "Connection timed out")
+		if [ "$VAR1" = "Connection refused" ]; then
+			echo -e "${RED}CONNECTION REFUSED ERROR OCCURRED. ELASTICSEARCH $i IS NOT ABLE TO CONNECT ELASTICSEARCH $k ON $j PORT. PLEASE CHECK${NC}"
+			echo
+		elif [ "$VAR2" = "Connection timed out" ]; then
+			echo -e "${RED}CONNECTION TIMED OUT ERROR OCCURRED. ELASTICSEARCH $i IS NOT ABLE TO CONNECT ELASTICSEARCH $k INSTANCE WHILE MAKING CONNECTION ON $j PORT. PLEASE CHECK${NC}"
+			echo				
+		fi
+		rm output.txt > /dev/null 2>&1
+
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$k /bin/bash << EOF
+
+			(ls /tmp | nc -l -p $j &) | sleep 2 && exit
+
+
+EOF
+		done
+	done
+			
+done
+
+for i in ${elasticsearch_private_ip[@]};
+do 
+	for j in ${elasticsearch_to_elasticsearch_port[@]}; 
+	do 
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			
+			ps aux | grep 'nc -l -p $j' | awk {'print $2'} | xargs kill -9 1>/dev/null 2>&1
+
+EOF
+	
+	done
+			
+done
+
+#postgres-backends -> postgres-backends connections will be checked. First loop will send some packets on secific port.
+#Second loop will try to establish connection using nc from every postgres to postgres node.
+#Third loop will kill the process that we have started in first loop.
+for i in ${postgresql_private_ip[@]};
+do 
+	for j in ${postgres_to_postgres_port[@]}; 
+	do 
+		
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			
+			(ls /tmp | nc -l -p $j &) | sleep 2 && exit
+			
+			
+EOF
+	
+	done
+			
+done
+
+for i in ${postgresql_private_ip[@]};
+do 
+	for j in ${postgres_to_postgres_port[@]}; 
+	do 
+		for k in ${postgresql_private_ip[@]};
+		do
+
+			ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash > output.txt 2>/dev/null  << EOF
+
+				nc -zv -w 4 $k $j 2>&1
+		
+EOF
+		VAR1=$(cat output.txt | grep -ow "Connection refused")
+		VAR2=$(cat output.txt | grep -ow "Connection timed out")
+		if [ "$VAR1" = "Connection refused" ]; then
+			echo -e "${RED}CONNECTION REFUSED ERROR OCCURRED. POSTGRESQL $i IS NOT ABLE TO CONNECT POSTGRESQL $k ON $j PORT. PLEASE CHECK${NC}"
+			echo
+		elif [ "$VAR2" = "Connection timed out" ]; then
+			echo -e "${RED}CONNECTION TIMED OUT ERROR OCCURRED. POSTGRESQL $i IS NOT ABLE TO CONNECT POSTGRESQL $k INSTANCE WHILE MAKING CONNECTION ON $j PORT. PLEASE CHECK${NC}"
+			echo				
+		fi
+		rm output.txt > /dev/null 2>&1
+
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$k /bin/bash << EOF
+
+			(ls /tmp | nc -l -p $j &) | sleep 2 && exit
+
+
+EOF
+
+		done
+	done
+			
+done
+
+for i in ${postgresql_private_ip[@]};
+do 
+	for j in ${postgres_to_postgres_port[@]}; 
+	do 
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			
+			ps aux | grep 'nc -l -p $j' | awk {'print $2'} | xargs kill -9 1>/dev/null 2>&1
+
+EOF
+	
+	done
+			
+done
+
+
+#postgres-backends -> elasticsearch-backends connections will be checked. First loop will send some packets on secific port.
+#Second loop will try to establish connection using nc from every postgres to elasticsearch node.
+#Third loop will kill the process that we have started in first loop.
+for i in ${elasticsearch_private_ip[@]};
+do 
+	for j in ${postgres_to_elasticsearch_port[@]}; 
+	do 
+		
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			
+			(ls /tmp | nc -l -p $j &) | sleep 2 && exit
+			
+			
+EOF
+	
+	done
+			
+done
+
+for i in ${postgresql_private_ip[@]};
+do 
+	for j in ${postgres_to_elasticsearch_port[@]}; 
+	do 
+		for k in ${elasticsearch_private_ip[@]};
+		do
+
+			ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash > output.txt 2>/dev/null  << EOF
+		
+				nc -zv -w 4 $k $j 2>&1
+			
+EOF
+		VAR1=$(cat output.txt | grep -ow "Connection refused")
+		VAR2=$(cat output.txt | grep -ow "Connection timed out")
+		if [ "$VAR1" = "Connection refused" ]; then
+			echo -e "${RED}CONNECTION REFUSED ERROR OCCURRED. POSTGRESQL $i IS NOT ABLE TO CONNECT ELASTICSEARCH $k ON $j PORT. PLEASE CHECK${NC}"
+		elif [ "$VAR2" = "Connection timed out" ]; then
+			echo -e "${RED}CONNECTION TIMED OUT ERROR OCCURRED. POSTGRESQL $i IS NOT ABLE TO CONNECT ELASTICSEARCH $k INSTANCE WHILE MAKING CONNECTION ON $j PORT. PLEASE CHECK${NC}"
+			echo				
+		fi
+		rm output.txt > /dev/null 2>&1
+
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$k /bin/bash << EOF
+
+			(ls /tmp | nc -l -p $j &) | sleep 2 && exit
+
+
+EOF
+
+		done
+	done
+			
+done
+
+for i in ${elasticsearch_private_ip[@]};
+do 
+	for j in ${postgres_to_elasticsearch_port[@]}; 
+	do 
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			
+			ps aux | grep 'nc -l -p $j' | awk {'print $2'} | xargs kill -9 1>/dev/null 2>&1
+
+EOF
+	
+	done
+			
+done
+
+#elasticsearch-backends -> postgresql-backends connections will be checked. First loop will send some packets on secific port.
+#Second loop will try to establish connection using nc from every postgres to elasticsearch node.
+#Third loop will kill the process that we have started in first loop.
+for i in ${postgresql_private_ip[@]};
+do 
+	for j in ${elasticsearch_to_postgres_port[@]}; 
+	do 
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			
+			(ls /tmp | nc -l -p $j &) | sleep 2 && exit
+			
+			
+EOF
+	
+	done
+			
+done
+
+for i in ${elasticsearch_private_ip[@]};
+do 
+	for j in ${elasticsearch_to_postgres_port[@]}; 
+	do 
+		for k in ${postgresql_private_ip[@]};
+		do
+
+			ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash > output.txt 2>/dev/null  << EOF
+
+				nc -zv -w 4 $k $j 2>&1
+
+EOF
+
+		VAR1=$(cat output.txt | grep -ow "Connection refused")
+		VAR2=$(cat output.txt | grep -ow "Connection timed out")
+		if [ "$VAR1" = "Connection refused" ]; then
+			echo -e "${RED}CONNECTION REFUSED ERROR OCCURRED. ELASTICSEARCH $i IS NOT ABLE TO CONNECT POSTGRESQL $k ON $j PORT. PLEASE CHECK${NC}"
+		elif [ "$VAR2" = "Connection timed out" ]; then
+			echo -e "${RED}CONNECTION TIMED OUT ERROR OCCURRED. ELASTICSEARCH $i IS NOT ABLE TO CONNECT POSTGRESQL $k INSTANCE WHILE MAKING CONNECTION ON $j PORT. PLEASE CHECK${NC}"
+			echo				
+		fi
+		rm output.txt > /dev/null 2>&1
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$k /bin/bash << EOF
+
+			(ls /tmp | nc -l -p $j &) | sleep 2 && exit
+
+
+EOF
+
+		done
+	done
+			
+done
+
+for i in ${postgresql_private_ip[@]};
+do 
+	for j in ${elasticsearch_to_postgres_port[@]}; 
+	do 
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash << EOF
+			
+			ps aux | grep 'nc -l -p $j' | awk {'print $2'} | xargs kill -9 1>/dev/null 2>&1
+
+EOF
+	
+	done
+			
+done
+
+echo
+echo -e "${PURPLE}Thank you for your patience. Port checking process is completed If you see error above in red color then please check that port and make connection open for the specific scenario. If you don't see the error then you can start the deployment ${NC}"
+echo
+
+# Below 4 no of loop will remobe hab utilty from remote server.
+for i in ${automate_server_private_ip[@]};
+do 
+		
+		
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash >> output.txt << EOF
+			sudo rm -rf /hab
+			
+EOF
+done
+
+for i in ${chef_server_private_ip[@]};
+do 
+		
+		
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash >> output.txt << EOF
+			sudo rm -rf /hab
+			
+EOF
+done
+
+for i in ${elasticsearch_private_ip[@]};
+do 
+		
+		
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash >> output.txt << EOF
+			sudo rm -rf /hab
+			
+EOF
+done
+
+for i in ${postgresql_private_ip[@]};
+do 
+		
+		
+		ssh -o StrictHostKeyChecking=no -i $SSH_KEY $SSH_USER@$i /bin/bash >> output.txt << EOF
+			sudo rm -rf /hab
+			
+EOF
+done
+
+sudo rm -rf /hab
+`
diff --git a/components/automate-cli/cmd/chef-automate/license.go b/components/automate-cli/cmd/chef-automate/license.go
index 59b57c4bb..8fdf395e1 100644
--- a/components/automate-cli/cmd/chef-automate/license.go
+++ b/components/automate-cli/cmd/chef-automate/license.go
@@ -81,6 +81,30 @@ var nodeUsageCommand = &cobra.Command{
 	},
 }
 
+var complianceUniqueNodeCounterCmd = &cobra.Command{
+	Use:   "complianceUniqNodeRunReport",
+	Short: "Generates the unique count of reported Compliance scans on hourly basis between the time duration",
+	RunE: func(cmd *cobra.Command, args []string) error {
+		validateArgs()
+		startTime, _ := convertStringToTime(CommandFlags.StartTime)
+		endTime, _ := convertStringToTime(CommandFlags.EndTime)
+		generator.GenerateComplianceNodeCount(CommandFlags.ESHostname, CommandFlags.ESPort, startTime, endTime)
+		return nil
+	},
+}
+
+var complianceNodeUsageCmd = &cobra.Command{
+	Use:   "complianceNodeRunReport",
+	Short: "Generates daily Infra Client Run reports for a span of time duration",
+	RunE: func(cmd *cobra.Command, args []string) error {
+		validateArgs()
+		startTime, _ := convertStringToTime(CommandFlags.StartTime)
+		endTime, _ := convertStringToTime(CommandFlags.EndTime)
+		generator.GenerateComplianceNodeRunReport(CommandFlags.ESHostname, CommandFlags.ESPort, startTime, endTime)
+		return nil
+	},
+}
+
 var noLicenseAppliedMsg = `
 A license has not been associated with this installation of Chef
 Automate. Please contact sales@chef.io to request a Chef Automate
@@ -369,6 +393,8 @@ func init() {
 	licenseCmd.AddCommand(licenseUsageCmd)
 	licenseCmd.AddCommand(uniqueNodeCounterCmd)
 	licenseCmd.AddCommand(nodeUsageCommand)
+	licenseCmd.AddCommand(complianceUniqueNodeCounterCmd)
+	licenseCmd.AddCommand(complianceNodeUsageCmd)
 	uniqueNodeCounterCmd.Flags().StringVarP(&CommandFlags.StartTime, "start_time", "s", "", "start time of the report in yyyy-mm-dd format")
 	uniqueNodeCounterCmd.Flags().StringVarP(&CommandFlags.EndTime, "end_time", "e", "", "end time of the report in yyyy-mm-dd format")
 	uniqueNodeCounterCmd.Flags().StringVarP(&CommandFlags.ESHostname, "es_hostname", "n", "localhost", "hostname of the ES host")
@@ -377,5 +403,13 @@ func init() {
 	nodeUsageCommand.Flags().StringVarP(&CommandFlags.EndTime, "end_time", "e", "", "end time of the report in yyyy-mm-dd format")
 	nodeUsageCommand.Flags().StringVarP(&CommandFlags.ESHostname, "es_hostname", "n", "localhost", "hostname of the ES host")
 	nodeUsageCommand.Flags().StringVarP(&CommandFlags.ESPort, "es_port", "p", "10141", "port of the ES host")
+	complianceUniqueNodeCounterCmd.Flags().StringVarP(&CommandFlags.StartTime, "start_time", "s", "", "start time of the report in yyyy-mm-dd format")
+	complianceUniqueNodeCounterCmd.Flags().StringVarP(&CommandFlags.EndTime, "end_time", "e", "", "end time of the report in yyyy-mm-dd format")
+	complianceUniqueNodeCounterCmd.Flags().StringVarP(&CommandFlags.ESHostname, "es_hostname", "n", "localhost", "hostname of the ES host")
+	complianceUniqueNodeCounterCmd.Flags().StringVarP(&CommandFlags.ESPort, "es_port", "p", "10141", "port of the ES host")
+	complianceNodeUsageCmd.Flags().StringVarP(&CommandFlags.StartTime, "start_time", "s", "", "start time of the report in yyyy-mm-dd format")
+	complianceNodeUsageCmd.Flags().StringVarP(&CommandFlags.EndTime, "end_time", "e", "", "end time of the report in yyyy-mm-dd format")
+	complianceNodeUsageCmd.Flags().StringVarP(&CommandFlags.ESHostname, "es_hostname", "n", "localhost", "hostname of the ES host")
+	complianceNodeUsageCmd.Flags().StringVarP(&CommandFlags.ESPort, "es_port", "p", "10141", "port of the ES host")
 	licenseApplyCmd.Flags().BoolVarP(&licenseCmdFlags.forceSet, "force", "f", false, "Force set license")
 }
diff --git a/components/automate-cli/cmd/chef-automate/provisionInfraHa.go b/components/automate-cli/cmd/chef-automate/provisionInfraHa.go
index 22d964089..3c9344dfc 100644
--- a/components/automate-cli/cmd/chef-automate/provisionInfraHa.go
+++ b/components/automate-cli/cmd/chef-automate/provisionInfraHa.go
@@ -19,6 +19,11 @@ func newProvisionInfraCmd() *cobra.Command {
 		"channel",
 		"",
 		"Release channel to deploy all services from")
+	provisionInfraCmd.PersistentFlags().StringVar(
+		&deployCmdFlags.airgap,
+		"airgap-bundle",
+		"",
+		"Path to an airgap install bundle")
 
 	return provisionInfraCmd
 }
diff --git a/components/automate-cli/cmd/chef-automate/provisionInfraHaTmpl.go b/components/automate-cli/cmd/chef-automate/provisionInfraHaTmpl.go
index a737d3686..8ba95c9b0 100644
--- a/components/automate-cli/cmd/chef-automate/provisionInfraHaTmpl.go
+++ b/components/automate-cli/cmd/chef-automate/provisionInfraHaTmpl.go
@@ -11,6 +11,8 @@ config file path:
 	config toml file path
 
 Options:
+    -c, --channel                    Provision infrastructure using particular channel, default channel is current
+    -p  --airgap-bundle              Provision using airgap bundle
     -o, --okta-aws                   Use okta_aws cli to authenticate with AWS
     -p, --parallelism WORKERS        Number of workers used for the terraform apply (default: 50)
     -d, --force-deploy               Force terraform deploy even if no changes have been detected. This is used when needing to update the tfstate
diff --git a/components/automate-cli/cmd/chef-automate/sshAutomateHA.go b/components/automate-cli/cmd/chef-automate/sshAutomateHA.go
index 75d7ae401..b0ce5862e 100644
--- a/components/automate-cli/cmd/chef-automate/sshAutomateHA.go
+++ b/components/automate-cli/cmd/chef-automate/sshAutomateHA.go
@@ -214,7 +214,7 @@ func runSshCommand(cmd *cobra.Command, args []string) error {
 	}
 	var sshString string = sshStrings[i-1]
 	sshTokens := strings.Split(sshString, " ")
-	return executeShellCommand(sshTokens[0], sshTokens[1:])
+	return executeShellCommand(sshTokens[0], sshTokens[1:], "")
 }
 
 func getAutomateHAInfraDetails() (*AutomteHAInfraDetails, error) {
diff --git a/components/automate-cli/pkg/reportgenerator/usagegenerator.go b/components/automate-cli/pkg/reportgenerator/usagegenerator.go
index b5599b7d6..007903068 100644
--- a/components/automate-cli/pkg/reportgenerator/usagegenerator.go
+++ b/components/automate-cli/pkg/reportgenerator/usagegenerator.go
@@ -4,10 +4,12 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"github.com/gocarina/gocsv"
-	elastic "gopkg.in/olivere/elastic.v6"
 	"os"
+	"strconv"
 	"time"
+
+	"github.com/gocarina/gocsv"
+	elastic "gopkg.in/olivere/elastic.v6"
 )
 
 type ConvergeInfo struct {
@@ -28,6 +30,8 @@ type ConvergeInfo struct {
 	TotalResourceCount int    `json:"total_resource_count"`
 }
 
+type ComplianceInfo interface{}
+
 func GenerateNodeCount(esHostName string, esPort string, startTime time.Time, endTime time.Time) {
 	elasticSearchURL := fmt.Sprintf("http://%s:%s", esHostName, esPort)
 	client, err := elastic.NewClient(
@@ -65,7 +69,6 @@ func queryElasticSearchNodeCount(client *elastic.Client, startTime time.Time, en
 	}
 	defer f.Close()
 	writer := gocsv.DefaultCSVWriter(f)
-	//writer := struct2csv.NewWriter(f)
 	err = writer.Write([]string{"Start Time", "End Time", "Unique Node Count"})
 	if err != nil {
 		fmt.Println("Error while writing csv: ", err)
@@ -158,6 +161,10 @@ func getUniqueCounts(client *elastic.Client, startTime time.Time, endTime time.T
 	return metric, ok
 }
 
+func get(key string, s interface{}) interface{} {
+	return s.(map[string]interface{})[key]
+}
+
 func queryElasticSearchNodeReport(client *elastic.Client, startTime time.Time, endTime time.Time) {
 	filename := fmt.Sprintf("nodeinfo_%s_%s.csv", startTime.Format("2006-01-02"), endTime.Format("2006-01-02"))
 	f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
@@ -167,7 +174,6 @@ func queryElasticSearchNodeReport(client *elastic.Client, startTime time.Time, e
 	}
 	defer f.Close()
 
-	//	writer := struct2csv.NewWriter(f)
 	writer := gocsv.DefaultCSVWriter(f)
 
 	var sourceFields = []string{
@@ -189,7 +195,6 @@ func queryElasticSearchNodeReport(client *elastic.Client, startTime time.Time, e
 	}
 
 	t := endTime.Add(24 * time.Hour)
-	//t := endTime.Add(-time.Hour * 24)
 	if t.Before(startTime) {
 		t = startTime
 	}
@@ -251,3 +256,245 @@ func queryElasticSearchNodeReport(client *elastic.Client, startTime time.Time, e
 	}
 	fmt.Println("The details of the runs can be found in : ", filename)
 }
+
+func GenerateComplianceNodeCount(esHostName string, esPort string, startTime time.Time, endTime time.Time) {
+	elasticSearchURL := fmt.Sprintf("http://%s:%s", esHostName, esPort)
+	client, err := elastic.NewClient(
+		elastic.SetURL(elasticSearchURL),
+		elastic.SetSniff(false),
+	)
+	if err != nil {
+		fmt.Println("Elastic error : ", err)
+		os.Exit(1)
+	}
+
+	queryElasticSearchComplianceNodeCount(client, startTime, endTime)
+}
+
+func GenerateComplianceNodeRunReport(esHostName string, esPort string, startTime time.Time, endTime time.Time) {
+	elasticSearchURL := fmt.Sprintf("http://%s:%s", esHostName, esPort)
+	client, err := elastic.NewClient(
+		elastic.SetURL(elasticSearchURL),
+		elastic.SetSniff(false),
+	)
+	if err != nil {
+		fmt.Println("Elastic error : ", err)
+		os.Exit(1)
+	}
+
+	queryElasticSearchComplianceNodeReport(client, startTime, endTime)
+}
+
+func queryElasticSearchComplianceNodeCount(client *elastic.Client, startTime time.Time, endTime time.Time) {
+	filename := fmt.Sprintf("compliancenodecount_%s_%s.csv", startTime.Format("2006-01-02"), endTime.Format("2006-01-02"))
+	f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
+	if err != nil {
+		fmt.Println("Failed to open Node Count report")
+		os.Exit(1)
+	}
+	defer f.Close()
+	writer := gocsv.DefaultCSVWriter(f)
+	err = writer.Write([]string{"Start Time", "End Time", "Unique Node Count"})
+	if err != nil {
+		fmt.Println("Error while writing csv: ", err)
+		os.Exit(1)
+	}
+
+	st := endTime.Add(24 * time.Hour)
+	for {
+		et := st
+		st = et.Add(24 * -time.Hour)
+
+		err = writer.Write([]string{"Day Count"})
+		if err != nil {
+			fmt.Println("Error while writing csv: ", err)
+			os.Exit(1)
+		}
+		dayCount, ok := getUniqueComplianceCounts(client, st, et)
+		if ok {
+			err = writer.Write([]string{st.Format(time.RFC3339), et.Format(time.RFC3339), fmt.Sprintf("%f", *dayCount.Value)})
+			if err != nil {
+				fmt.Println("Error while writing csv: ", err)
+				os.Exit(1)
+			}
+		}
+		t := et.Add(-time.Hour)
+		if t.Before(st) {
+			t = st
+		}
+		err = writer.Write([]string{"Hourly Count"})
+		if err != nil {
+			fmt.Println("Error while writing csv: ", err)
+			os.Exit(1)
+		}
+		writer.Flush()
+		for {
+			metric, ok := getUniqueComplianceCounts(client, t, et)
+			if ok && *metric.Value > 0 {
+				err = writer.Write([]string{t.Format(time.RFC3339), et.Format(time.RFC3339), fmt.Sprintf("%f", *metric.Value)})
+				if err != nil {
+					fmt.Println("Error while writing csv: ", err)
+					os.Exit(1)
+				}
+				writer.Flush()
+			}
+
+			if t == st {
+				break
+			}
+
+			et = t
+			t = et.Add(-time.Hour)
+			if t.Before(st) {
+				t = st
+			}
+		}
+		err = writer.Write([]string{"", "", ""})
+		if err != nil {
+			fmt.Println("Error while writing csv: ", err)
+			os.Exit(1)
+		}
+		writer.Flush()
+		if st == startTime {
+			break
+		}
+	}
+	writer.Flush()
+	fmt.Println("The node count report is available in: ", filename)
+}
+
+func getUniqueComplianceCounts(client *elastic.Client, startTime time.Time, endTime time.Time) (*elastic.AggregationValueMetric, bool) {
+	rangeQuery := elastic.NewRangeQuery("end_time").
+		Format("yyyy-MM-dd||yyyy-MM-dd-HH:mm:ss||yyyy-MM-dd'T'HH:mm:ssZ")
+	rangeQuery.Gte(startTime)
+	rangeQuery.Lte(endTime)
+
+	aggr := elastic.NewCardinalityAggregation().Field("node_uuid")
+	searchService := client.Search().
+		Query(rangeQuery).
+		Index("comp-7-r-*").
+		Size(100).
+		Aggregation("nodes_count", aggr)
+
+	searchResult, err := searchService.Do(context.Background())
+	if err != nil {
+		fmt.Println("Error in query: ", err)
+		os.Exit(1)
+	}
+
+	metric, ok := searchResult.Aggregations.ValueCount("nodes_count")
+	return metric, ok
+}
+
+func queryElasticSearchComplianceNodeReport(client *elastic.Client, startTime time.Time, endTime time.Time) {
+	filename := fmt.Sprintf("complianceinfo_%s_%s.csv", startTime.Format("2006-01-02"), endTime.Format("2006-01-02"))
+	f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
+	if err != nil {
+		fmt.Println("Failed to open Compliance scan detail report")
+		os.Exit(1)
+	}
+	defer f.Close()
+
+	w := gocsv.DefaultCSVWriter(f)
+
+	var sourceField = []string{
+		"node_uuid",
+		"version",
+		"node_name",
+		"environment",
+		"platform.name",
+		"controls_sums.total",
+		"controls_sums.passed.total",
+		"controls_sums.skipped.total",
+		"controls_sums.failed.total",
+		"profiles.name",
+		"profiles.title",
+		"profiles.version",
+		"end_time",
+	}
+
+	t := endTime.Add(24 * time.Hour)
+
+	if t.Before(startTime) {
+		t = startTime
+	}
+	header := true
+	for {
+		rangeQuery := elastic.NewRangeQuery("end_time").
+			Format("yyyy-MM-dd||yyyy-MM-dd-HH:mm:ss||yyyy-MM-dd'T'HH:mm:ssZ")
+		rangeQuery.Gte(t)
+		rangeQuery.Lte(endTime)
+
+		fetchSource := elastic.NewFetchSourceContext(true).Include(sourceField...)
+
+		searchService := client.Search().
+			Query(rangeQuery).
+			Index("comp-7-r-*").
+			Size(10000).
+			FetchSourceContext(fetchSource)
+
+		searchResult, err := searchService.Do(context.Background())
+		if err != nil {
+			fmt.Println("Error in query: ", err)
+			os.Exit(1)
+		}
+
+		if searchResult.Hits.TotalHits > 0 {
+			if header == true {
+				headers := []string{"Node_ID", "Version", "Node_Name", "Environment", "End_Time", "Platform__Name", "Controls_Sums__Total", "Controls_Sums__Passed", "Controls_Sums__Skipped", "Controls_Sums__Failed", "Profiles__Count"}
+				if err := w.Write(headers); err != nil {
+					fmt.Println("Error while writing csv: ", err)
+				}
+				w.Flush()
+				if err := w.Error(); err != nil {
+					fmt.Println("Error while writing csv: ", err)
+				}
+			}
+
+			var s ComplianceInfo
+
+			for _, hit := range searchResult.Hits.Hits {
+
+				err = json.Unmarshal(*hit.Source, &s)
+				if err != nil {
+					fmt.Println("Json marshal err :", err)
+					os.Exit(1)
+				}
+
+				record := []string{
+					get("node_uuid", s).(string),
+					get("version", s).(string),
+					get("node_name", s).(string),
+					get("environment", s).(string),
+					get("end_time", s).(string),
+					get("name", get("platform", s)).(string),
+					fmt.Sprint(get("total", get("controls_sums", s))),
+					fmt.Sprint(get("total", get("passed", get("controls_sums", s)))),
+					fmt.Sprint(get("total", get("skipped", get("controls_sums", s)))),
+					fmt.Sprint(get("total", get("failed", get("controls_sums", s)))),
+					strconv.Itoa(len(get("profiles", s).([]interface{}))),
+				}
+				if err := w.Write(record); err != nil {
+					fmt.Println("Error while writing csv: ", err)
+
+				}
+				w.Flush()
+				header = false
+				if err := w.Error(); err != nil {
+					fmt.Println("Error while writing csv: ", err)
+
+				}
+			}
+		}
+		if t == startTime {
+			break
+		}
+
+		endTime = t
+		t = endTime.Add(-24 * time.Hour)
+		if t.Before(startTime) {
+			t = startTime
+		}
+	}
+	fmt.Println("The details of the runs can be found in : ", filename)
+}
diff --git a/components/automate-cluster-ctl/readme b/components/automate-cluster-ctl/readme
index d8987685e..723837302 100644
--- a/components/automate-cluster-ctl/readme
+++ b/components/automate-cluster-ctl/readme
@@ -1,1 +1,2 @@
-Add Readme 
\ No newline at end of file
+Add Readme 
+bump the pkg version
diff --git a/components/automate-cs-bookshelf/habitat/plan.sh b/components/automate-cs-bookshelf/habitat/plan.sh
index 8e6cdfd7e..a89722738 100644
--- a/components/automate-cs-bookshelf/habitat/plan.sh
+++ b/components/automate-cs-bookshelf/habitat/plan.sh
@@ -6,7 +6,7 @@ pkg_name="automate-cs-bookshelf"
 pkg_description="Wrapper package for chef/bookshelf"
 pkg_origin="chef"
 # WARNING: Version managed by .expeditor/update_chef_server.sh
-pkg_version="14.4.4"
+pkg_version="14.11.36"
 vendor_origin="chef"
 pkg_maintainer="Chef Software Inc. <support@chef.io>"
 pkg_license=("Chef-MLSA")
@@ -15,7 +15,7 @@ pkg_deps=(
   chef/mlsa
   "${local_platform_tools_origin:-chef}/automate-platform-tools"
   # WARNING: Version pin managed by .expeditor/update_chef_server.sh
-  "${vendor_origin}/bookshelf/14.4.4/20210520120637"
+  "${vendor_origin}/bookshelf/14.11.36/20211227114241"
 )
 
 pkg_binds=(
diff --git a/components/automate-cs-nginx/habitat/plan.sh b/components/automate-cs-nginx/habitat/plan.sh
index 9e8609036..cf6762120 100644
--- a/components/automate-cs-nginx/habitat/plan.sh
+++ b/components/automate-cs-nginx/habitat/plan.sh
@@ -7,7 +7,7 @@ vendor_origin="chef"
 pkg_maintainer="Chef Software Inc. <support@chef.io>"
 pkg_license=('Chef-MLSA')
 # WARNING: Version managed by .expeditor/update_chef_server.sh
-pkg_version="14.4.4"
+pkg_version="14.11.36"
 pkg_deps=(
   core/coreutils
   chef/mlsa
@@ -18,10 +18,10 @@ pkg_deps=(
   # chef-server-* packages.
   #
   core/curl/7.68.0/20200601114640
-  core/ruby26/2.6.5/20200404043345
+  core/ruby27/2.7.0/20200404045319
   # WARNING: Version pin managed by .expeditor/update_chef_server.sh
-  "${vendor_origin}/chef-server-nginx/14.4.4/20210520121142"
-  "${vendor_origin}/chef-server-ctl/14.4.4/20210520120637"
+  "${vendor_origin}/chef-server-nginx/14.11.36/20211227114734"
+  "${vendor_origin}/chef-server-ctl/14.11.36/20211227114241"
 )
 
 pkg_bin_dirs=(bin)
@@ -63,7 +63,7 @@ scaffolding_go_binary_list=(
 chef_automate_hab_binding_mode="relaxed"
 
 do_prepare() {
-  GO_LDFLAGS="-X main.RubyPath=$(pkg_path_for core/ruby26)"
+  GO_LDFLAGS="-X main.RubyPath=$(pkg_path_for core/ruby27)"
   GO_LDFLAGS="$GO_LDFLAGS -X main.ChefServerCtlPath=$(pkg_path_for chef/chef-server-ctl)"
   GO_LDFLAGS="$GO_LDFLAGS -X main.KnifePath=${pkg_prefix}/bin/knife"
   GO_LDFLAGS="$GO_LDFLAGS -X main.Version=${pkg_version}/${pkg_release}"
@@ -79,5 +79,5 @@ do_install() {
   install "$PLAN_CONTEXT/bin/knife" "$wrapper_bin_path/knife"
 
   sed -i "s!__BUILDTIME_HAB_PKG_PATH_CHEF_SERVER_CTL__!$(pkg_path_for chef/chef-server-ctl)!g" "$wrapper_bin_path/knife"
-  sed -i "s!__BUILDTIME_HAB_PKG_PATH_RUBY__!$(pkg_path_for core/ruby26)!g" "$wrapper_bin_path/knife"
+  sed -i "s!__BUILDTIME_HAB_PKG_PATH_RUBY__!$(pkg_path_for core/ruby27)!g" "$wrapper_bin_path/knife"
 }
diff --git a/components/automate-cs-oc-bifrost/habitat/plan.sh b/components/automate-cs-oc-bifrost/habitat/plan.sh
index 781cc84a4..7875e049c 100644
--- a/components/automate-cs-oc-bifrost/habitat/plan.sh
+++ b/components/automate-cs-oc-bifrost/habitat/plan.sh
@@ -6,7 +6,7 @@ pkg_name="automate-cs-oc-bifrost"
 pkg_description="Wrapper package for chef/oc_bifrost"
 pkg_origin="chef"
 # WARNING: Version managed by .expeditor/update_chef_server.sh
-pkg_version="14.4.4"
+pkg_version="14.11.36"
 vendor_origin="chef"
 pkg_maintainer="Chef Software Inc. <support@chef.io>"
 pkg_license=("Chef-MLSA")
@@ -15,7 +15,7 @@ pkg_deps=(
   chef/mlsa
   "${local_platform_tools_origin:-chef}/automate-platform-tools"
   # WARNING: Version pin managed by .expeditor/update_chef_server.sh
-  "${vendor_origin}/oc_bifrost/14.4.4/20210520120641"
+  "${vendor_origin}/oc_bifrost/14.11.36/20211227114250"
 )
 
 pkg_binds=(
diff --git a/components/automate-cs-oc-erchef/habitat/hooks/run b/components/automate-cs-oc-erchef/habitat/hooks/run
index 5aed1b51d..ba91b1d74 100644
--- a/components/automate-cs-oc-erchef/habitat/hooks/run
+++ b/components/automate-cs-oc-erchef/habitat/hooks/run
@@ -49,7 +49,7 @@ HOME="{{pkg.svc_data_path}}" BUNDLE_GEMFILE="{{pkgPathFor "chef/oc_erchef"}}/Gem
     bundle exec ruby "{{pkg.svc_config_path}}/chef_server_data_bootstrap.rb"
 
 # ruby vars are needed in order to make depselector start up
-export PATH={{pkgPathFor "core/ruby26"}}/bin:${PATH}
+export PATH={{pkgPathFor "core/ruby27"}}/bin:${PATH}
 export LD_LIBRARY_PATH={{pkgPathFor "core/libffi"}}/lib:${LD_LIBRARY_PATH}
 export GEM_HOME={{pkgPathFor "chef/oc_erchef"}}/vendor/bundle
 export GEM_PATH={{pkgPathFor "chef/oc_erchef"}}/vendor/bundle
diff --git a/components/automate-cs-oc-erchef/habitat/plan.sh b/components/automate-cs-oc-erchef/habitat/plan.sh
index 6ab36652d..3a1967e0f 100644
--- a/components/automate-cs-oc-erchef/habitat/plan.sh
+++ b/components/automate-cs-oc-erchef/habitat/plan.sh
@@ -6,7 +6,7 @@ pkg_name="automate-cs-oc-erchef"
 pkg_description="Wrapper package for chef/oc_erchef"
 pkg_origin="chef"
 # WARNING: Version managed by .expeditor/update_chef_server.sh
-pkg_version="14.4.4"
+pkg_version="14.11.36"
 vendor_origin="chef"
 pkg_maintainer="Chef Software Inc. <support@chef.io>"
 pkg_license=("Chef-MLSA")
@@ -16,7 +16,7 @@ pkg_deps=(
   chef/mlsa
   "${local_platform_tools_origin:-chef}/automate-platform-tools"
   # WARNING: Version pin managed by .expeditor/update_chef_server.sh
-  "${vendor_origin}/oc_erchef/14.4.4/20210520120641"
+  "${vendor_origin}/oc_erchef/14.11.36/20211227114250"
 )
 
 pkg_build_deps=(
diff --git a/components/automate-deployment/habitat/plan.sh b/components/automate-deployment/habitat/plan.sh
index c151eb394..8b07bb164 100644
--- a/components/automate-deployment/habitat/plan.sh
+++ b/components/automate-deployment/habitat/plan.sh
@@ -30,7 +30,7 @@ pkg_deps=(
   chef/mlsa
   # deployment-service uses the postgres11 client to backup/restore postgres.
   # we need pg11 because the ha backend uses postgres 11
-  core/postgresql11-client
+  core/postgresql13-client
 )
 pkg_bin_dirs=(bin)
 pkg_exports=(
diff --git a/components/automate-deployment/pkg/services/internal/generated/gen.go b/components/automate-deployment/pkg/services/internal/generated/gen.go
index 4139b91cd..be4dea1ff 100644
--- a/components/automate-deployment/pkg/services/internal/generated/gen.go
+++ b/components/automate-deployment/pkg/services/internal/generated/gen.go
@@ -492,6 +492,10 @@ var ProductMetadataJSON = `
       "name": "chef/automate-ha-cluster-ctl",
       "metadata": null
     },
+    {
+      "name": "chef/automate-netcat",
+      "metadata": null
+    },
     {
       "name": "core/rsync",
       "metadata": null
diff --git a/components/automate-netcat/habitat/plan.sh b/components/automate-netcat/habitat/plan.sh
new file mode 100644
index 000000000..3f2e61be2
--- /dev/null
+++ b/components/automate-netcat/habitat/plan.sh
@@ -0,0 +1,38 @@
+# shellcheck disable=SC2148
+UPSTREAM_PKG_IDENT="core/netcat"
+pkg_name="automate-netcat"
+pkg_description="Wrapper package for core/netcat"
+pkg_origin="chef"
+pkg_version="0.1.0"
+vendor_origin="chef"
+pkg_maintainer="Chef Software Inc. <support@chef.io>"
+pkg_license=("Chef-MLSA")
+pkg_upstream_url="https://www.chef.io/automate"
+pkg_build_deps=("${UPSTREAM_PKG_IDENT}")
+pkg_svc_user=root
+pkg_svc_group=root
+pkg_deps=(
+  chef/mlsa
+  "${UPSTREAM_PKG_IDENT}"
+)
+
+
+do_before() {
+  update_pkg_version
+}
+
+do_download() {
+  return 0
+}
+
+do_build() {
+  return 0
+}
+
+do_install() {
+  return 0
+}
+
+do_end() {
+  return 0
+}
diff --git a/components/docs-chef-io/content/automate/elasticsearch.md b/components/docs-chef-io/content/automate/elasticsearch.md
index 60d3d1288..65aa93a59 100644
--- a/components/docs-chef-io/content/automate/elasticsearch.md
+++ b/components/docs-chef-io/content/automate/elasticsearch.md
@@ -1,14 +1,197 @@
 +++
 title = "Elasticsearch"
-
-draft = true
-gh_repo = "automate"
+date = 2022-01-04T12:09:09-08:00
+draft = false
 
 [menu]
   [menu.automate]
-    title = "Elasticsearch"
-    parent = "automate/configure"
-    identifier = "automate/configure/elasticsearch.md Elasticsearch"
-    weight = 40
+    title = "Configure Elasticsearch"
+    identifier = "automate/configuring_automate/elasticsearch/elasticsearch.md Elasticsearch"
+    parent = "automate/configuring_automate/elasticsearch"
 +++
 
+You can configure Chef Automate to use Elasticsearch clusters that are not deployed via Chef Automate itself.
+
+## Configure External Elasticsearch
+
+These configuration directions are intended for in the initial deployment of Chef Automate.
+
+{{< note >}}
+Chef Automate supports the official Elasticsearch Service by Elastic. Chef Automate does not test or support alternative services, such as Amazon Elasticsearch Service (Amazon ES).
+{{< /note >}}
+
+Add the following to your config.toml:
+
+```toml
+[global.v1.external.elasticsearch]
+  enable = true
+  nodes = ["http://elastic1.example:9200", "http://elastic2.example:9200", "..." ]
+
+# Uncomment and fill out if using external elasticsearch with SSL and/or basic auth
+# [global.v1.external.elasticsearch.auth]
+#   scheme = "basic_auth"
+# [global.v1.external.elasticsearch.auth.basic_auth]
+## Create this elasticsearch user before starting the Automate deployment;
+## Automate assumes it exists.
+#   username = "<admin username>"
+#   password = "<admin password>"
+# [global.v1.external.elasticsearch.ssl]
+#  Specify either a root_cert or a root_cert_file
+#  root_cert = """$(cat </path/to/cert_file.crt>)"""
+#  server_name = "<elasticsearch server name>"
+
+# Uncomment and fill out if using external elasticsearch that uses hostname-based routing/load balancing
+# [esgateway.v1.sys.ngx.http]
+#  proxy_set_header_host = "<your external es hostname>:1234"
+
+# Uncomment and add to change the ssl_verify_depth for the root cert bundle
+#  ssl_verify_depth = "2"
+```
+
+Because externally-deployed Elasticsearch nodes will not have access to Chef Automate's built-in backup storage services, you must configure Elasticsearch backup settings separately from Chef Automate's primary backup settings. You can configure backups to use either the local filesystem or S3.
+
+### Add Resolvers for Elasticsearch
+
+In case you want to resolve the Elasticsearch node IPs dynamically using DNS servers, you can add resolvers/nameservers to the configuration.
+
+Name Servers can be added in two ways:
+
+1. **Add nameserver IPs:** Add the nameservers to your `config.toml` file to resolve the Elasticsearch nodes.
+
+    ```toml
+    [esgateway.v1.sys.ngx.main.resolvers]
+      # Multiple resolvers can be specified by adding the resolvers in the list.
+      nameservers = ["192.0.2.0:24", "198.51.100.0:24"]
+    ```
+
+1. **Set system DNS entries:** To use existing system nameserver entries from `/etc/resolv.conf`, add the following setting to `config.toml`:
+
+    ```toml
+    [esgateway.v1.sys.ngx.main.resolvers]
+      enable_system_nameservers = true
+    ```
+
+If both options are set, nameserver IPs takes precedence over the system nameserver entries.
+
+Apply the changes:
+
+```bash
+sudo chef-automate config patch config.toml
+````
+
+If you wish to reset to the default configuration or to modify the configuration:
+
+1. Run `chef-automate config show config.toml`.
+1. Open `config.toml` and remove the `esgateway.v1.sys.ngx.main.resolvers` configuration or change the values.
+1. Run `chef-automate config set config.toml` to apply your changes.
+
+## Backup External Elasticsearch
+
+### Backup External Elasticsearch to a Local Filesystem
+
+To configure local filesystem backups of Chef Automate data stored in an externally-deployed Elasticsearch cluster:
+
+1. Ensure that the filesystems you intend to use for backups are mounted to the same path on all Elasticsearch master and data nodes.
+1. Configure the Elasticsearch `path.repo` setting on each node as described in the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/modules-snapshots.html#_shared_file_system_repository).
+1. Add the following to your `config.toml`:
+
+```toml
+[global.v1.external.elasticsearch.backup]
+enable = true
+location = "fs"
+
+[global.v1.external.elasticsearch.backup.fs]
+# The `path.repo` setting you've configured on your Elasticsearch nodes must be
+# a parent directory of the setting you configure here:
+path = "/var/opt/chef-automate/backups"
+```
+
+### Backup External Elasticsearch to AWS S3
+
+To configure AWS S3 backups of Chef Automate data stored in an externally-deployed Elasticsearch cluster:
+
+1. Install the [`repository-s3` plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/repository-s3.html) on all nodes in your Elasticsearch cluster.
+1. If you wish to use IAM authentication to provide your Elasticsearch nodes access to the S3 bucket, you must apply the appropriate IAM policy to each host system in the cluster.
+1. Configure each Elasticsearch node with a S3 client configuration containing the proper S3 endpoint, credentials, and other settings as [described in the Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/plugins/current/repository-s3-client.html).
+1. Enable S3 backups by adding the following settings to your `config.toml`:
+
+    ```toml
+    [global.v1.external.elasticsearch.backup]
+    enable = true
+    location = "s3"
+
+    [global.v1.external.elasticsearch.backup.s3]
+
+      # bucket (required): The name of the bucket
+      bucket = "<bucket name>"
+
+      # base_path (optional):  The path within the bucket where backups should be stored
+      # If base_path is not set, backups will be stored at the root of the bucket.
+      base_path = "<base path>"
+
+      # name of an s3 client configuration you create in your elasticsearch.yml
+      # see https://www.elastic.co/guide/en/elasticsearch/plugins/current/repository-s3-client.html
+      # for full documentation on how to configure client settings on your
+      # Elasticsearch nodes
+      client = "<client name>"
+
+    [global.v1.external.elasticsearch.backup.s3.settings]
+    ## The meaning of these settings is documented in the S3 Repository Plugin
+    ## documentation. See the following links:
+    ## https://www.elastic.co/guide/en/elasticsearch/plugins/current/repository-s3-repository.html
+
+    ## Backup repo settings
+    # compress = false
+    # server_side_encryption = false
+    # buffer_size = "100mb"
+    # canned_acl = "private"
+    # storage_class = "standard"
+    ## Snapshot settings
+    # max_snapshot_bytes_per_sec = "40mb"
+    # max_restore_bytes_per_sec = "40mb"
+    # chunk_size = "null"
+    ## S3 client settings
+    # read_timeout = "50s"
+    # max_retries = 3
+    # use_throttle_retries = true
+    # protocol = "https"
+    ```
+
+### Backup External Elasticsearch to GCS
+
+To configure Google Cloud Storage Bucket (GCS) backups of Chef Automate data stored in an externally-deployed Elasticsearch cluster:
+
+1. Install the [`repository-gcs` plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/repository-gcs.html) on all nodes in your Elasticsearch cluster.
+1. Create a storage bucket and configure a service account to access it per the steps [described in the Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/plugins/current/repository-gcs-usage.html).
+1. Configure each Elasticsearch node with a GCS client configuration that contains the proper GCS settings as [described in the Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/plugins/current/repository-gcs-client.html).
+1. Enable GCS backups by adding the following settings to your `config.toml`:
+
+    ```toml
+    [global.v1.external.elasticsearch]
+      enable = true
+      nodes = ["https://my-es.cluster"]
+      ## If multiple
+      # nodes = ["https://my-es.node-1", "https://my-es.node-2", "etc..."]
+
+    ## The following settings are required if you have Elasticsearch setup with basic auth
+    #[global.v1.external.elasticsearch.auth]
+    #  scheme = "basic_auth"
+    #
+    #[global.v1.external.elasticsearch.auth.basic_auth]
+    #  username = "everyuser"
+    #  password = "pass123"
+
+    [global.v1.external.elasticsearch.backup]
+      enable = true
+      location = "gcs"
+
+    [global.v1.external.elasticsearch.backup.gcs]
+      bucket = "<bucket name>"
+      # Client name is normally default, but can be set here if you have generated service
+      # account credentials with a different client name
+      client = "default"
+
+    ## GCS Bucket Settings:
+    # type = nearline
+    # access control = uniform
+    ```
diff --git a/components/docs-chef-io/content/automate/monitoring.md b/components/docs-chef-io/content/automate/monitoring.md
index 0369260e8..4a20451b5 100644
--- a/components/docs-chef-io/content/automate/monitoring.md
+++ b/components/docs-chef-io/content/automate/monitoring.md
@@ -17,11 +17,11 @@ Use the authenticated https endpoint `/status` to monitor your Chef Automate ins
 
 ## Checking the Status Endpoint
 
-The authenticated endpoint `/status` provides status for the overall Chef Automate installation as well as for its component services.
+The authenticated endpoint `/status` provides status for the Chef Automate installation as well as for its component services.
 When all Chef Automate component services are up, `/status` returns a response code of 200.
 Otherwise, `/status` returns 500.
 
-The status of a service can be `OK`, `UNKNOWN`, or `CRITICAL`, and is reflected in the JSON output:
+The status of a service can be `OK`, `UNKNOWN`, or `CRITICAL`, and is shown in the JSON output:
 
    ```json
        {
diff --git a/components/docs-chef-io/content/automate/postgres_external_upgrade.md b/components/docs-chef-io/content/automate/postgres_external_upgrade.md
new file mode 100644
index 000000000..61524bba2
--- /dev/null
+++ b/components/docs-chef-io/content/automate/postgres_external_upgrade.md
@@ -0,0 +1,264 @@
++++
+title = "External PostgreSQL Upgrade"
+
+draft = false
+
+gh_repo = "automate"
+[menu]
+  [menu.automate]
+    title = "Upgrade External PostgreSQL"
+    identifier = "automate/configuring_automate/postgresql/postgres_external_upgrade.md PostgreSQL Upgrade"
+    parent = "automate/configuring_automate/postgresql"
+    weight = 20
++++
+
+This guide covers upgrading services used by Chef Automate.
+
+## Upgrade Amazon RDS for PostgreSQL
+
+Amazon Web Services Relational Database Service (AWS RDS) should upgrade by Monday, January 17. Amazon is automatically migrating all AWS RDS customers from PostgreSQL from 9.6 to 13 starting Tuesday, January 18, 2022.
+
+To upgrade AWS RDS, please follow instructions on [Upgrading the PostgreSQL DB engine for Amazon RDS - Amazon Relational Database Service](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_UpgradeDBInstance.PostgreSQL.html)
+
+## Upgrade External PostgreSQL from 9.6 to 13.4
+
+Chef Automate uses PostgreSQL as the primary database for storing node data. [PostgreSQL 9.6 is EOL](https://endoflife.date/postgresql) and Chef customers running Chef Automate with PostgreSQL 9.6 should upgrade to [Postgres 13](https://www.postgresql.org/about/news/postgresql-13-released-2077/).
+
+### Migration Planning
+
+The upgrade process for PostgreSQL from 9.6 to 13.4 requires a one-time downtime to vacuum, upgrade, and re-index the database. The entire upgrade operation takes about five minutes for each 2GB of data in PostgreSQL. This process may take longer depending on your server hardware and the size of the node objects in Chef Automate.
+
+### Requirements
+
+{{% warning %}}
+Upgrading PostgreSQL upgrades the database for all connected services. If you have multiple services connected to PostgreSQL, make sure that you have stopped the other services and prepared them for the upgrade.
+{{% /warning %}}
+
+This upgrade guide is for systems running:
+
+- A Single PostgreSQL 9.6 installation
+- Using Ubuntu 18.04 or higher
+- On a virtual machine such as an EC2 instance or on a single physical machine
+- Enough free space to run a second copy of the data that is in the existing PostgreSQL 9.6 installation. This upgrade requires a minimum of 55% free space on the machine.
+
+### Backup Chef Automate
+
+{{% danger %}}
+**BACKUP CHEF AUTOMATE AND SECURE THE DATA**. Preserve your backup at all costs. Copy the backup to a second and separate location.
+{{% /danger %}}
+
+Database migrations have inherent risk to your system. Create a backup before beginning any migration or update. This ensures that you have a recoverable state in case any step in the process fails. Copy the backup to a another disk that is not connected to Chef Automate. This ensures that you have state to restore, in case of a failure in the upgrade process.
+
+Follow the [Backup]({{< relref "backup.md" >}}) documentation to make a copy of your Chef Automate data.
+
+### Upgrade Chef Automate
+
+- For **airgapped** Chef Automate systems, follow the instructions in the [Airgap Installation Guide]({{< ref "airgapped_installation.md" >}}) to upgrade to the latest version.
+
+- Upgrade standard Chef Automate installations to the latest version that supports External PostgreSQL v13 with the command:
+
+```bash
+chef-automate upgrade run
+```
+
+### Stop Chef Automate
+
+After successful upgrade, stop Chef Automate Services.
+
+```bash
+sudo chef-automate stop
+```
+
+### Upgrade from PostgreSQL 9.6 to 13.4
+
+#### Install PostgreSQL 13.4
+
+1. SSH or Login to the machine running PostgreSQL 9.6 with user who has sudo or root access.
+
+    ```bash
+    ssh -i "<xyz.pem>" <sudo_username>@<postgres_machine_ip>
+    ```
+
+1. Install PostgreSQL v13
+
+    ```bash
+    sudo apt-get update
+    sudo apt-get install postgresql-13
+    ```
+
+1. If the PostgreSQL v9.6 config was modified or customized. Please check the differences and update the new PostgreSQL v13 config with similar changes. Please connect with your database administrator if you don't know these changes.
+
+    ```bash
+    sudo sdiff -s /etc/postgresql/9.6/main/postgresql.conf /etc/postgresql/13/main/postgresql.conf
+    sudo sdiff -s /etc/postgresql/9.6/main/pg_hba.conf /etc/postgresql/13/main/pg_hba.conf
+    ```
+
+### Stop PostgreSQL Services
+
+1. Stop Both PostgreSQL Servers:
+
+    ```bash
+    sudo systemctl stop postgresql.service
+    ```
+
+1. Login as the `postgres` user
+
+    ```bash
+    sudo su - postgres
+    ```
+
+### Prepare the Database for Migration
+
+Run `vacuumdb --all --full` on the PostgreSQL database if you don't have automatic vacuuming set up. This process will reduce the size of the database by deleting unnecessary data and speeds up the migration. This operation takes around 1 to 2 minutes per gigabyte of data depending on the complexity of the data, and requires at least as much free disk space as the size of your database.
+
+For more information on upgrading using `vacuumdb` see the PostgreSQL 13 documentation for [vacuumdb](https://www.postgresql.org/docs/13/app-vacuumdb.html).
+
+1. Run Vacuum DB before moving data from PostgreSQL v9.6 to v13
+
+    ```bash
+    vacuumdb --all --full
+    ```
+
+    Expected output:
+
+    ```bash
+    vacuumdb: vacuuming database "automate-cs-bookshelf"
+    vacuumdb: vacuuming database "automate-cs-oc-bifrost"
+    vacuumdb: vacuuming database "automate-cs-oc-erchef"
+    vacuumdb: vacuuming database "chef_applications_service"
+    vacuumdb: vacuuming database "chef_authn_service"
+    vacuumdb: vacuuming database "chef_authz_service"
+    vacuumdb: vacuuming database "chef_cereal_service"
+    vacuumdb: vacuuming database "chef_compliance_service"
+    vacuumdb: vacuuming database "chef_config_mgmt_service"
+    vacuumdb: vacuuming database "chef_infra_proxy"
+    vacuumdb: vacuuming database "chef_ingest_service"
+    vacuumdb: vacuuming database "chef_license_control_service"
+    vacuumdb: vacuuming database "chef_session_service"
+    vacuumdb: vacuuming database "chef_teams_service"
+    vacuumdb: vacuuming database "chef_user_settings_service"
+    vacuumdb: vacuuming database "data_feed_service"
+    vacuumdb: vacuuming database "dex"
+    vacuumdb: vacuuming database "nodemanager_service"
+    vacuumdb: vacuuming database "notifications_service"
+    vacuumdb: vacuuming database "postgres"
+    vacuumdb: vacuuming database "secrets_service"
+    vacuumdb: vacuuming database "template1"
+    ```
+
+#### Upgrade
+
+For more information on upgrading using `pg_upgrade` and `pg_upgrade --check` see the PostgreSQL 13 documentation for [pg_upgrade](https://www.postgresql.org/docs/13/pgupgrade.html).
+
+1. Run pg_upgrade check command.
+
+    ```bash
+    cd ~
+    /usr/lib/postgresql/13/bin/pg_upgrade \
+    --old-datadir=/var/lib/postgresql/9.6/main \
+    --new-datadir=/var/lib/postgresql/13/main \
+    --old-bindir=/usr/lib/postgresql/9.6/bin \
+    --new-bindir=/usr/lib/postgresql/13/bin \
+    --old-options '-c config_file=/etc/postgresql/9.6/main/postgresql.conf' \
+    --new-options '-c config_file=/etc/postgresql/13/main/postgresql.conf' \
+    --check
+    ```
+
+1. Migrate the Data (run pg_upgrade command without --check):
+
+    ```bash
+    cd ~
+    /usr/lib/postgresql/13/bin/pg_upgrade \
+    --old-datadir=/var/lib/postgresql/9.6/main \
+    --new-datadir=/var/lib/postgresql/13/main \
+    --old-bindir=/usr/lib/postgresql/9.6/bin \
+    --new-bindir=/usr/lib/postgresql/13/bin \
+    --old-options '-c config_file=/etc/postgresql/9.6/main/postgresql.conf' \
+    --new-options '-c config_file=/etc/postgresql/13/main/postgresql.conf'
+    ```
+
+#### Configure
+
+1. Exit the PostgreSQL shell
+
+    ```bash
+    exit
+    ```
+
+1. Swap the ports of PostgreSQL v9.6 and 13.4:
+
+    ```bash
+    PG_9_6_PORT=5432 # Assuming Postgres v9.6 is currently running on this port
+    PG_13_PORT=5433 # Assuming Postgres 13 is currently running on this port
+    sudo sed -i "s/port = $PG_9_6_PORT/port = $PG_13_PORT/g" /etc/postgresql/9.6/main/postgresql.conf
+    sudo sed -i "s/port = $PG_13_PORT/port = $PG_9_6_PORT/g" /etc/postgresql/13/main/postgresql.conf
+    ```
+
+1. Start both PostgreSQL Servers:
+
+    ```bash
+    sudo systemctl start postgresql.service
+    ```
+
+#### Validate
+
+1. Confirm the installed version:
+
+    ```bash
+    sudo su - postgres
+    psql -c "SELECT version();"
+    ```
+
+1. Run the generated `analyze_new_cluster.sh` script
+
+    ```bash
+    cd ~
+    ./analyze_new_cluster.sh
+    ```
+
+    Reindexing is not required for Chef Automate. If `pg_upgrade` reported errors or need for reindexing please refer to [pg_upgrade documentation](https://www.postgresql.org/docs/13/pgupgrade.html) for details.
+
+1. Exit postgres user:
+
+    ```bash
+    exit
+    ```
+
+#### Cleanup PostgreSQL 9.6
+
+After you have confirmed the installation and you no longer need PostgreSQL 9.6, remove PostgreSQL v9.6 and its data:
+
+```bash
+sudo su - postgres
+./delete_old_cluster.sh
+```
+
+### Restart Chef Automate
+
+1. Start Chef Automate:
+
+    ```bash
+    sudo chef-automate start
+    ```
+
+1. Check that the Chef Automate services are up and running:
+
+    ```bash
+    sudo chef-automate status
+    ```
+
+## Troubleshoot Upgrade Failures
+
+1. If the upgrade failed and you are left with a corrupted Chef Automate or a corrupted PostgreSQL database, **DO NOT RISK YOUR BACKUP OF AUTOMATE**. Take all steps necessary to preserve the backup, including copying it to another disk.
+1. Contact Chef customer support.
+1. If you have configured the backup directory as a location other than the default directory (`/var/opt/chef-automate/backups`), you must supply the backup directory path to the `backup restore` command as shown in the snippet below. Without a backup ID, Chef Automate uses the most recent backup in the backup directory.
+
+   To restore on a new host, run:
+
+   ```bash
+   chef-automate backup restore </path/to/backups/>BACKUP_ID
+   ```
+
+      For other restoration types please refer this [Restore]({{< ref "restore.md" >}})
+
+1. Do not upgrade PostgreSQL until you have an uncorrupted Chef Automate and an uncorrupted PostgreSQL database.
diff --git a/components/docs-chef-io/content/automate/postgresql.md b/components/docs-chef-io/content/automate/postgresql.md
new file mode 100644
index 000000000..3e1fb615c
--- /dev/null
+++ b/components/docs-chef-io/content/automate/postgresql.md
@@ -0,0 +1,84 @@
++++
+title = "PostgreSQL"
+
+date = 2022-01-04T12:09:09-08:00
+draft = false
+
+[menu]
+  [menu.automate]
+    title = "Configure Postgresql"
+    identifier = "automate/configuring_automate/postgresql/postgresql.md Postgresql"
+    parent = "automate/configuring_automate/postgresql"
+    weight = 10
++++
+
+[\[edit on GitHub\]](https://github.com/chef/automate/blob/master/components/docs-chef-io/content/automate/postgresql.md)
+
+You can configure Chef Automate to use external PostgreSQL clusters that are not deployed via Chef Automate itself.
+
+## Configuring an External PostgreSQL Database
+
+These configuration directions are intended for in the initial deployment of Chef Automate.
+
+Add the following settings to your `config.toml`:
+
+```toml
+[global.v1.external.postgresql]
+enable = true
+nodes = ["<pghostname1>:<port1>", "<pghostname2>:<port2>", "..."]
+
+# To use postgres with SSL, uncomment and fill out the following:
+# [global.v1.external.postgresql.ssl]
+# enable = true
+# root_cert = """$(cat </path/to/root/cert.pem>)"""
+
+[global.v1.external.postgresql.auth]
+scheme = "password"
+
+# Create these postgres users before starting the Automate deployment;
+# Automate assumes they already exist.
+[global.v1.external.postgresql.auth.password.superuser]
+username = "<admin username>"
+password = "<admin password>"
+[global.v1.external.postgresql.auth.password.dbuser]
+username = "<dbuser username>"
+password = "<dbuser password>"
+
+[global.v1.external.postgresql.backup]
+enable = true
+```
+
+## Adding Resolvers for PostgreSQL Database
+
+In case you want to resolve the PostgreSQL cluster node IPs dynamically using DNS servers, you can add resolvers/nameservers to the configuration.
+
+Name Servers can be added in two ways:
+
+1. **Add nameserver IPs:** If you are aware of the nameservers which should resolve the PostgreSQL nodes, the nameservers can be added to your `config.toml` file.
+
+    ```toml
+    [pg_gateway.v1.sys.resolvers]
+      # Multiple resolvers can be specified by adding the resolvers in the list.
+      nameservers = ["127.0.0.53:53"]
+    ```
+
+1. **Set system DNS entries:** To use existing system nameserver entries from `/etc/resolv.conf`, add the following setting to `config.toml`:
+
+    ```toml
+    [pg_gateway.v1.sys.resolvers]
+      enable_system_nameservers = true
+    ```
+
+If both options are set, nameserver IPs takes precedence over the system nameserver entries.
+
+Apply the changes:
+
+```bash
+sudo chef-automate config patch config.toml
+````
+
+If you wish to reset to the default configuration or to modify the configuration:
+
+1. Run `chef-automate config show config.toml`.
+1. Edit `config.toml` to replace/edit the `pg_gateway.v1.sys.resolvers` section with the configuration values.
+1. Run `chef-automate config set config.toml` to apply your changes.
diff --git a/components/docs-chef-io/content/automate/upgrade.md b/components/docs-chef-io/content/automate/upgrade.md
new file mode 100644
index 000000000..acb34ba64
--- /dev/null
+++ b/components/docs-chef-io/content/automate/upgrade.md
@@ -0,0 +1,65 @@
++++
+title = "Upgrade Chef Automate"
+date = 2022-01-04T12:02:46-08:00
+draft = false
+
+[menu]
+  [menu.automate]
+    title = "Upgrade"
+    identifier = "automate/upgrade.md Upgrade"
+    parent = "automate"
++++
+
+[\[edit on GitHub\]](https://github.com/chef/automate/blob/master/components/docs-chef-io/content/automate/upgrade.md)
+
+By default, Chef Automate automatically upgrades to the latest version.
+
+## Release Channels
+
+The Chef Automate upgrade process makes use of **release channels** to allow greater control over the automatic upgrades applied to your system. Chef Automate pulls the latest release within a specified release channel. The default channel is the `current` channel.
+
+To change the release channel that is used for upgrades, modify the `channel` setting in your `config.toml` file:
+
+```toml
+channel = "current"
+```
+
+## Disable Automatic Upgrades
+
+You can disable automatic upgrades by changing the `upgrade_strategy` setting to `none` in your `config.toml`:
+
+```toml
+upgrade_strategy = "none"
+```
+
+To manually initiate an upgrade, run
+
+```shell
+chef-automate upgrade run
+```
+
+This command upgrades Chef Automate to the latest version available from your release channel.
+
+## Troubleshoot an Upgrade
+
+If you are unable to open Chef Automate, check that the `config.toml` contains the public DNS as the FQDN.
+
+```shell
+# This is a default Chef Automate configuration file. You can run
+# 'chef-automate deploy' with this config file and it should
+# successfully create a new Chef Automate instance with default settings.
+
+[global.v1]
+# The external fully qualified domain name.
+# When the application is deployed you should be able to access 'https://<fqdn>/'
+# to login.
+fqdn = "<_Public DNS_name>"
+```
+
+Once you correct and save the FQDN, run:
+
+```shell
+sudo chef-automate config patch config.toml
+```
+
+Open Chef Automate in your browser.
diff --git a/components/docs-chef-io/data/automate/cli_chef_automate/commands/chef-automate.yaml b/components/docs-chef-io/data/automate/cli_chef_automate/commands/chef-automate.yaml
index f11e804d2..e1798c82b 100644
--- a/components/docs-chef-io/data/automate/cli_chef_automate/commands/chef-automate.yaml
+++ b/components/docs-chef-io/data/automate/cli_chef_automate/commands/chef-automate.yaml
@@ -51,5 +51,7 @@ see_also:
 - test - Run Automate HA smoke tests
 - uninstall - Uninstall Chef Automate
 - upgrade - upgrade automate to the latest version
+- validate-ha-infrastructure - Validate existing infrastructure of Automate HA
+- validate-ha-infrastructure - Validate existing infrastructure of Automate HA
 - version - Show CLI version
 - workspace - Set workspace env for Automate HA.
diff --git a/components/docs-chef-io/data/automate/cli_chef_automate/commands/chef-automate_provision-infra.yaml b/components/docs-chef-io/data/automate/cli_chef_automate/commands/chef-automate_provision-infra.yaml
index 8fba85ee4..849be4dff 100644
--- a/components/docs-chef-io/data/automate/cli_chef_automate/commands/chef-automate_provision-infra.yaml
+++ b/components/docs-chef-io/data/automate/cli_chef_automate/commands/chef-automate_provision-infra.yaml
@@ -3,6 +3,8 @@ synopsis: Provision Automate HA infra.
 usage: chef-automate provision-infra [flags]
 description: Provision infra for Automate HA deployment.
 options:
+- name: airgap-bundle
+  usage: Path to an airgap install bundle
 - name: channel
   usage: Release channel to deploy all services from
 - name: help
diff --git a/components/docs-chef-io/data/automate/cli_chef_automate/commands/chef-automate_validate-ha-infrastructure.yaml b/components/docs-chef-io/data/automate/cli_chef_automate/commands/chef-automate_validate-ha-infrastructure.yaml
new file mode 100644
index 000000000..41e9cab21
--- /dev/null
+++ b/components/docs-chef-io/data/automate/cli_chef_automate/commands/chef-automate_validate-ha-infrastructure.yaml
@@ -0,0 +1,25 @@
+name: chef-automate validate-ha-infrastructure
+synopsis: Validate existing infrastructure of Automate HA
+usage: chef-automate validate-ha-infrastructure [flags]
+description: Validate existing infrastructure of automate HA
+options:
+- name: airgap-bundle
+  usage: Path to airgap bundle
+- name: habitat-bundle
+  usage: Path to haitat tar bundle
+- name: help
+  shorthand: h
+  default_value: "false"
+  usage: help for validate-ha-infrastructure
+inherited_options:
+- name: debug
+  shorthand: d
+  default_value: "false"
+  usage: Enable debug output
+- name: no-check-version
+  default_value: "false"
+  usage: Disable version check
+- name: result-json
+  usage: Write command result as JSON to PATH
+see_also:
+- chef-automate - Chef Automate CLI
diff --git a/lib/platform/pg/exporter.go b/lib/platform/pg/exporter.go
index 5c3ec6b60..5e84c9e1c 100644
--- a/lib/platform/pg/exporter.go
+++ b/lib/platform/pg/exporter.go
@@ -18,21 +18,23 @@ import (
 	"github.com/chef/automate/lib/platform/command"
 )
 
+const Pg13Client = "core/postgresql13-client"
+
 var (
 	// DefaultPGDumpCmd is the command we will run for
 	// pg_dump.This package-level var is provided so a
 	// stub-command can be injected by the tests.
-	DefaultPGDumpCmd = []string{"hab", "pkg", "exec", "core/postgresql11-client", "pg_dump"}
+	DefaultPGDumpCmd = []string{"hab", "pkg", "exec", Pg13Client, "pg_dump"}
 
 	// PGRestoreCmd is the command we will run for
 	// pg_restore. This package-level var is provided so a
 	// stub-command can be injected by the tests.
-	DefaultPGRestoreCmd = []string{"hab", "pkg", "exec", "core/postgresql11-client", "pg_restore"}
+	DefaultPGRestoreCmd = []string{"hab", "pkg", "exec", Pg13Client, "pg_restore"}
 
 	// PSQLCmd is the command we will run for psql. This
 	// package-level var is provided so a stub-command can be
 	// injected by the tests.
-	DefaultPSQLCmd = []string{"hab", "pkg", "exec", "core/postgresql11-client", "psql"}
+	DefaultPSQLCmd = []string{"hab", "pkg", "exec", Pg13Client, "psql"}
 )
 
 func PGDumpCmd() []string {
diff --git a/products.meta b/products.meta
index 0bf5630d0..5488c4d1d 100644
--- a/products.meta
+++ b/products.meta
@@ -58,6 +58,7 @@
     "chef/automate-ha-pgleaderchk",
     "chef/automate-ha-postgresql",
     "chef/automate-ha-cluster-ctl",
+    "chef/automate-netcat",
     "core/rsync"
   ],
 


Test patch:
diff --git a/integration/tests/a1migration.sh b/integration/tests/a1migration.sh
index e95bbab77..2292eae1d 100644
--- a/integration/tests/a1migration.sh
+++ b/integration/tests/a1migration.sh
@@ -46,7 +46,7 @@ do_test_deploy() {
     chef_server_migration_smoke_tests
     ## skipping status test because of the missing file in automate - /etc/opscode/chef-server-running.json 
     ## adding smoke tag or else all the test will be considered skipping only the status test
-    PATH="/hab/bin:/bin" chef-server-ctl test --smoke --skip-status
+    PATH="/hab/bin:/bin" chef-server-ctl test --smoke --skip-status --skip=response_headers
 
     workflow_server_migration_smoke_tests
 }
diff --git a/integration/tests/chef_server.sh b/integration/tests/chef_server.sh
index 6bdb803a6..3baa392c8 100644
--- a/integration/tests/chef_server.sh
+++ b/integration/tests/chef_server.sh
@@ -55,7 +55,7 @@ do_test_deploy() {
     umask 022
     ## skipping status test because of the missing file in automate - /etc/opscode/chef-server-running.json 
     ## adding smoke tag or else all the test will be considered skipping only the status test
-    PATH="/hab/bin:/bin" chef-server-ctl test --smoke --skip-status
+    PATH="/hab/bin:/bin" chef-server-ctl test --smoke --skip-status --skip=response_headers
     test_chef_server_ctl
     test_knife
     test_cookbook_caching
diff --git a/integration/tests/chef_server_backup.sh b/integration/tests/chef_server_backup.sh
index 7e143ab96..d85f93057 100644
--- a/integration/tests/chef_server_backup.sh
+++ b/integration/tests/chef_server_backup.sh
@@ -29,7 +29,7 @@ do_deploy() {
 do_test_deploy() {
     ## skipping status test because of the missing file in automate - /etc/opscode/chef-server-running.json 
     ## adding smoke tag or else all the test will be considered skipping only the status test
-    PATH="/hab/bin:/bin" chef-server-ctl test --smoke --skip-status
+    PATH="/hab/bin:/bin" chef-server-ctl test --smoke --skip-status --skip=response_headers
     test_chef_server_ctl
     do_test_deploy_default
 }
diff --git a/integration/tests/chef_server_only.sh b/integration/tests/chef_server_only.sh
index 0ecc92843..240dfddc9 100644
--- a/integration/tests/chef_server_only.sh
+++ b/integration/tests/chef_server_only.sh
@@ -32,6 +32,6 @@ do_deploy() {
 do_test_deploy() {
     ## skipping status test because of the missing file in automate - /etc/opscode/chef-server-running.json 
     ## adding smoke tag or else all the test will be considered skipping only the status test
-    PATH="/hab/bin:/bin" chef-server-ctl test --smoke --skip-status
+    PATH="/hab/bin:/bin" chef-server-ctl test --smoke --skip-status --skip=response_headers
     test_chef_server_ctl
 }
diff --git a/integration/tests/chef_server_upgrade.sh b/integration/tests/chef_server_upgrade.sh
index 2d843f4bd..6afdaddff 100644
--- a/integration/tests/chef_server_upgrade.sh
+++ b/integration/tests/chef_server_upgrade.sh
@@ -24,7 +24,7 @@ do_deploy() {
 do_test_deploy() {
     ## skipping status test because of the missing file in automate - /etc/opscode/chef-server-running.json 
     ## adding smoke tag or else all the test will be considered skipping only the status test
-    PATH="/hab/bin:/bin" chef-server-ctl test --smoke --skip-status
+    PATH="/hab/bin:/bin" chef-server-ctl test --smoke --skip-status --skip=response_headers
     test_chef_server_ctl
     test_knife
     do_test_deploy_default
@@ -33,7 +33,7 @@ do_test_deploy() {
 do_test_upgrade() {
     ## skipping status test because of the missing file in automate - /etc/opscode/chef-server-running.json 
     ## adding smoke tag or else all the test will be considered skipping only the status test
-    PATH="/hab/bin:/bin" chef-server-ctl test --smoke --skip-status
+    PATH="/hab/bin:/bin" chef-server-ctl test --smoke --skip-status --skip=response_headers
     test_chef_server_ctl
     test_knife
     do_test_upgrade_default
diff --git a/integration/tests/ha_chef_server.sh b/integration/tests/ha_chef_server.sh
index 53667ac75..fc18584bc 100644
--- a/integration/tests/ha_chef_server.sh
+++ b/integration/tests/ha_chef_server.sh
@@ -173,7 +173,7 @@ EOH
 do_test_deploy() {
     ## skipping status test because of the missing file in automate - /etc/opscode/chef-server-running.json 
     ## adding smoke tag or else all the test will be considered skipping only the status test
-    hab pkg exec chef/automate-cs-nginx chef-server-ctl test --smoke --skip-status
+    hab pkg exec chef/automate-cs-nginx chef-server-ctl test --smoke --skip-status --skip=response_headers
 }
 
 do_cleanup() {
diff --git a/lib/platform/pg/exporter_test.go b/lib/platform/pg/exporter_test.go
index 95b7f25f8..981d34004 100644
--- a/lib/platform/pg/exporter_test.go
+++ b/lib/platform/pg/exporter_test.go
@@ -101,7 +101,7 @@ func TestExport(t *testing.T) {
 	}
 
 	connURI := connURI("test_database")
-	stdArgs := []string{"pkg", "exec", "core/postgresql11-client", "pg_dump", "--if-exists", "--verbose", "--clean", "--file", "test_database.sql", "--no-privileges", "--no-owner", connURI}
+	stdArgs := []string{"pkg", "exec", pg.Pg13Client, "pg_dump", "--if-exists", "--verbose", "--clean", "--file", "test_database.sql", "--no-privileges", "--no-owner", connURI}
 
 	tests := []struct {
 		desc           string
@@ -114,16 +114,16 @@ func TestExport(t *testing.T) {
 	}{
 		{"it exports the db with pg_dump", "", []string{}, stdArgs, nil, false, false},
 		{"it runs pg_dump with the right --exclude-table options", "", []string{"table1", "table2"},
-			[]string{"pkg", "exec", "core/postgresql11-client", "pg_dump",
+			[]string{"pkg", "exec", pg.Pg13Client, "pg_dump",
 				"--if-exists", "--verbose", "--clean", "--file", "test_database.sql", "--no-privileges", "--no-owner",
 				"--exclude-table", "table1", "--exclude-table", "table2",
 				connURI},
 			nil, false, false},
 		{"it runs pg_dump without --no-privileges and --no-owner if User is set", "testuser", []string{},
-			[]string{"pkg", "exec", "core/postgresql11-client", "pg_dump", "--if-exists", "--verbose", "--clean", "--file", "test_database.sql", connURI},
+			[]string{"pkg", "exec", pg.Pg13Client, "pg_dump", "--if-exists", "--verbose", "--clean", "--file", "test_database.sql", connURI},
 			nil, false, false},
 		{"it runs pg_dump with -Fc when UseCustomFormat is set to true", "testuser", []string{},
-			[]string{"pkg", "exec", "core/postgresql11-client", "pg_dump", "--if-exists",
+			[]string{"pkg", "exec", pg.Pg13Client, "pg_dump", "--if-exists",
 				"--verbose", "--clean", "--file", "test_database.fc", "-Fc", connURI},
 			nil, false, true},
 		{"it returns an error in psql resturns an error", "", []string{}, stdArgs, errors.New("test-error"), true, false},
@@ -192,7 +192,7 @@ func TestImport(t *testing.T) {
 
 		mockExec.Expect("Run", command.ExpectedCommand{
 			Cmd: "hab",
-			Args: []string{"pkg", "exec", "core/postgresql11-client", "psql", "-f", exportFile,
+			Args: []string{"pkg", "exec", pg.Pg13Client, "psql", "-f", exportFile,
 				"-v", "ON_ERROR_STOP=true", connURI("test_database")},
 			Env: testExpectedEnv,
 		}).Return(nil)
@@ -212,7 +212,7 @@ func TestImport(t *testing.T) {
 
 		mockExec.Expect("Run", command.ExpectedCommand{
 			Cmd: "hab",
-			Args: []string{"pkg", "exec", "core/postgresql11-client", "psql", "-f", exportFile,
+			Args: []string{"pkg", "exec", pg.Pg13Client, "psql", "-f", exportFile,
 				connURI("test_database")},
 			Env: testExpectedEnv,
 		}).Return(nil)
@@ -233,7 +233,7 @@ func TestImport(t *testing.T) {
 
 		mockExec.Expect("Run", command.ExpectedCommand{
 			Cmd: "hab",
-			Args: []string{"pkg", "exec", "core/postgresql11-client", "psql", "-f", exportFile,
+			Args: []string{"pkg", "exec", pg.Pg13Client, "psql", "-f", exportFile,
 				"-v", "ON_ERROR_STOP=true", connURI("test_database")},
 			Env: testExpectedEnv,
 		}).Return(nil)
@@ -255,7 +255,7 @@ func TestImport(t *testing.T) {
 
 		mockExec.Expect("Run", command.ExpectedCommand{
 			Cmd: "hab",
-			Args: []string{"pkg", "exec", "core/postgresql11-client", "psql", "-f", exportFile,
+			Args: []string{"pkg", "exec", pg.Pg13Client, "psql", "-f", exportFile,
 				"-v", "ON_ERROR_STOP=true", connURI("test_database")},
 			Env: testExpectedEnv,
 		}).Return(nil)
@@ -273,7 +273,7 @@ func TestImport(t *testing.T) {
 
 		mockExec.Expect("Run", command.ExpectedCommand{
 			Cmd: "hab",
-			Args: []string{"pkg", "exec", "core/postgresql11-client", "psql", "-f", exportFile, "-v", "ON_ERROR_STOP=true",
+			Args: []string{"pkg", "exec", pg.Pg13Client, "psql", "-f", exportFile, "-v", "ON_ERROR_STOP=true",
 				connURI("test_database")},
 			Env: testExpectedEnv,
 		}).Return(nil)
@@ -290,7 +290,7 @@ func TestImport(t *testing.T) {
 
 		mockExec.Expect("Run", command.ExpectedCommand{
 			Cmd: "hab",
-			Args: []string{"pkg", "exec", "core/postgresql11-client", "psql", "-f", exportFile,
+			Args: []string{"pkg", "exec", pg.Pg13Client, "psql", "-f", exportFile,
 				"-v", "ON_ERROR_STOP=true", connURI("test_database")},
 			Env: testExpectedEnv,
 		}).Return(nil)
@@ -309,7 +309,7 @@ func TestImport(t *testing.T) {
 
 		mockExec.Expect("Run", command.ExpectedCommand{
 			Cmd: "hab",
-			Args: []string{"pkg", "exec", "core/postgresql11-client", "psql", "-f", exportFile,
+			Args: []string{"pkg", "exec", pg.Pg13Client, "psql", "-f", exportFile,
 				"-v", "ON_ERROR_STOP=true", connURI("test_database")},
 			Env: testExpectedEnv,
 		}).Return(nil)
@@ -328,7 +328,7 @@ func TestImport(t *testing.T) {
 
 		mockExec.Expect("Run", command.ExpectedCommand{
 			Cmd: "hab",
-			Args: []string{"pkg", "exec", "core/postgresql11-client", "psql", "-f", exportFile,
+			Args: []string{"pkg", "exec", pg.Pg13Client, "psql", "-f", exportFile,
 				"-v", "ON_ERROR_STOP=true", connURI("test_database")},
 			Env: testExpectedEnv,
 		}).Return(errors.New("test-error"))