Repository: chef/automate
Pull request #8046 (2023-07-07 09:31:43)

minor fix
### :nut_and_bolt: Description: What code changed, and why?

<!-- /!\ Please ensure that you are NOT disclosing any customer information without their consent /!\ -->

### :chains: Related Resources

### :+1: Definition of Done

### :athletic_shoe: How to Build and Test the Change

### :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:

- [ ] Is the code clear? *(complicated code or lots of comments--subdivide and use well-named methods, meaningful variable names, etc.)*
- [ ] Consistency checked? *(user notifications, user prompts, visual patterns, code patterns, variable names)*
- [ ] Repeated code blocks eliminated? *(adapt and reuse existing components, blocks, functions, etc.)*
- [ ] Spelling, grammar, typos checked? *(at a minimum use `make spell` in any component directory)*
- [ ] 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]: ### <span aria-hidden="true">👷</span> Deploy Preview for *chef-automate* processing.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | e56469d26420450c5249f67b92b73b7664ea2d72 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/chef-automate/deploys/64a7db824e7f040008fec6da |
sonarqube-for-infrastructure-prod[bot]: **SonarQube Quality Gate**

[![Quality Gate passed](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonar.progress.com/dashboard?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046)

[![Bug](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/common/bug-16px.png 'Bug')](https://sonar.progress.com/project/issues?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&resolved=false&types=BUG) [![A](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/checks/RatingBadge/A-16px.png 'A')](https://sonar.progress.com/project/issues?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&resolved=false&types=BUG) [0 Bugs](https://sonar.progress.com/project/issues?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&resolved=false&types=BUG)  
[![Vulnerability](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/common/vulnerability-16px.png 'Vulnerability')](https://sonar.progress.com/project/issues?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&resolved=false&types=VULNERABILITY) [![A](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/checks/RatingBadge/A-16px.png 'A')](https://sonar.progress.com/project/issues?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonar.progress.com/project/issues?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/common/security_hotspot-16px.png 'Security Hotspot')](https://sonar.progress.com/security_hotspots?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046) [![A](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/checks/RatingBadge/A-16px.png 'A')](https://sonar.progress.com/security_hotspots?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046) [0 Security Hotspots](https://sonar.progress.com/security_hotspots?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046)  
[![Code Smell](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/common/code_smell-16px.png 'Code Smell')](https://sonar.progress.com/project/issues?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&resolved=false&types=CODE_SMELL) [![A](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/checks/RatingBadge/A-16px.png 'A')](https://sonar.progress.com/project/issues?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonar.progress.com/project/issues?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonar.progress.com/component_measures?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&metric=coverage&view=list) No Coverage information  
[![No Duplication information](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonar.progress.com/component_measures?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&metric=duplicated_lines_density&view=list) No Duplication information


sonarqube-for-infrastructure-prod[bot]: **SonarQube Quality Gate**

[![Quality Gate passed](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonar.progress.com/dashboard?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046)

[![Bug](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/common/bug-16px.png 'Bug')](https://sonar.progress.com/project/issues?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&resolved=false&types=BUG) [![A](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/checks/RatingBadge/A-16px.png 'A')](https://sonar.progress.com/project/issues?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&resolved=false&types=BUG) [0 Bugs](https://sonar.progress.com/project/issues?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&resolved=false&types=BUG)  
[![Vulnerability](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/common/vulnerability-16px.png 'Vulnerability')](https://sonar.progress.com/project/issues?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&resolved=false&types=VULNERABILITY) [![A](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/checks/RatingBadge/A-16px.png 'A')](https://sonar.progress.com/project/issues?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonar.progress.com/project/issues?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/common/security_hotspot-16px.png 'Security Hotspot')](https://sonar.progress.com/security_hotspots?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046) [![A](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/checks/RatingBadge/A-16px.png 'A')](https://sonar.progress.com/security_hotspots?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046) [0 Security Hotspots](https://sonar.progress.com/security_hotspots?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046)  
[![Code Smell](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/common/code_smell-16px.png 'Code Smell')](https://sonar.progress.com/project/issues?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&resolved=false&types=CODE_SMELL) [![A](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/checks/RatingBadge/A-16px.png 'A')](https://sonar.progress.com/project/issues?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonar.progress.com/project/issues?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonar.progress.com/component_measures?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&metric=coverage&view=list) No Coverage information  
[![No Duplication information](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v97/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonar.progress.com/component_measures?id=chef_automate_AYeOVEbqJ4YHsO5MtKWC&pullRequest=8046&metric=duplicated_lines_density&view=list) No Duplication information



Patch:
diff --git a/VERSION b/VERSION
index e5abe410c..807a48905 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-4.7.194
\ No newline at end of file
+4.7.202
\ No newline at end of file
diff --git a/components/automate-cli/cmd/chef-automate/automateHAAwsDeployment.go b/components/automate-cli/cmd/chef-automate/automateHAAwsDeployment.go
index a736482b4..6c6d98016 100644
--- a/components/automate-cli/cmd/chef-automate/automateHAAwsDeployment.go
+++ b/components/automate-cli/cmd/chef-automate/automateHAAwsDeployment.go
@@ -5,6 +5,7 @@ import (
 	"crypto/x509"
 	"encoding/pem"
 	"fmt"
+	"net/http"
 	"os"
 	"path/filepath"
 	"strings"
@@ -12,6 +13,8 @@ import (
 	"github.com/chef/automate/components/automate-cli/pkg/status"
 	"github.com/chef/automate/components/automate-deployment/pkg/toml"
 	"github.com/chef/automate/components/local-user-service/password"
+	"github.com/chef/automate/lib/httputils"
+	"github.com/chef/automate/lib/logger"
 	"github.com/chef/automate/lib/platform/command"
 	"github.com/chef/automate/lib/stringutils"
 	ptoml "github.com/pelletier/go-toml"
@@ -19,19 +22,24 @@ import (
 )
 
 const (
-	DEPLOY    = "deploy"
-	PROVISION = "provision"
+	DEPLOY       = "deploy"
+	PROVISION    = "provision"
+	TOKEN_URL    = "http://169.254.169.254/latest/api/token"
+	METADATA_URL = "http://169.254.169.254/latest/meta-data/iam/info"
 )
 
 type awsDeployment struct {
 	config     AwsConfigToml
 	configPath string
 	AWSConfigIp
+	httpRequestClient httputils.HTTPClient
 }
 
 func newAwsDeployemnt(configPath string) *awsDeployment {
+	log := logger.NewLogrusStandardLogger()
 	return &awsDeployment{
-		configPath: configPath,
+		configPath:        configPath,
+		httpRequestClient: httputils.NewClient(log),
 	}
 }
 
@@ -205,7 +213,10 @@ func (a *awsDeployment) validateConfigFields() *list.List {
 func (a *awsDeployment) validateEnvFields() *list.List {
 	errorList := list.New()
 	if len(a.config.Aws.Config.Profile) < 1 {
-		errorList.PushBack("Invalid or empty aws profile name")
+		err := a.isIamRolePresent()
+		if err != nil {
+			errorList.PushBack("Invalid local AWS Profile name or Bastion IAM role, Please Check your local AWS Profile name or Bastion IAM Role is configured properly")
+		}
 	}
 	if len(a.config.Aws.Config.Region) < 1 {
 		errorList.PushBack("Invalid or empty aws region")
@@ -444,3 +455,21 @@ func (a *awsDeployment) getAwsHAIp() error {
 	a.AWSConfigIp = *ConfigIp
 	return nil
 }
+
+func (a *awsDeployment) isIamRolePresent() error {
+	_, tokenResponseBody, err := a.httpRequestClient.MakeRequestWithHeaders(http.MethodPut, TOKEN_URL, nil, "X-aws-ec2-metadata-token-ttl-seconds", "21600")
+	if err != nil {
+		return fmt.Errorf("error while getting the token value: %v", err)
+	}
+
+	token := string(tokenResponseBody)
+
+	resp, _, err := a.httpRequestClient.MakeRequestWithHeaders(http.MethodGet, METADATA_URL, nil, "X-aws-ec2-metadata-token", token)
+	if err != nil {
+		return fmt.Errorf("error while getting the response for IAM role: %v", err)
+	}
+	if resp.StatusCode != 200 {
+		return errors.New("Please check if Bastion has attached an IAM Role to it")
+	}
+	return nil
+}
diff --git a/components/automate-cli/cmd/chef-automate/certRotate.go b/components/automate-cli/cmd/chef-automate/certRotate.go
index c9a940fd4..5f4a696b4 100644
--- a/components/automate-cli/cmd/chef-automate/certRotate.go
+++ b/components/automate-cli/cmd/chef-automate/certRotate.go
@@ -14,8 +14,11 @@ import (
 	"github.com/chef/automate/api/config/deployment"
 	"github.com/chef/automate/components/automate-cli/pkg/docs"
 	"github.com/chef/automate/components/automate-cli/pkg/status"
+	"github.com/chef/automate/components/automate-deployment/pkg/cli"
 	"github.com/chef/automate/lib/io/fileutils"
+	"github.com/chef/automate/lib/logger"
 	"github.com/chef/automate/lib/platform/command"
+	"github.com/chef/automate/lib/sshutils"
 	"github.com/chef/automate/lib/stringutils"
 	"github.com/chef/toml"
 	"github.com/pkg/errors"
@@ -132,7 +135,19 @@ type certRotateFlags struct {
 }
 
 type certRotateFlow struct {
-	FileUtils fileutils.FileUtils
+	fileUtils   fileutils.FileUtils
+	sshUtil     sshutils.SSHUtil
+	writer      *cli.Writer
+	pullConfigs PullConfigs
+}
+
+func NewCertRotateFlow(fileUtils fileutils.FileUtils, sshUtil sshutils.SSHUtil, writer *cli.Writer, pullConfigs PullConfigs) *certRotateFlow {
+	return &certRotateFlow{
+		fileUtils:   fileUtils,
+		sshUtil:     sshUtil,
+		writer:      writer,
+		pullConfigs: pullConfigs,
+	}
 }
 
 type patchFnParameters struct {
@@ -141,6 +156,7 @@ type patchFnParameters struct {
 	fileName      string
 	timestamp     string
 	remoteService string
+	concurrent    bool
 	infra         *AutomateHAInfraDetails
 	flagsObj      *certRotateFlags
 	skipIpsList   []string
@@ -182,7 +198,11 @@ func init() {
 
 func certRotateCmdFunc(flagsObj *certRotateFlags) func(cmd *cobra.Command, args []string) error {
 	return func(cmd *cobra.Command, args []string) error {
-		c := certRotateFlow{FileUtils: &fileutils.FileSystemUtils{}}
+		log, err := logger.NewLogger("text", "info")
+		if err != nil {
+			return err
+		}
+		c := NewCertRotateFlow(&fileutils.FileSystemUtils{}, sshutils.NewSSHUtilWithCommandExecutor(sshutils.NewSshClient(), log, command.NewExecExecutor()), writer, NewPullConfigs(&AutomateHAInfraDetails{}, &SSHUtilImpl{}))
 		return c.certRotate(cmd, args, flagsObj)
 	}
 }
@@ -214,7 +234,7 @@ func (c *certRotateFlow) certRotate(cmd *cobra.Command, args []string, flagsObj
 		currentCertsInfo, err := certShowFlow.fetchCurrentCerts()
 
 		if err != nil {
-			return errors.New("Error occured while fetching current certs.")
+			return errors.Wrap(err, "Error occured while fetching current certs")
 		}
 
 		if flagsObj.timeout < DEFAULT_TIMEOUT {
@@ -266,13 +286,14 @@ func (c *certRotateFlow) certRotateFrontend(sshUtil SSHUtil, certs *certificates
 
 	// Creating and patching the required configurations.
 	config := fmt.Sprintf(FRONTEND_CONFIG, certs.publicCert, certs.privateCert, certs.publicCert, certs.privateCert)
-
+	concurrent := true
 	patchFnParam := &patchFnParameters{
 		sshUtil:       sshUtil,
 		config:        config,
 		fileName:      fileName,
 		timestamp:     timestamp,
 		remoteService: remoteService,
+		concurrent:    concurrent,
 		infra:         infra,
 		flagsObj:      flagsObj,
 		skipIpsList:   skipIpsList,
@@ -305,33 +326,35 @@ func (c *certRotateFlow) certRotatePG(sshUtil SSHUtil, certs *certificates, infr
 
 	skipIpsList := c.compareCurrentCertsWithNewCerts(remoteService, certs, flagsObj, currentCertsInfo)
 	c.skipMessagePrinter(remoteService, SKIP_IPS_MSG_CERT_ROTATE, flagsObj.node, skipIpsList)
-
+	concurrent := false
 	patchFnParam := &patchFnParameters{
 		sshUtil:       sshUtil,
 		config:        config,
 		fileName:      fileName,
 		timestamp:     timestamp,
 		remoteService: remoteService,
+		concurrent:    concurrent,
 		infra:         infra,
 		flagsObj:      flagsObj,
 		skipIpsList:   skipIpsList,
 	}
 
-	//patching on PG
+	// patching on PG
 	err := c.patchConfig(patchFnParam)
 	if err != nil {
 		return err
 	}
-
 	// ignore patching of root-ca when node flag is provided
 	if flagsObj.node != "" {
 		return nil
 	}
-	//get frontend ips to skip root-ca patch
-	skipIpsList = c.getFrontEndIpsForSkippingRootCAPatching(remoteService, certs.rootCA, infra, currentCertsInfo)
+	skipIpsList, err = c.getSkipIpsListForPgRootCAPatching(infra, sshUtil, certs)
+	if err != nil {
+		return err
+	}
 	c.skipMessagePrinter("frontend", SKIP_FRONT_END_IPS_MSG_PG, "", skipIpsList)
 
-	// Patching root-ca to frontend-nodes for maintaining the connection.
+	//Patching root-ca to frontend-nodes for maintaining the connection.
 	filenameFe := "pg_fe.toml"
 	remoteService = "frontend"
 	// Creating and patching the required configurations.
@@ -341,6 +364,7 @@ func (c *certRotateFlow) certRotatePG(sshUtil SSHUtil, certs *certificates, infr
 	patchFnParam.fileName = filenameFe
 	patchFnParam.remoteService = remoteService
 	patchFnParam.skipIpsList = skipIpsList
+	patchFnParam.concurrent = true
 
 	err = c.patchConfig(patchFnParam)
 	if err != nil {
@@ -349,6 +373,30 @@ func (c *certRotateFlow) certRotatePG(sshUtil SSHUtil, certs *certificates, infr
 	return nil
 }
 
+func (c *certRotateFlow) getSkipIpsListForPgRootCAPatching(infra *AutomateHAInfraDetails, sshUtil SSHUtil, certs *certificates) ([]string, error) {
+
+	c.pullConfigs.setInfraAndSSHUtil(infra, sshUtil)
+	//fetching current config from automate
+	automateCurrentConfig, err := c.pullConfigs.pullAutomateConfigs()
+	if err != nil {
+		return nil, err
+	}
+
+	//get frontend ips to skip root-ca patch
+	skipIpsList := c.getFrontendIPsToSkipRootCAPatchingForPg(automateCurrentConfig, certs.rootCA, infra)
+
+	//fetching current config from chefServer
+	chefServerCurrentConfig, err := c.pullConfigs.pullChefServerConfigs()
+
+	if err != nil {
+		return nil, err
+	}
+	newskipIpsList := c.getFrontendIPsToSkipRootCAPatchingForPg(chefServerCurrentConfig, certs.rootCA, infra)
+	skipIpsList = append(skipIpsList, newskipIpsList...)
+
+	return skipIpsList, nil
+}
+
 // certRotateOS will rotate the certificates of OpenSearch.
 func (c *certRotateFlow) certRotateOS(sshUtil SSHUtil, certs *certificates, infra *AutomateHAInfraDetails, flagsObj *certRotateFlags, currentCertsInfo *certShowCertificates) error {
 	if isManagedServicesOn() {
@@ -392,13 +440,14 @@ func (c *certRotateFlow) certRotateOS(sshUtil SSHUtil, certs *certificates, infr
 	} else {
 		config = fmt.Sprintf(OPENSEARCH_CONFIG, certs.rootCA, certs.adminCert, certs.adminKey, certs.publicCert, certs.privateCert, fmt.Sprintf("%v", adminDn), fmt.Sprintf("%v", nodesDn))
 	}
-
+	concurrent := false
 	patchFnParam := &patchFnParameters{
 		sshUtil:       sshUtil,
 		config:        config,
 		fileName:      fileName,
 		timestamp:     timestamp,
 		remoteService: remoteService,
+		concurrent:    concurrent,
 		infra:         infra,
 		flagsObj:      flagsObj,
 		skipIpsList:   skipIpsList,
@@ -421,31 +470,23 @@ func (c *certRotateFlow) certRotateOS(sshUtil SSHUtil, certs *certificates, infr
 		}
 
 	}
-
-	//fetching server_name from automate
-	plgcng := NewPullConfigs(infra, sshUtil)
-	automatesConfig, err := plgcng.pullAutomateConfigs()
-
-	if err != nil {
-		return err
-	}
-
-	oldCn := getOldCn(automatesConfig)
-
 	// Patching root-ca to frontend-nodes for maintaining the connection.
-	cn := nodesCn
 	filenameFe := "os_fe.toml"
 	remoteService = "frontend"
 
-	skipIpsList = c.getFrontEndIpsForSkippingCnAndRootCaPatching(certs.rootCA, cn, oldCn, flagsObj.node, currentCertsInfo, infra)
+	skipIpsList, err = c.getSkipIpsListForOsRootCACNPatching(infra, sshUtil, certs, nodesCn, flagsObj)
+	if err != nil {
+		return err
+	}
 	skipMessage := ""
+
 	// Creating and patching the required configurations.
 	var configFe string
 	if flagsObj.node != "" {
-		configFe = fmt.Sprintf(OPENSEARCH_FRONTEND_CONFIG_IGNORE_ROOT_CERT, cn)
+		configFe = fmt.Sprintf(OPENSEARCH_FRONTEND_CONFIG_IGNORE_ROOT_CERT, nodesCn)
 		skipMessage = SKIP_FRONT_END_IPS_MSG_CN
 	} else {
-		configFe = fmt.Sprintf(OPENSEARCH_FRONTEND_CONFIG, certs.rootCA, cn)
+		configFe = fmt.Sprintf(OPENSEARCH_FRONTEND_CONFIG, certs.rootCA, nodesCn)
 		skipMessage = SKIP_FRONT_END_IPS_MSG_OS
 	}
 	c.skipMessagePrinter(remoteService, skipMessage, "", skipIpsList)
@@ -454,6 +495,7 @@ func (c *certRotateFlow) certRotateOS(sshUtil SSHUtil, certs *certificates, infr
 	patchFnParam.fileName = filenameFe
 	patchFnParam.remoteService = remoteService
 	patchFnParam.skipIpsList = skipIpsList
+	patchFnParam.concurrent = true
 
 	err = c.patchConfig(patchFnParam)
 	if err != nil {
@@ -462,6 +504,32 @@ func (c *certRotateFlow) certRotateOS(sshUtil SSHUtil, certs *certificates, infr
 	return nil
 }
 
+func (c *certRotateFlow) getSkipIpsListForOsRootCACNPatching(infra *AutomateHAInfraDetails, sshUtil SSHUtil, certs *certificates, nodesCn string, flagsObj *certRotateFlags) ([]string, error) {
+
+	c.pullConfigs.setInfraAndSSHUtil(infra, sshUtil)
+	//fetching current config from automate
+	automateCurrentConfig, err := c.pullConfigs.pullAutomateConfigs()
+	if err != nil {
+		return nil, err
+	}
+
+	// Patching root-ca to frontend-nodes for maintaining the connection.
+	cn := nodesCn
+
+	skipIpsList := c.getFrontIpsToSkipRootCAandCNPatchingForOs(automateCurrentConfig, certs.rootCA, cn, flagsObj.node, infra)
+
+	//fetching current config from chefServer
+	chefServerCurrentConfig, err := c.pullConfigs.pullChefServerConfigs()
+
+	if err != nil {
+		return nil, err
+	}
+	newskipIpsList := c.getFrontIpsToSkipRootCAandCNPatchingForOs(chefServerCurrentConfig, certs.rootCA, cn, flagsObj.node, infra)
+	skipIpsList = append(skipIpsList, newskipIpsList...)
+
+	return skipIpsList, nil
+}
+
 func patchOSNodeDN(flagsObj *certRotateFlags, patchFnParam *patchFnParameters, c *certRotateFlow, nodesDn string) error {
 
 	peerConfig := fmt.Sprintf(OPENSEARCH_DN_CONFIG_FOR_PEERS, fmt.Sprintf("%v", nodesDn))
@@ -473,7 +541,7 @@ func patchOSNodeDN(flagsObj *certRotateFlags, patchFnParam *patchFnParameters, c
 	patchFnParam.config = peerConfig
 	patchFnParam.timestamp = time.Now().Format("20060102150405")
 	patchFnParam.skipIpsList = []string{flagsObj.node}
-
+	patchFnParam.concurrent = false
 	err := c.patchConfig(patchFnParam)
 	if err != nil {
 		return err
@@ -483,22 +551,60 @@ func patchOSNodeDN(flagsObj *certRotateFlags, patchFnParam *patchFnParameters, c
 	return nil
 }
 
-func getOldCn(automatesConfig map[string]*deployment.AutomateConfig) string {
+func (c *certRotateFlow) getFrontIpsToSkipRootCAandCNPatchingForOs(automatesConfig map[string]*deployment.AutomateConfig, newRootCA string, newCn string, node string, infra *AutomateHAInfraDetails) []string {
+	oldRootCA := ""
 	oldCn := ""
-	for _, config := range automatesConfig {
+	skipIpsList := []string{}
+	for ip, config := range automatesConfig {
 		if config.Global.V1.External.Opensearch != nil && config.Global.V1.External.Opensearch.Ssl != nil {
 			if config.Global.V1.External.Opensearch.Ssl.ServerName != nil {
 				oldCn = config.Global.V1.External.Opensearch.Ssl.ServerName.Value
-				break
+				if c.getFrontEndIpsForSkippingCnAndRootCaPatching(newRootCA, newCn, oldCn, oldRootCA, node) {
+					if !isStringPresent(skipIpsList, ip) {
+						// If not present, append the string to the array
+						skipIpsList = append(skipIpsList, ip)
+					}
+				}
+			}
+			if config.Global.V1.External.Opensearch.Ssl.RootCert != nil {
+				oldRootCA = config.Global.V1.External.Opensearch.Ssl.RootCert.Value
+				if c.getFrontEndIpsForSkippingCnAndRootCaPatching(newRootCA, newCn, oldCn, oldRootCA, node) {
+					if !isStringPresent(skipIpsList, ip) {
+						// If not present, append the string to the array
+						skipIpsList = append(skipIpsList, ip)
+					}
+				}
+			}
+		}
+	}
+	return skipIpsList
+}
+
+func isStringPresent(arr []string, str string) bool {
+	for _, s := range arr {
+		if s == str {
+			return true
+		}
+	}
+	return false
+}
+
+func (c *certRotateFlow) getFrontendIPsToSkipRootCAPatchingForPg(automatesConfig map[string]*deployment.AutomateConfig, newRootCA string, infra *AutomateHAInfraDetails) []string {
+	oldRootCA := ""
+	skipIpsList := []string{}
+	for ip, config := range automatesConfig {
+		if config.Global.V1.External.Postgresql.Ssl.RootCert != nil {
+			oldRootCA = config.Global.V1.External.Postgresql.Ssl.RootCert.Value
+			if oldRootCA == newRootCA {
+				skipIpsList = append(skipIpsList, ip)
 			}
 		}
 	}
-	return oldCn
+	return skipIpsList
 }
 
 // patchConfig will patch the configurations to required nodes.
 func (c *certRotateFlow) patchConfig(param *patchFnParameters) error {
-
 	f, err := os.Create(param.fileName)
 	if err != nil {
 		return err
@@ -508,7 +614,6 @@ func (c *certRotateFlow) patchConfig(param *patchFnParameters) error {
 		return err
 	}
 	f.Close()
-
 	var ips []string
 	if param.flagsObj.node != "" && param.remoteService != "frontend" {
 		isValid := c.validateEachIp(param.remoteService, param.infra, param.flagsObj)
@@ -525,24 +630,46 @@ func (c *certRotateFlow) patchConfig(param *patchFnParameters) error {
 
 	//collect ips on which need to perform action/cert-rotation
 	filteredIps := c.getFilteredIps(ips, param.skipIpsList)
+	filteredIpsString := strings.Join(filteredIps, ", ")
+	sshConfig := c.getSshDetails(param.infra)
+	sshConfig.hostIP = filteredIpsString
+	sshConfig.timeout = param.flagsObj.timeout
+	configRes := sshutils.SSHConfig{
+		SshUser:    sshConfig.sshUser,
+		SshPort:    sshConfig.sshPort,
+		SshKeyFile: sshConfig.sshKeyFile,
+		HostIP:     sshConfig.hostIP,
+		Timeout:    sshConfig.timeout,
+	}
 
-	// Defining set of commands which run on particular remoteservice nodes
 	var scriptCommands string
+	command := getScriptCommands(param, scriptCommands)
+	if !param.concurrent {
+		err = c.copyAndExecute(filteredIps, param.sshUtil, param.timestamp, param.remoteService, param.fileName, scriptCommands, param.flagsObj)
+		if err != nil {
+			return err
+		}
+	} else {
+		err = c.copyAndExecuteConcurrentlyToFrontEndNodes(filteredIps, configRes, param.timestamp, param.remoteService, param.fileName, command, param.flagsObj)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// Defining set of commands which run on particular remoteservice nodes
+func getScriptCommands(param *patchFnParameters, scriptCommands string) string {
 	if param.remoteService == AUTOMATE || param.remoteService == CHEF_SERVER || param.remoteService == "frontend" {
 		scriptCommands = fmt.Sprintf(FRONTEND_COMMAND, PATCH, param.remoteService+param.timestamp, DATE_FORMAT)
 	} else if param.remoteService == POSTGRESQL || param.remoteService == OPENSEARCH {
 		scriptCommands = fmt.Sprintf(COPY_USER_CONFIG, param.remoteService+param.timestamp, param.remoteService)
 	}
-	err = c.copyAndExecute(filteredIps, param.sshUtil, param.timestamp, param.remoteService, param.fileName, scriptCommands, param.flagsObj)
-	if err != nil {
-		return err
-	}
-	return nil
+	return scriptCommands
 }
 
-// copyAndExecute will copy the toml file to each required node and then execute the set of commands.
 func (c *certRotateFlow) copyAndExecute(ips []string, sshUtil SSHUtil, timestamp string, remoteService string, fileName string, scriptCommands string, flagsObj *certRotateFlags) error {
-
 	var err error
 	var tomlFilePath string
 	for i := 0; i < len(ips); i++ {
@@ -574,6 +701,53 @@ func (c *certRotateFlow) copyAndExecute(ips []string, sshUtil SSHUtil, timestamp
 	return nil
 }
 
+// copyAndExecuteConcurrently will copy the toml file to each required nodes concurrently and then execute the set of commands.
+func (c *certRotateFlow) copyAndExecuteConcurrentlyToFrontEndNodes(ips []string, sshConfig sshutils.SSHConfig, timestamp string, remoteService string, fileName string, scriptCommands string, flagsObj *certRotateFlags) error {
+
+	sshConfig.Timeout = flagsObj.timeout
+	copyResults := c.sshUtil.CopyFileToRemoteConcurrently(sshConfig, fileName, remoteService+timestamp, false, ips)
+	isError := false
+	for _, result := range copyResults {
+		if result.Error != nil {
+			c.writer.Errorf("Remote copying automate-verify CLI failed on node : %s with error: %v\n", result.HostIP, result.Error)
+			isError = true
+		}
+	}
+	if isError {
+		return fmt.Errorf("remote copying failed on node")
+	}
+
+	fmt.Printf("\nStarted Applying the Configurations in %s node: %s \n", remoteService, ips)
+	excuteResults := c.sshUtil.ExecuteConcurrently(sshConfig, scriptCommands, ips)
+	for _, result := range excuteResults {
+		printCertRotateOutput(result, remoteService, writer)
+	}
+	return nil
+}
+
+func printCertRotateOutput(cmdResult sshutils.Result, remoteService string, writer *cli.Writer) {
+	writer.Printf("\n=======================================================================\n")
+
+	if cmdResult.Error != nil || strings.Contains(cmdResult.Output, "DeploymentServiceCallError") {
+		printCertRotateErrorOutput(cmdResult, remoteService, writer)
+	} else {
+		writer.Printf("Output for Host IP %s : \n%s", cmdResult.HostIP, cmdResult.Output+"\n")
+		writer.Success("Command is executed on node : " + cmdResult.HostIP + "\n")
+	}
+	writer.BufferWriter().Flush()
+}
+
+func printCertRotateErrorOutput(cmdResult sshutils.Result, remoteService string, writer *cli.Writer) {
+	isOutputError := false
+	if strings.Contains(cmdResult.Output, "DeploymentServiceCallError") {
+		isOutputError = true
+		writer.Failf(CMD_FAILED_MSG, remoteService, cmdResult.HostIP, cmdResult.Output)
+	}
+	if !isOutputError {
+		writer.Failf(CMD_FAILED_MSG, remoteService, cmdResult.HostIP, cmdResult.Error.Error())
+	}
+}
+
 // validateEachIp validate given ip with the remoteService cluster.
 func (c *certRotateFlow) validateEachIp(remoteService string, infra *AutomateHAInfraDetails, flagsObj *certRotateFlags) bool {
 	ips := c.getIps(remoteService, infra)
@@ -694,30 +868,11 @@ func (c *certRotateFlow) comparePublicCertAndPrivateCert(newCerts *certificates,
 	return skipIpsList
 }
 
-// getFrontEndIpsForSkippingRootCAPatching compare new root-ca and current root-ca of remoteService and returns ips to skip root-ca patching.
-func (c *certRotateFlow) getFrontEndIpsForSkippingRootCAPatching(remoteService string, newRootCA string, infra *AutomateHAInfraDetails, currentCertsInfo *certShowCertificates) []string {
-	skipIpsList := []string{}
-
-	if remoteService == POSTGRESQL {
-		if strings.TrimSpace(currentCertsInfo.PostgresqlRootCert) == newRootCA {
-			skipIpsList = append(skipIpsList, c.getIps("frontend", infra)...)
-		}
-	}
-
-	if remoteService == AUTOMATE {
-		if strings.TrimSpace(currentCertsInfo.AutomateRootCert) == newRootCA {
-			skipIpsList = append(skipIpsList, c.getIps(CHEF_SERVER, infra)...)
-		}
-	}
-
-	return skipIpsList
-}
-
 // getFrontEndIpsForSkippingCnAndRootCaPatching compare new root-ca and new cn with current root-ca and cn and returns ips to skip root-ca patching.
-func (c *certRotateFlow) getFrontEndIpsForSkippingCnAndRootCaPatching(newRootCA, newCn, oldCn, node string, currentCertsInfo *certShowCertificates, infra *AutomateHAInfraDetails) []string {
+func (c *certRotateFlow) getFrontEndIpsForSkippingCnAndRootCaPatching(newRootCA, newCn, oldCn, oldRootCA, node string) bool {
 	isRootCaSame := false
 
-	if strings.TrimSpace(currentCertsInfo.OpensearchRootCert) == newRootCA {
+	if strings.TrimSpace(oldRootCA) == newRootCA {
 		isRootCaSame = true
 	}
 
@@ -728,14 +883,14 @@ func (c *certRotateFlow) getFrontEndIpsForSkippingCnAndRootCaPatching(newRootCA,
 	}
 
 	if node == "" && isRootCaSame && isCnSame {
-		return c.getIps("frontend", infra)
+		return true
 	}
 
 	if node != "" && isCnSame {
-		return c.getIps("frontend", infra)
+		return true
 	}
 
-	return []string{}
+	return false
 }
 
 // skipMessagePrinter print the skip message
@@ -885,7 +1040,7 @@ func (c *certRotateFlow) getCertFromFile(certPath string, infra *AutomateHAInfra
 		}
 		return []byte(out), nil
 	}
-	return c.FileUtils.ReadFile(certPath)
+	return c.fileUtils.ReadFile(certPath)
 }
 
 // GetRemoteFileDetails returns the remote file details from the remotePath.
@@ -951,7 +1106,6 @@ func (c *certRotateFlow) getMerger(fileName string, timestamp string, remoteType
 	if err != nil {
 		return "", err
 	}
-
 	var (
 		dest interface{}
 		err1 error
diff --git a/components/automate-cli/cmd/chef-automate/initConfigHaTmpl.go b/components/automate-cli/cmd/chef-automate/initConfigHaTmpl.go
index 21a75e243..e816f7fa0 100644
--- a/components/automate-cli/cmd/chef-automate/initConfigHaTmpl.go
+++ b/components/automate-cli/cmd/chef-automate/initConfigHaTmpl.go
@@ -154,7 +154,7 @@ enable_custom_certs = false
 # ============== AWS network Config ============================
 ## === INPUT NEEDED ===
 
-# Eg.: profile = "default"
+# Eg.: profile = "default", Leave this field empty if you have an IAM role attached to the bastion machine.
 profile = ""
 
 # Eg.: region = "us-east-1"
diff --git a/components/automate-cli/cmd/chef-automate/pullAndGenerateConfig.go b/components/automate-cli/cmd/chef-automate/pullAndGenerateConfig.go
index de3d5a755..a178fedfb 100644
--- a/components/automate-cli/cmd/chef-automate/pullAndGenerateConfig.go
+++ b/components/automate-cli/cmd/chef-automate/pullAndGenerateConfig.go
@@ -197,6 +197,7 @@ type PullConfigs interface {
 	getExceptionIps() []string
 	setExceptionIps(ips []string)
 	getOsCertsByIp(map[string]*ConfigKeys) []CertByIP
+	setInfraAndSSHUtil(*AutomateHAInfraDetails, SSHUtil)
 }
 
 type PullConfigsImpl struct {
@@ -216,6 +217,11 @@ func (p *PullConfigsImpl) getExceptionIps() []string {
 	return p.exceptionIps
 }
 
+func (p *PullConfigsImpl) setInfraAndSSHUtil(infra *AutomateHAInfraDetails, sshUtil SSHUtil) {
+	p.infra = infra
+	p.sshUtil = sshUtil
+}
+
 func (p *PullConfigsImpl) setExceptionIps(ips []string) {
 	p.exceptionIps = ips
 }
diff --git a/components/automate-cli/cmd/chef-automate/sshUtils.go b/components/automate-cli/cmd/chef-automate/sshUtils.go
index a13ae10cb..07f182b26 100644
--- a/components/automate-cli/cmd/chef-automate/sshUtils.go
+++ b/components/automate-cli/cmd/chef-automate/sshUtils.go
@@ -11,15 +11,21 @@ import (
 	"path/filepath"
 	"regexp"
 	"strings"
+	"sync"
 	"time"
 	"unicode"
 
+	"github.com/chef/automate/lib/io/fileutils"
+	"github.com/chef/automate/lib/sshutils"
+	"github.com/chef/automate/lib/stringutils"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/crypto/ssh/knownhosts"
 )
 
+var mutex sync.Mutex
+
 type SSHConfig struct {
 	sshUser    string
 	sshPort    string
@@ -83,12 +89,36 @@ func (s *SSHUtilImpl) getClientConfig() (*ssh.ClientConfig, error) {
 	var (
 		keyErr *knownhosts.KeyError
 	)
+	homePath := os.Getenv("HOME")
+	if homePath == "" {
+		errStr := "Environment variable HOME cannot be empty. Please set a value for HOME env"
+		writer.Errorln(errStr)
+		writer.BufferWriter().Flush()
+		return nil, errors.New(errStr)
+	}
+	sshDirPath := filepath.Join(os.Getenv("HOME"), ".ssh")
+	exists, err := fileutils.PathExists(sshDirPath)
+	if err != nil {
+		errStr := fmt.Sprintf("Error evaluating path: %v\n", err)
+		writer.Errorln(errStr)
+		writer.BufferWriter().Flush()
+		return nil, errors.New(errStr)
+	}
+	if !exists {
+		errStr := fmt.Sprintf("Path %s does not exist", sshDirPath)
+		writer.Errorln(errStr)
+		writer.BufferWriter().Flush()
+		return nil, errors.New(errStr)
+	}
 	// Client config
 	return &ssh.ClientConfig{
 		User: s.SshConfig.sshUser,
 		Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
 		HostKeyCallback: ssh.HostKeyCallback(func(host string, remote net.Addr, pubKey ssh.PublicKey) error {
-			kh := checkKnownHosts()
+			mutex.Lock()
+			defer mutex.Unlock()
+			knownHostPath := filepath.Join(sshDirPath, sshutils.AUTOMATE_KNOWN_HOSTS)
+			kh := checkKnownHosts(knownHostPath)
 			hErr := kh(host, remote, pubKey)
 			// Reference: https://blog.golang.org/go1.13-errors
 			// To understand what errors.As is.
@@ -101,7 +131,7 @@ func (s *SSHUtilImpl) getClientConfig() (*ssh.ClientConfig, error) {
 			} else if errors.As(hErr, &keyErr) && len(keyErr.Want) == 0 {
 				// host key not found in known_hosts then give a warning and continue to connect.
 				// writer.Printf("WARNING: %s is not trusted, adding this key to known_hosts file.\n", host)
-				return addHostKey(host, remote, pubKey)
+				return addHostKey(host, knownHostPath, remote, pubKey)
 			}
 			// writer.Printf("Pub key exists for %s.\n", host)
 			return nil
@@ -129,8 +159,8 @@ func (s *SSHUtilImpl) getConnection() (*ssh.Client, error) {
 	return conn, err
 }
 
-func createKnownHosts() {
-	f, fErr := os.OpenFile(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"), os.O_CREATE, 0600)
+func createKnownHosts(knownHostPath string) {
+	f, fErr := os.OpenFile(knownHostPath, os.O_CREATE, 0600)
 	if fErr != nil {
 		writer.Errorf("%v", fErr)
 		return
@@ -138,9 +168,9 @@ func createKnownHosts() {
 	f.Close()
 }
 
-func checkKnownHosts() ssh.HostKeyCallback {
-	createKnownHosts()
-	kh, e := knownhosts.New(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"))
+func checkKnownHosts(knownHostPath string) ssh.HostKeyCallback {
+	createKnownHosts(knownHostPath)
+	kh, e := knownhosts.New(knownHostPath)
 	if e != nil {
 		writer.Errorf("%v", e)
 		return nil
@@ -148,10 +178,17 @@ func checkKnownHosts() ssh.HostKeyCallback {
 	return kh
 }
 
-func addHostKey(host string, remote net.Addr, pubKey ssh.PublicKey) error {
+func addHostKey(host, khFilePath string, remote net.Addr, pubKey ssh.PublicKey) error {
+
+	content, err := os.ReadFile(khFilePath)
+	if err != nil {
+		writer.Errorf("Error while reading the known hosts file:%v", err)
+		return err
+	}
+	lastLine := stringutils.GetLastLine(string(content))
+
 	// add host key if host is not found in known_hosts, error object is return, if nil then connection proceeds,
 	// if not nil then connection stops.
-	khFilePath := filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts")
 
 	f, fErr := os.OpenFile(khFilePath, os.O_APPEND|os.O_WRONLY, 0600)
 	if fErr != nil {
@@ -160,9 +197,12 @@ func addHostKey(host string, remote net.Addr, pubKey ssh.PublicKey) error {
 	defer f.Close()
 
 	knownHosts := knownhosts.Normalize(remote.String())
-	_, fileErr := f.WriteString(knownhosts.Line([]string{knownHosts}, pubKey))
-	f.WriteString("\n")
-	return fileErr
+	line := knownhosts.Line([]string{knownHosts}, pubKey)
+	if lastLine != "" {
+		line = "\n" + line
+	}
+	_, err = f.WriteString(line)
+	return err
 }
 
 func (s *SSHUtilImpl) connectAndExecuteCommandOnRemote(remoteCommands string, spinner bool) (string, error) {
diff --git a/components/automate-cli/cmd/chef-automate/status.go b/components/automate-cli/cmd/chef-automate/status.go
index 2018c904c..1d23a8586 100644
--- a/components/automate-cli/cmd/chef-automate/status.go
+++ b/components/automate-cli/cmd/chef-automate/status.go
@@ -56,6 +56,7 @@ type BeStatusValue struct {
 	process     string
 	upTime      string
 	role        string
+	lag         string
 }
 
 type FeStatus []FeStatusValue
@@ -196,7 +197,8 @@ func executeStatusSummary(cmd *cobra.Command, args []string, statusSummaryCmdFla
 		return err
 	}
 	sshUtil := NewSSHUtil(&SSHConfig{})
-	statusSummary := NewStatusSummary(infra, FeStatus{}, BeStatus{}, 10, time.Second, statusSummaryCmdFlags, sshUtil)
+	remoteCmdExecutor := NewRemoteCmdExecutorWithoutNodeMap(sshUtil, writer)
+	statusSummary := NewStatusSummary(infra, FeStatus{}, BeStatus{}, 10, time.Second, statusSummaryCmdFlags, remoteCmdExecutor)
 	err = statusSummary.Prepare()
 	if err != nil {
 		return err
diff --git a/components/automate-cli/cmd/chef-automate/summary.go b/components/automate-cli/cmd/chef-automate/summary.go
index 1f60b4bda..8863f80c0 100644
--- a/components/automate-cli/cmd/chef-automate/summary.go
+++ b/components/automate-cli/cmd/chef-automate/summary.go
@@ -4,6 +4,7 @@ import (
 	"container/list"
 	"encoding/json"
 	"fmt"
+	"regexp"
 	"strconv"
 	"strings"
 	"time"
@@ -30,6 +31,7 @@ const (
 	initialHealth               = "ERROR"
 	initialFormattedDuration    = "0d 0h 0m 0s"
 	initialRole                 = "Unknown"
+	initialLag                  = "NA"
 	defaultServiceDetails       = "DefaultServiceDetails"
 	defaultServiceHealthDetails = "DefaultServiceHealthDetails"
 	censusDetails               = "CensusDetails"
@@ -52,7 +54,7 @@ type Summary struct {
 	spinnerTimeout        time.Duration
 	infra                 *AutomateHAInfraDetails
 	statusSummaryCmdFlags *StatusSummaryCmdFlags
-	sshUtil               SSHUtil
+	remoteCmdExecutor     RemoteCmdExecutor
 }
 
 type A2haHabitatAutoTfvars struct {
@@ -60,7 +62,7 @@ type A2haHabitatAutoTfvars struct {
 	HabSupRingKey              string `json:"hab_sup_ring_key"`
 }
 
-func NewStatusSummary(infra *AutomateHAInfraDetails, feStatus FeStatus, beStatus BeStatus, timeout int64, spinnerTimeout time.Duration, flags *StatusSummaryCmdFlags, sshUtil SSHUtil) StatusSummary {
+func NewStatusSummary(infra *AutomateHAInfraDetails, feStatus FeStatus, beStatus BeStatus, timeout int64, spinnerTimeout time.Duration, flags *StatusSummaryCmdFlags, remoteCmdExecutor RemoteCmdExecutor) StatusSummary {
 	return &Summary{
 		feStatus:              feStatus,
 		beStatus:              beStatus,
@@ -68,7 +70,7 @@ func NewStatusSummary(infra *AutomateHAInfraDetails, feStatus FeStatus, beStatus
 		spinnerTimeout:        spinnerTimeout,
 		infra:                 infra,
 		statusSummaryCmdFlags: flags,
-		sshUtil:               sshUtil,
+		remoteCmdExecutor:     remoteCmdExecutor,
 	}
 }
 
@@ -196,6 +198,7 @@ func (ss *Summary) automateNodeMap(automateIps []string) *NodeTypeAndCmd {
 	return automateNodeMap
 }
 
+// returns automateIps, chefServerIps, opensearchIps, postgresqlIps, errList
 func (ss *Summary) getIPAddressesFromFlagOrInfra() ([]string, []string, []string, []string, *list.List) {
 	errorList := list.New()
 	if ss.statusSummaryCmdFlags.node != "" {
@@ -343,8 +346,7 @@ func (ss *Summary) prepareBEScript(serviceIps []string, nodeMap *NodeTypeAndCmd,
 		nodeMap.Postgresql.CmdInputs.MutipleCmdWithArgs = script
 	}
 
-	cmdUtil := NewRemoteCmdExecutor(nodeMap, ss.sshUtil, writer)
-	beOutput, err := cmdUtil.Execute()
+	beOutput, err := ss.remoteCmdExecutor.ExecuteWithNodeMap(nodeMap)
 	if err != nil {
 		return err
 	}
@@ -358,7 +360,6 @@ func (ss *Summary) prepareBEScript(serviceIps []string, nodeMap *NodeTypeAndCmd,
 }
 
 func (ss *Summary) prepareFEScript(serviceIps []string, nodeMap *NodeTypeAndCmd, serviceName, serviceType string) error {
-	cmdUtil := NewRemoteCmdExecutor(nodeMap, ss.sshUtil, writer)
 
 	// Status Script
 	status := GenerateOriginalAutomateCLICommand(
@@ -392,7 +393,7 @@ func (ss *Summary) prepareFEScript(serviceIps []string, nodeMap *NodeTypeAndCmd,
 		nodeMap.ChefServer.CmdInputs.MutipleCmdWithArgs = script
 	}
 
-	feOutput, err := cmdUtil.Execute()
+	feOutput, err := ss.remoteCmdExecutor.ExecuteWithNodeMap(nodeMap)
 	if err != nil {
 		return err
 	}
@@ -431,6 +432,7 @@ func (ss *Summary) getBEStatus(outputs []*CmdResult, ip string, authToken, servi
 		process:     fmt.Sprintf("%s (pid: %s)", initialServiceState, initialServicePid),
 		upTime:      initialFormattedDuration,
 		role:        initialRole,
+		lag:         initialLag,
 	}
 
 	for _, output := range outputs {
@@ -451,6 +453,11 @@ func (ss *Summary) getBEStatus(outputs []*CmdResult, ip string, authToken, servi
 		return defaultBeStatusValue
 	}
 
+	lag, err := ss.getFollowerLag(cmdResMap[defaultServiceHealthDetails].Output, serviceName, role)
+	if err != nil {
+		lag = "Error"
+	}
+
 	return BeStatusValue{
 		serviceName: serviceName,
 		ipAddress:   ip,
@@ -458,6 +465,7 @@ func (ss *Summary) getBEStatus(outputs []*CmdResult, ip string, authToken, servi
 		process:     fmt.Sprintf("%s (pid: %s)", serviceState, servicePid),
 		upTime:      formattedDuration,
 		role:        role,
+		lag:         lag,
 	}
 }
 
@@ -516,6 +524,30 @@ func (ss *Summary) getBECensus(output, service, memeberId string) (string, error
 	return role, nil
 }
 
+func (ss *Summary) getFollowerLag(output, service, role string) (string, error) {
+	lag := initialLag
+	outputMap, err := parseStringInToMapStringInterface(output)
+	if err != nil {
+		return lag, err
+	}
+
+	if service == postgresql {
+		if role == "Follower" {
+			// Define the regular expression pattern to match the substring
+			pattern := `(\d+ bytes and \d+ seconds)`
+
+			// Compile the regular expression pattern
+			regExp := regexp.MustCompile(pattern)
+
+			// Find the first matching substring of the stdout string
+			lag = regExp.FindString(outputMap["stdout"].(string))
+
+			return lag, nil
+		}
+	}
+	return lag, nil
+}
+
 func (ss *Summary) getFEStatus(ip string, outputs []*CmdResult, serviceType string) FeStatusValue {
 	var osStatus, status string
 
@@ -535,12 +567,13 @@ func (ss *Summary) getFEStatus(ip string, outputs []*CmdResult, serviceType stri
 		}
 	}
 
-	return FeStatusValue{
+	feStatusValue := FeStatusValue{
 		serviceName: serviceType,
 		ipAddress:   ip,
 		status:      status,
 		Opensearch:  osStatus,
 	}
+	return feStatusValue
 }
 
 func (ss *Summary) opensearchStatusInFE(osStatusOutput *CmdResult) string {
@@ -630,9 +663,9 @@ func (ss *Summary) ShowBEStatus() string {
 		tTemp := table.Table{}
 		tTemp.Render()
 		for _, status := range ss.beStatus {
-			t.AppendRow(table.Row{status.serviceName, status.ipAddress, status.health, status.process, status.upTime, status.role})
+			t.AppendRow(table.Row{status.serviceName, status.ipAddress, status.health, status.process, status.lag, status.upTime, status.role})
 		}
-		t.AppendHeader(table.Row{"Name", "IP Address", "Health", "Process", "Uptime", "Role"})
+		t.AppendHeader(table.Row{"Name", "IP Address", "Health", "Process", "Lag", "Uptime", "Role"})
 		return t.Render()
 	}
 	return ""
diff --git a/components/automate-cli/cmd/chef-automate/tfvarsToJson.go b/components/automate-cli/cmd/chef-automate/tfvarsToJson.go
index d25e81191..f184df6af 100644
--- a/components/automate-cli/cmd/chef-automate/tfvarsToJson.go
+++ b/components/automate-cli/cmd/chef-automate/tfvarsToJson.go
@@ -12,18 +12,18 @@ import (
 
 func convTfvarToJson(filename string) string {
 	if len(strings.TrimSpace(filename)) < 1 {
-		writer.Printf("Invalid or empty argument")
+		writer.Println("Invalid or empty argument")
 		return ""
 	}
 	src, err := ioutil.ReadFile(filename) // nosemgrep
 	if err != nil {
-		writer.Printf("Error reading %q: %s", filename, err)
+		writer.Printf("Error reading %q: %s\n", filename, err)
 		return ""
 	}
 
 	f, diags := hclsyntax.ParseConfig(src, filename, hcl.Pos{Line: 1, Column: 1})
 	if diags.HasErrors() {
-		writer.Printf("Error parsing %q: %s", filename, diags.Error())
+		writer.Printf("Error parsing %q: %s\n", filename, diags.Error())
 		return ""
 	}
 	if f.Body == nil {
@@ -33,7 +33,7 @@ func convTfvarToJson(filename string) string {
 
 	attrs, diags := f.Body.JustAttributes()
 	if diags.HasErrors() {
-		writer.Printf("Error evaluating %q: %s", filename, diags.Error())
+		writer.Printf("Error evaluating %q: %s\n", filename, diags.Error())
 		return ""
 	}
 
@@ -41,12 +41,12 @@ func convTfvarToJson(filename string) string {
 	for name, attr := range attrs {
 		value, diags := attr.Expr.Value(nil)
 		if diags.HasErrors() {
-			writer.Printf("Error evaluating %q in %q: %s", name, filename, diags.Error())
+			writer.Printf("Error evaluating %q in %q: %s\n", name, filename, diags.Error())
 			return ""
 		}
 		buf, err := ctyjson.Marshal(value, value.Type())
 		if err != nil {
-			writer.Printf("Error converting %q in %q to JSON: %s", name, filename, err)
+			writer.Printf("Error converting %q in %q to JSON: %s\n", name, filename, err)
 			return ""
 		}
 		values[name] = json.RawMessage(buf)
@@ -54,7 +54,7 @@ func convTfvarToJson(filename string) string {
 
 	output, err := json.MarshalIndent(values, "", "  ")
 	if err != nil {
-		writer.Printf("Error marshalling %q to JSON: %s", filename, err)
+		writer.Printf("Error marshalling %q to JSON: %s\n", filename, err)
 		return ""
 	}
 	return string(output)
diff --git a/components/automate-cli/cmd/chef-automate/verify.go b/components/automate-cli/cmd/chef-automate/verify.go
index 41db17033..85e4f3c9f 100644
--- a/components/automate-cli/cmd/chef-automate/verify.go
+++ b/components/automate-cli/cmd/chef-automate/verify.go
@@ -316,7 +316,7 @@ func (v *verifyCmdFlow) RunVerify(config string) error {
 		v.sshPort, v.sshKeyFile, v.sshUserName = v.getSSHConfig(v.Config.Architecture.Aws)
 		configDetails, err := fetchAwsConfigFromTerraform()
 		if err != nil {
-			return status.New(status.ConfigVerifyError, err.Error())
+			return status.Wrap(errors.New("Cannot run verify command before provision in AWS deployment.\nPlease run 'chef-automate provision-infra config.toml --airgap-bundle automate.aib' to provision your infra"), status.ConfigVerifyError, err.Error())
 		}
 
 		v.automateIPs = configDetails.AutomateIps
diff --git a/components/automate-cli/pkg/verifyserver/models/batchcheck.go b/components/automate-cli/pkg/verifyserver/models/batchcheck.go
index f855bb31f..d0ba3043a 100644
--- a/components/automate-cli/pkg/verifyserver/models/batchcheck.go
+++ b/components/automate-cli/pkg/verifyserver/models/batchcheck.go
@@ -87,6 +87,7 @@ type ExternalPG struct {
 type Config struct {
 	SSHUser         *SSHUser       `json:"ssh_user"`
 	Arch            string         `json:"arch"`
+	Profile         string         `json:"profile"`
 	Backup          *Backup        `json:"backup"`
 	Hardware        *Hardware      `json:"hardware"`
 	ExternalDbType  string         `json:"external_db_type"`
@@ -280,14 +281,19 @@ func (c *Config) populateAwsS3BucketName(haConfig *config.HaDeployConfig) {
 				BucketName: haConfig.Architecture.Aws.S3BucketName,
 			},
 		}
-		cred := credentials.NewSharedCredentials("", "")
-		creds, err := cred.Get()
-		if err != nil {
-			log.Println("populateAwsS3BucketName:", err)
-		}
-		c.Backup.ObjectStorage.AccessKey = creds.AccessKeyID
-		c.Backup.ObjectStorage.SecretKey = creds.SecretAccessKey
 		c.Backup.ObjectStorage.AWSRegion = haConfig.Aws.Config.Region
+
+		if haConfig.Aws.Config.Profile != "" {
+			cred := credentials.NewSharedCredentials("", "")
+			creds, err := cred.Get()
+			if err != nil {
+				log.Println("populateAwsS3BucketName:", err)
+			}
+
+			c.Backup.ObjectStorage.AccessKey = creds.AccessKeyID
+			c.Backup.ObjectStorage.SecretKey = creds.SecretAccessKey
+		}
+
 	}
 }
 
diff --git a/components/automate-cli/pkg/verifyserver/services/batchcheckservice/trigger/s3backupchecktrigger/s3backupconfigchecktrigger.go b/components/automate-cli/pkg/verifyserver/services/batchcheckservice/trigger/s3backupchecktrigger/s3backupconfigchecktrigger.go
index c3b2fdbd9..204237768 100644
--- a/components/automate-cli/pkg/verifyserver/services/batchcheckservice/trigger/s3backupchecktrigger/s3backupconfigchecktrigger.go
+++ b/components/automate-cli/pkg/verifyserver/services/batchcheckservice/trigger/s3backupchecktrigger/s3backupconfigchecktrigger.go
@@ -33,7 +33,7 @@ func (svc *S3BackupConfigCheck) Run(config *models.Config) []models.CheckTrigger
 		return s3ConfigSkippedResponse(config, constants.S3_BACKUP_CONFIG, constants.SKIP_BACKUP_TEST_MESSAGE_S3)
 	}
 
-	if isObjectStorage(config.Backup) {
+	if !isObjectStorage(config ) {
 		return emptyResp(config, constants.S3_BACKUP_CONFIG)
 	}
 
@@ -102,10 +102,17 @@ func getS3CheckRequest(object *models.ObjectStorage) models.S3ConfigRequest {
 	}
 }
 
-func isObjectStorage(backup *models.Backup) bool {
-	return backup.ObjectStorage.BucketName == "" ||
-		backup.ObjectStorage.AccessKey == "" ||
-		backup.ObjectStorage.SecretKey == ""
+func isObjectStorage(config *models.Config) bool {
+	backup := config.Backup
+	if backup.ObjectStorage.BucketName == "" {
+		return false
+	} else if config.Profile != "" {
+		if backup.ObjectStorage.AccessKey == "" ||
+			backup.ObjectStorage.SecretKey == "" {
+			return false
+		}
+	}
+	return true
 }
 
 func emptyResp(config *models.Config, checktype string) []models.CheckTriggerResponse {
diff --git a/components/automate-cli/pkg/verifyserver/utils/awsutils/aws.go b/components/automate-cli/pkg/verifyserver/utils/awsutils/aws.go
index 54884c868..1a2b11c91 100644
--- a/components/automate-cli/pkg/verifyserver/utils/awsutils/aws.go
+++ b/components/automate-cli/pkg/verifyserver/utils/awsutils/aws.go
@@ -35,7 +35,9 @@ func (au *AwsUtilsImpl) NewSessionWithOptions(endpoint, accessKey, secretKey, re
 	}
 	config.Endpoint = &endpoint
 	config.Region = aws.String(region)
-	config.Credentials = credentials.NewStaticCredentials(accessKey, secretKey, "")
+	if accessKey != "" && secretKey != "" {
+		config.Credentials = credentials.NewStaticCredentials(accessKey, secretKey, "")
+	}
 	return session.NewSessionWithOptions(session.Options{
 		Config: config,
 	})
diff --git a/components/docs-chef-io/Makefile b/components/docs-chef-io/Makefile
index d721e2183..8aa118322 100644
--- a/components/docs-chef-io/Makefile
+++ b/components/docs-chef-io/Makefile
@@ -7,6 +7,7 @@ SWAGGER_FILES=$(shell find $(SWAGGER_DIR) -name '*.swagger.json')
 STATIC_SWAGGER_FILES=$(shell find data/automate/api-static/ -type f | sort -n)
 
 preview_netlify: chef_web_docs
+	rm -rf chef-web-docs/_vendor/github.com/chef/automate/docs-chef-io/*
 	cp -R content/* chef-web-docs/_vendor/github.com/chef/automate/components/docs-chef-io/content
 	cp -R data/* chef-web-docs/_vendor/github.com/chef/automate/components/docs-chef-io/data
 	cp -R layouts/* chef-web-docs/_vendor/github.com/chef/automate/components/docs-chef-io/layouts
diff --git a/components/docs-chef-io/content/automate/backup.md b/components/docs-chef-io/content/automate/backup.md
index adf34f6d4..f88185019 100644
--- a/components/docs-chef-io/content/automate/backup.md
+++ b/components/docs-chef-io/content/automate/backup.md
@@ -181,7 +181,7 @@ See how to [restore from GCS]({{< ref "restore/#restore-from-a-google-cloud-stor
 
 ### Create a Backup
 
-Make a backup with the [`backup create`]({{< ref "cli_chef_automate/#chef-automate-backup-create" >}}) command:
+Make a backup with the [`backup create`]({{< relref "cli/#chef-automate-backup-create" >}}) command:
 
 ```shell
 chef-automate backup create
@@ -195,7 +195,7 @@ Success: Created backup 20180518010336
 ```
 
 Restores from a filesystem backup may fail with incorrect directory permissions.
-Run the [`fix-repo-permissions` command]({{< ref "cli_chef_automate/#chef-automate-backup-fix-repo-permissions" >}}) to address such issues:
+Run the [`fix-repo-permissions` command]({{< relref "cli/#chef-automate-backup-fix-repo-permissions" >}}) to address such issues:
 
 ```shell
 sudo chef-automate backup fix-repo-permissions <path>
@@ -203,7 +203,7 @@ sudo chef-automate backup fix-repo-permissions <path>
 
 ### List Backups
 
-You can list existing backups with the [`backup list`]({{< ref "cli_chef_automate/#chef-automate-backup-list" >}}) command:
+You can list existing backups with the [`backup list`]({{< relref "cli/#chef-automate-backup-list" >}}) command:
 
 ```shell
 chef-automate backup list
@@ -250,7 +250,7 @@ where `bucket_name` is the name of the GCS bucket and `base_path` is an optional
 
 ## Delete Backups
 
-To delete backups from a running instance of Chef Automate, first find the relevant backup ID with `chef-automate backup list` and then delete the backup using [`chef automate backup delete ID`]({{< ref "cli_chef_automate/#chef-automate-backup-delete" >}}).
+To delete backups from a running instance of Chef Automate, first find the relevant backup ID with `chef-automate backup list` and then delete the backup using [`chef automate backup delete ID`]({{< relref "cli/#chef-automate-backup-delete" >}}).
 
 ```shell
 chef-automate backup list
@@ -300,4 +300,4 @@ chef-automate debug set-log-level deployment-service debug
 
 ## References
 
-See the [`chef-automate backup` command reference]({{< ref "cli_chef_automate/#chef-automate-backup" >}}).
+See the [`chef-automate backup` command reference]({{< relref "cli/#chef-automate-backup" >}}).
diff --git a/components/docs-chef-io/content/automate/cli_chef_automate.md b/components/docs-chef-io/content/automate/cli.md
similarity index 77%
rename from components/docs-chef-io/content/automate/cli_chef_automate.md
rename to components/docs-chef-io/content/automate/cli.md
index 4346a62ae..233c00260 100644
--- a/components/docs-chef-io/content/automate/cli_chef_automate.md
+++ b/components/docs-chef-io/content/automate/cli.md
@@ -1,7 +1,7 @@
 +++
-title = "Automate CLI (Standalone)"
+title = "Chef Automate CLI"
+aliases = ['cli_chef_automate', 'cli_chef_automate_ha']
 
-date = 2018-03-26T15:29:24-07:00
 draft = false
 
 gh_repo = "automate"
@@ -9,13 +9,13 @@ toc_layout = "cli_chef_automate_toc"
 
 [menu]
   [menu.automate]
-    title = "Automate CLI (Standalone)"
+    title = "Automate CLI"
     parent = "automate/reference"
-    identifier = "automate/reference/cli_chef_automate.md Automate CLI (Standalone)"
+    identifier = "automate/reference/cli_chef_automate.md Automate CLI"
     weight = 24
 +++
 
-## Chef Automate CLI Commands
+## `chef-automate` CLI Commands
 
 {{< automate/automate_cli_commands data_path="automate/cli_chef_automate/commands" >}}
 
diff --git a/components/docs-chef-io/content/automate/cli_chef_automate_ha.md b/components/docs-chef-io/content/automate/cli_chef_automate_ha.md
deleted file mode 100644
index 5f3cad903..000000000
--- a/components/docs-chef-io/content/automate/cli_chef_automate_ha.md
+++ /dev/null
@@ -1,27 +0,0 @@
-+++
-title = "Automate CLI (HA)"
-
-date = 2018-03-26T15:29:24-07:00
-draft = false
-
-gh_repo = "automate"
-toc_layout = "cli_chef_automate_toc"
-
-[menu]
-  [menu.automate]
-    title = "Automate CLI (HA)"
-    parent = "automate/reference"
-    identifier = "automate/reference/cli_chef_automate_ha.md Automate CLI (HA)"
-    weight = 25
-+++
-
-## Chef Automate CLI Commands
-
-{{< automate/automate_cli_commands_ha data_path="automate/cli_chef_automate/commands" >}}
-
-## Error Codes 
-
-If `chef-automate` encounters an error during execution, it exits with a non-zero
-error code. Here's what our error codes mean:
-
-{{< automate/automate_cli_status_errors data_path="automate/cli_chef_automate/status/errors" >}}
diff --git a/components/docs-chef-io/content/automate/ha_aws_deploy_steps.md b/components/docs-chef-io/content/automate/ha_aws_deploy_steps.md
index caf8f8530..fa2c38346 100644
--- a/components/docs-chef-io/content/automate/ha_aws_deploy_steps.md
+++ b/components/docs-chef-io/content/automate/ha_aws_deploy_steps.md
@@ -110,7 +110,7 @@ Run the following steps on Bastion Host Machine:
     - If you don't have a custom FQDN leave `fqdn` as empty for this AWS deployment. By default, AWS Application load balancer will be used as `fqdn`.
     - Set `instance_count` for Chef Automate, Chef Infra Server, Postgresql, OpenSearch.
     - Set AWS Config Details:
-        - Set `profile`, by default `profile` is `"default"`
+        - Set the AWS `profile`. In case you attached the IAM role to the bastion machine, leave this field empty.
         - Set `region`, by default `region` is `"us-east-1"`
         - Set `aws_vpc_id`, which you had created as Prerequisite step. Example: `"vpc12318h"`
         - Set `private_custom_subnets` to the 3 private subnets we created in prerequisites step and set `public_custom_subnets` to the 3 public subnets we created in prerequisites step: example : `["subnet-07e469d218301533","subnet-07e469d218041534","subnet-07e469d283041535"]`
@@ -161,7 +161,8 @@ Run the following steps on Bastion Host Machine:
     ```
 
 1. After the deployment successfully completed. To view the automate UI, run the command `chef-automate info`, you will get the `automate_url`.
-   If we want to change the FQDN URL from the loadbalancer URL to some other FQDN URL, then use below template
+  If you want to change the FQDN URL from the loadbalancer URL to some other FQDN URL, then use below template
+
 
 -   create a file `a2.fqdn.toml`
 
diff --git a/components/docs-chef-io/content/automate/ha_aws_deployment_prerequisites.md b/components/docs-chef-io/content/automate/ha_aws_deployment_prerequisites.md
index 72616b5a1..1a3e31505 100644
--- a/components/docs-chef-io/content/automate/ha_aws_deployment_prerequisites.md
+++ b/components/docs-chef-io/content/automate/ha_aws_deployment_prerequisites.md
@@ -120,14 +120,14 @@ The Chef Automate HA cluster requires multiple ports for the frontend and backen
 
 The first column in the table below represents the source of the connection. The table's other columns represent the destination with the matrix value as a port number. The specified port numbers need to be opened on the origin and destination.
 
-|                 | Chef Automate        | Chef Infra Server    | Postgresql                                | OpenSearch                           | Bastion | Load Balancer |
+|                 | Chef Automate        | Chef Infra Server    | Postgresql                                | OpenSearch                           | Bastion | Automate Load Balancer |
 | --------------- | -------------------- | -------------------- | ----------------------------------------- | ------------------------------------ | ------- | ------------- |
 | Chef Automate   |                      |                      | 7432                                      | 9200                                 |         |               |
-| Infra Server    | 443                  |                      | 7432                                      | 9200                                 |         |               |
+| Infra Server    |                      |                      | 7432                                      | 9200                                 |         | 443              |
 | PostgreSQL      |                      |                      | 9631, 7432, 5432, 6432, 9638<br/>UDP 9638 |                                      |         |               |
 | OpenSearch      |                      |                      |                                           | 9631, 9200, 9300, 9638 <br/>UDP 9638 |         |               |
 | Bastion         | 22, 9631, 9638, 7799 | 22, 9631, 9638, 7799 | 22, 9631, 9638, 7432, 7799                | 22, 9631, 9638, 9200, 7799           |         | 22            |
-| Load Balancer   | 443, 80              | 443, 80              |                                           |                                      |         |               |
+| Automate Load Balancer   | 443, 80              | 443, 80              |                                           |                                      |         |               |
 | Internet Access |                      |                      |                                           |                                      | 80, 443 |               |
 
 {{< note >}} Custom SSH port is supported, but use the same port across all the machines. {{< /note >}}
diff --git a/components/docs-chef-io/content/automate/ha_aws_managed_deploy_steps.md b/components/docs-chef-io/content/automate/ha_aws_managed_deploy_steps.md
index e5665cb98..bea25bf6b 100644
--- a/components/docs-chef-io/content/automate/ha_aws_managed_deploy_steps.md
+++ b/components/docs-chef-io/content/automate/ha_aws_managed_deploy_steps.md
@@ -110,7 +110,7 @@ Set the above prerequisites in `~/.aws/credentials` in Bastion Host:
    - Set `backup_config` to `"s3"` or `efs`.
    - If `backup_config` is `s3`, uncomment and set the value for following `s3_bucketName` attribute to your bucket name. If the bucket name does not exist, it will be created for you automatically.
    - Set `admin_password` to access Chef Automate UI for user `admin`.
-   - Don't set `fqdn` for the AWS deployment.
+   - If you don't have a custom FQDN leave `fqdn` as empty for this AWS deployment. By default, AWS Application load balancer will be used as `fqdn`.
    - Set `instance_count` for *Chef Automate*, *Chef Infra Server*, *Postgresql*, *OpenSearch*.
    - Set AWS Config Details:
       - Set `profile`. The default value of `profile` is `"default"`.
@@ -153,6 +153,13 @@ Set the above prerequisites in `~/.aws/credentials` in Bastion Host:
 
    #Run provision command to deploy `automate.aib` with set `config.toml`
    chef-automate provision-infra config.toml --airgap-bundle automate.aib
+    "
+    ```
+
+1. Once the provisioning is successful, **if you have added custom DNS to your configuration file (`fqdn`), make sure to map the load-balancer FQDN from the output of previous command to your DNS from DNS Provider**. After that continue with the deployment process with following.
+
+    ```bash
+    sudo -- sh -c "
 
    #Run deploy command to deploy `automate.aib` with set `config.toml`
    chef-automate deploy config.toml --airgap-bundle automate.aib
@@ -166,7 +173,7 @@ Set the above prerequisites in `~/.aws/credentials` in Bastion Host:
    ```
 
 1. After the deployment successfully completed. To view the automate UI, run the command `chef-automate info`, you will get the `automate_url`.
-  If we want to change the FQDN URL from the loadbalancer URL to some other FQDN URL, then use below template
+  If you want to change the FQDN URL from the loadbalancer URL to some other FQDN URL, then use below template
   
 - Create a file `a2.fqdn.toml`
 
diff --git a/components/docs-chef-io/content/automate/ha_chef_backend_to_automate_ha.md b/components/docs-chef-io/content/automate/ha_chef_backend_to_automate_ha.md
index 045f17477..2d85b4a05 100644
--- a/components/docs-chef-io/content/automate/ha_chef_backend_to_automate_ha.md
+++ b/components/docs-chef-io/content/automate/ha_chef_backend_to_automate_ha.md
@@ -309,4 +309,4 @@ Bootstrap the nodes to update the `chef_server_url` using the following steps:
 
 ## Use Automate HA for Chef-Backend User
 
-Download and Install the [Chef Workstation](https://www.chef.io/downloads/tools/workstation) from the Bastion machine or local machine install chef-workstation. You can refer to the [Workstation page](https://docs.chef.io/workstation/getting_started/#set-up-your-chef-repo) to set up your Workstation.
+From the bastion host or a local machine, download and install [Chef Workstation](https://www.chef.io/downloads). See the [Chef Workstation documentation](/workstation/getting_started/#set-up-your-chef-repo) for information on setting up Workstation.
diff --git a/components/docs-chef-io/content/automate/ha_on_premises_deployment_prerequisites.md b/components/docs-chef-io/content/automate/ha_on_premises_deployment_prerequisites.md
index 3bd02be15..34ede3e06 100644
--- a/components/docs-chef-io/content/automate/ha_on_premises_deployment_prerequisites.md
+++ b/components/docs-chef-io/content/automate/ha_on_premises_deployment_prerequisites.md
@@ -123,14 +123,14 @@ The Chef Automate HA cluster requires multiple ports for the frontend and backen
 
 The first column in the table below represents the source of the connection. The table's other columns represent the destination with the matrix value as a port number. The specified port numbers need to be opened on the origin and destination.
 
-|               | Chef Automate  | Chef Infra Server | Postgresql                          | OpenSearch                           | Bastion | Load Balancer |
+|               | Chef Automate  | Chef Infra Server | Postgresql                          | OpenSearch                           | Bastion | Automate Load Balancer |
 |---------------|----------------|-------------------|-------------------------------------|--------------------------------------|---------| ------------- |
 | Chef Automate |                |                   | 7432                                | 9200                                 |         |               |
-| Infra Server  | 443            |                   | 7432                                | 9200                                 |         |               |
+| Infra Server  |                |                   | 7432                                | 9200                                 |         | 443              |
 | PostgreSQL    |                |                   | 9631, 7432, 5432, 6432, 9638<br/>UDP 9638 |                                      |         |               |
 | OpenSearch    |                |                   |                                     | 9631, 9200, 9300, 9638 <br/>UDP 9638 |         |               |
 | Bastion       | 22, 9631, 9638, 7799 | 22, 9631, 9638, 7799    | 22, 9631, 9638, 7432, 7799                | 22, 9631, 9638, 9200, 7799                 |         | 22            |
-| Load Balancer | 443, 80        | 443, 80           |                                     |                                      |         |               |
+| Automate Load Balancer | 443, 80        | 443, 80           |                                     |                                      |         |               |
 
 {{< note >}} Custom SSH port is supported, but use the same port across all the machines. {{< /note >}}
 
diff --git a/components/docs-chef-io/content/automate/managed_services.md b/components/docs-chef-io/content/automate/managed_services.md
index 2284ca2f8..26a061df3 100644
--- a/components/docs-chef-io/content/automate/managed_services.md
+++ b/components/docs-chef-io/content/automate/managed_services.md
@@ -191,7 +191,7 @@ Before switching to external AWS PostgreSQL RDS/OpenSearch services, take s3 bac
 
 ### Create a Backup
 
-Make a backup with the [backup create]({{< ref "cli_chef_automate/#chef-automate-backup-create" >}}) command:
+Make a backup with the [backup create]({{< relref "cli/#chef-automate-backup-create" >}}) command:
 
 ```shell
 chef-automate backup create
@@ -205,7 +205,7 @@ Success: Created backup 20180518010336
 
 ### List Backups
 
-You can list existing backups with the [backup list]({{< ref "cli_chef_automate/#chef-automate-backup-list" >}}) command:
+You can list existing backups with the [backup list]({{< relref "cli/#chef-automate-backup-list" >}}) command:
 
 ```shell
 chef-automate backup list
@@ -610,7 +610,7 @@ When your Chef Automate instance is equipped with internet connectivity, you can
 
 ### Create a Backup
 
-Make a backup with the [`backup create`]({{< ref "cli_chef_automate/#chef-automate-backup-create" >}}) command:
+Make a backup with the [`backup create`]({{< relref "cli/#chef-automate-backup-create" >}}) command:
 
 ```shell
 chef-automate backup create
@@ -625,7 +625,7 @@ Success: Created backup 20180518010336
 
 ### List Backups
 
-You can list existing backups with the [`backup list`]({{< ref "cli_chef_automate/#chef-automate-backup-list" >}}) command:
+You can list existing backups with the [`backup list`]({{< relref "cli/#chef-automate-backup-list" >}}) command:
 
 ```shell
 chef-automate backup list
diff --git a/components/docs-chef-io/content/automate/restore.md b/components/docs-chef-io/content/automate/restore.md
index 782ce684a..d1ca6e284 100644
--- a/components/docs-chef-io/content/automate/restore.md
+++ b/components/docs-chef-io/content/automate/restore.md
@@ -100,7 +100,7 @@ chef-automate backup restore </path/to/backups/>BACKUP_ID --patch-config </path/
 ```
 
 Restores from a filesystem backup may fail with incorrect directory permissions.
-Run the [`fix-repo-permissions` command]({{< ref "cli_chef_automate/#chef-automate-backup-fix-repo-permissions" >}}) to address such issues:
+Run the [`fix-repo-permissions` command]({{< relref "cli/#chef-automate-backup-fix-repo-permissions" >}}) to address such issues:
 
 ```shell
 sudo chef-automate backup fix-repo-permissions <path>
@@ -139,7 +139,7 @@ chef-automate backup restore --airgap-bundle </path/to/bundle> gs://bucket_name/
 Use the `--patch-config` option with a [configuration patch file]({{< relref "restore.md#prerequisites" >}}) to restore to a host with a different FQDN than that of the backup host.
 
 Restores from a filesystem backup may fail with incorrect directory permissions.
-Run the [`fix-repo-permissions` command]({{< ref "cli_chef_automate/#chef-automate-backup-fix-repo-permissions" >}}) to address such issues:
+Run the [`fix-repo-permissions` command]({{< relref "cli/#chef-automate-backup-fix-repo-permissions" >}}) to address such issues:
 
 ```shell
 sudo chef-automate backup fix-repo-permissions <path>
@@ -215,4 +215,4 @@ chef-automate debug set-log-level deployment-service debug
 
 ## References
 
-See the [`chef-automate backup restore` command reference]({{< ref "cli_chef_automate/#chef-automate-backup-restore" >}}).
+See the [`chef-automate backup restore` command reference]({{< relref "cli/#chef-automate-backup-restore" >}}).
diff --git a/components/docs-chef-io/layouts/partials/cli_chef_automate_toc.html b/components/docs-chef-io/layouts/partials/cli_chef_automate_toc.html
index 843e5f9ba..d5904aaa9 100644
--- a/components/docs-chef-io/layouts/partials/cli_chef_automate_toc.html
+++ b/components/docs-chef-io/layouts/partials/cli_chef_automate_toc.html
@@ -1,53 +1,35 @@
-<aside class="sticky sidebar" data-sticky data-top-anchor="utility-bar:bottom" data-btm-anchor="footer:top">
-  <span>
-    <strong>
-      <h4 class="mini-toc-header">Table Of Contents</h4>
-    </strong>
-    <button class="close-button hide-for-large" aria-label="Close menu" type="button" data-close>
-      <span aria-hidden="true">&times;</span>
-    </button>
-  </span>
-  <p>
+<aside class="sticky sidebar hide-for-print" data-sticky data-top-anchor="utility-bar:bottom" data-btm-anchor="page-content:bottom">
+  <div id="table-of-contents">
+    <span>
+      <span class="mini-toc-nav">Table Of Contents</span>
+      <button class="close-button hide-for-large" aria-label="Close menu" type="button" data-close>
+        <span aria-hidden="true">&times;</span>
+      </button>
+    </span>
     <a href="#{{ anchorize ( .Title )}}">{{ .Title }}</a>
-    {{ $page := .Title }}
-  </p>
-  <ul>
-    <li>
-      <a href = "#automate-cli-commands">Automate CLI Commands</a>
-      <ul>
-        {{ range .Site.Data.automate.cli_chef_automate.commands }}
-        {{ if and (eq $page "Automate CLI (HA)") (or (not .compatible_with) (eq .compatible_with "AutomateHA")) }}
-          {{ $command := .name }}
-          {{ if ne $command "chef-automate"}}
-            {{ $command := replaceRE `chef-automate ` "" $command }}
-            <li>
-              <a href = "#{{ .name | urlize}}">{{ $command }}</a>
-            </li>
-          {{ else}}
-            <li>
-              <a href = "#{{ .name | urlize}}">{{ $command }}</a>
-            </li>
-          {{ end }}
-          {{ end }} 
-          {{ if and (eq $page "Automate CLI (Standalone)") (or (not .compatible_with) (eq .compatible_with "Automate")) }}
-          {{ $command := .name }}
-          {{ if ne $command "chef-automate"}}
-            {{ $command := replaceRE `chef-automate ` "" $command }}
-            <li>
-              <a href = "#{{ .name | urlize}}">{{ $command }}</a>
-            </li>
-          {{ else}}
-            <li>
-              <a href = "#{{ .name | urlize}}">{{ $command }}</a>
-            </li>
-          {{ end }}
-          {{ end }} 
+    <ul>
+      <li>
+        <a href = "#automate-cli-commands">Automate CLI Commands</a>
+        <ul>
+          {{ range .Site.Data.automate.cli_chef_automate.commands }}
+            {{ $command := .name }}
+            {{ if ne $command "chef-automate"}}
+              {{ $command := replaceRE `chef-automate ` "" $command }}
+              <li>
+                <a href = "#{{ .name | urlize}}">{{ $command }}</a>
+              </li>
+            {{ else}}
+              <li>
+                <a href = "#{{ .name | urlize}}">{{ $command }}</a>
+              </li>
+            {{ end }}
 
-        {{ end }}
-      </ul>
-    </li>
-    <li>
-      <a href = "#error-codes">Error Codes</a>
-    </li>
-  </ul>
+          {{ end }}
+        </ul>
+      </li>
+      <li>
+        <a href = "#error-codes">Error Codes</a>
+      </li>
+    </ul>
+  </div>
 </aside>
diff --git a/components/docs-chef-io/layouts/shortcodes/automate/automate_cli_commands.html b/components/docs-chef-io/layouts/shortcodes/automate/automate_cli_commands.html
index 216b914b7..6fa0dee66 100644
--- a/components/docs-chef-io/layouts/shortcodes/automate/automate_cli_commands.html
+++ b/components/docs-chef-io/layouts/shortcodes/automate/automate_cli_commands.html
@@ -2,79 +2,85 @@
 {{ range $data_path }}
 {{ $id := urlize .name }}
 {{ $command := strings.TrimPrefix "chef-automate " .name }}
-{{ if or (not .compatible_with) (eq .compatible_with "Automate") }}
-<h3 id="{{ $id }}">
-   {{ .name }}</h2>
-   <p>{{ .description }}</p>
-   <h4 id="{{ $id }}-usage">
-   Usage
-</h3>
+<h3 id="{{ $id }}">{{ .name }}</h3>
+
+<p>{{ .description | markdownify }}</p>
+
+<h4 id="{{ $id }}-supported-on">Supported on</h4>
+
+{{ if (eq .compatible_with "AutomateHA") }}
+  <p>Command available on Chef Automate high availability deployments.</p>
+{{ else if (eq .compatible_with "Automate") }}
+  <p>Command available on standalone Chef Automate deployments.</p>
+{{ else }}
+  <p>Command available on standalone and high availability Chef Automate deployments.</p>
+{{ end }}
+
+{{ if (eq .supported_on "Bastion" )}}
+  <p>In high availability Chef Automate deployments, this command is supported on the bastion host.</p>
+{{ else if (eq .supported_on "FrontEnd" ) }}
+  <p>In high availability Chef Automate deployments, this command is only supported on Chef Automate and Chef Infra Server frontend nodes.</p>
+{{ else if (eq .supported_on "Automate" )}}
+  <p>In high availability Chef Automate deployments, this command is only supported on Chef Automate nodes.</p>
+{{ end }}
+
+<h4 id="{{ $id }}-usage">Usage</h4>
+
 <pre><code>{{ .usage }}</code></pre>
-<h4 id="{{ $id }}-flags">
-Flags</h3>
-<div class="fake-code grid-x">
-   <table>
-      <tr class="table-heading">
-         <th>Flags</th>
-         <th>Description</th>
-      </tr>
-      {{- range .options -}}
-      <tr>
-         {{ if or (not .compatible_with_options) (eq .compatible_with_options "Automate") }}
-         <td>
-            <div class="cell medium-4">{{ if .shorthand }}-{{ .shorthand }}, {{ end }}--{{ .name }}</div>
-         </td>
-         <td>
-            <div class="cell medium-8">{{ .usage }}{{ if .default_value }} (default: {{ .default_value }}){{ end }}</div>
-         </td>
-         {{ end }}
-      </tr>
-      {{- end -}}
-      {{- range .inherited_options -}}
-      <tr>
-         {{ if or (not .compatible_with_options) (eq .compatible_with_options "Automate") }}
-         <td>
-            <div class="cell medium-4">{{ if .shorthand }}-{{ .shorthand }}, {{ end }}--{{ .name }}</div>
-         </td>
-         <td>
-            <div class="cell medium-8">{{ .usage }}{{ if .default_value }} (default: {{ .default_value }}){{ end }}</div>
-         </td>
-         {{ end }}
-      </tr>
-      {{- end -}}
-   </table>
-</div>
-<h4 id="{{ $id }}-see-also">
-Related Commands</h3>
-<table>
-   <div class="grid-x">
-      <tr class="table-heading">
-         <th>Commands</th>
-         <th>Description</th>
-      </tr>
-      {{ range .see_also }}
+
+<h4 id="{{ $id }}-flags">Flags</h4>
+
+<dl>
+  {{ $flags := slice }}
+  {{ range .options }}
+    {{ $flags = $flags | append . }}
+  {{ end }}
+  {{ range .inherited_options }}
+    {{ $flags = $flags | append . }}
+  {{ end }}
+
+  {{ range sort $flags "name" }}
+    {{ if .shorthand }}<dt><code>-{{ .shorthand }}</code></dt>{{ end }}
+    <dt><code>--{{ .name }}</code></dt>
+    <dd>{{.usage}}</dd>
+    {{ if (eq .compatible_with_options "Automate") }}
+      <dd>Flag only available on standalone Chef Automate deployments.</dd>
+    {{ else if (eq .compatible_with_options "AutomateHA") }}
+      <dd>Flag only available on high availability Chef Automate deployments.</dd>
+    {{ end }}
+    <dd></dd>
+    {{ with .default_value }}<dd>Default value: <code>{{ . }}</code></dd>{{ end }}
+  {{ end }}
+
+</dl>
+
+{{ if ne .name "chef-automate" }}
+  <h4 id="{{ $id }}-see-also">Related Commands</h4>
+  <ul>
+    {{ range .see_also }}
       {{ $sa_bits := split . " - " }}
       {{ $sa_id := index $sa_bits 0 }}
-      {{ $sa_string := index $sa_bits 1 }}
+
       {{ $sa_command := "" }}
       {{ $sa_command_id := "" }}
+
       {{ if ( hasPrefix $id (urlize $sa_id ) )}}
-      {{ $sa_command_id = urlize $sa_id }}
-      {{ $sa_command = strings.TrimPrefix "chef-automate " $sa_id }}
+
+        {{ $sa_command_id = urlize $sa_id }}
+        {{ $sa_command = strings.TrimPrefix "chef-automate " $sa_id }}
+
       {{ else }}
-      {{ $sa_command_id = urlize (print $id "-" $sa_id) }}
-      {{ $sa_command = print $command " " $sa_id }}
+
+        {{ $sa_command_id = urlize (print $id "-" $sa_id) }}
+        {{ $sa_command = print $command " " $sa_id }}
+
       {{ end }}
-      <tr>
-         <td>
-            <div class="cell medium-3"><a href="#{{ $sa_command_id }}">{{ $sa_command }}</a></div>
-         </td>
-         <td>
-            <div class="cell medium-9">{{ $sa_string }}</div>
-         </td>
-      </tr>
+
+      {{ if ne $sa_id "chef-automate" }}
+        <li><a href="#{{ $sa_command_id }}"><code>chef-automate {{ $sa_command }}</code></a></li>
       {{ end }}
-   </div>
-</table>
+    {{ end }}
+  </ul>
+
 {{ end }}
 {{ end }}
diff --git a/components/docs-chef-io/layouts/shortcodes/automate/automate_cli_commands_ha.html b/components/docs-chef-io/layouts/shortcodes/automate/automate_cli_commands_ha.html
deleted file mode 100644
index 990ce43c8..000000000
--- a/components/docs-chef-io/layouts/shortcodes/automate/automate_cli_commands_ha.html
+++ /dev/null
@@ -1,82 +0,0 @@
-{{ $data_path := index $.Site.Data (split (.Get "data_path") "/") }}
-{{ range $data_path }}
-{{ $id := urlize .name }}
-{{ $command := strings.TrimPrefix "chef-automate " .name }}
-{{ if or (not .compatible_with) (eq .compatible_with "AutomateHA") }}
-<h3 id="{{ $id }}">
-   {{ .name }}</h2>
-   <p>{{ .description }}</p>
-   <h4 id="{{ $id }}-usage">
-   Usage
-</h3>
-<pre><code>{{ .usage }}</code></pre>
-<h4 id="{{ $id }}-flags">
-Flags</h3>
-<div class="fake-code grid-x">
-   <table>
-      <tr class="table-heading">
-         <th>Flags</th>
-         <th>Description</th>
-      </tr>
-      {{- range .options -}}
-      <tr>
-         {{ if or (not .compatible_with_options) (eq .compatible_with_options "AutomateHA") }}
-         <td>
-            <div class="cell medium-4">{{ if .shorthand }}-{{ .shorthand }}, {{ end }}--{{ .name }}</div>
-         </td>
-         <td>
-            <div class="cell medium-8">{{ .usage }}{{ if .default_value }} (default: {{ .default_value }}){{ end }}</div>
-         </td>
-         {{ end }}
-      </tr>
-      {{- end -}}    
-      {{- range .inherited_options -}}
-      <tr>
-         {{ if or (not .compatible_with_options) (eq .compatible_with_options "AutomateHA") }}
-         <td>
-            <div class="cell medium-4">{{ if .shorthand }}-{{ .shorthand }}, {{ end }}--{{ .name }}</div>
-         </td>
-         <td>
-            <div class="cell medium-8">{{ .usage }}{{ if .default_value }} (default: {{ .default_value }}){{ end }}</div>
-         </td>
-         {{ end }}
-      </tr>
-      {{- end -}}
-   </table>
-</div>
-<h4>Supported On</h4>
-<p>{{ .supported_on }}</p>
-<h4 id="{{ $id }}-see-also">
-Related Commands</h3>   
-<table>
-   <div class="grid-x">
-      <tr class="table-heading">
-         <th>Commands</th>
-         <th>Description</th>
-      </tr>
-      {{ range .see_also }}
-      {{ $sa_bits := split . " - " }}
-      {{ $sa_id := index $sa_bits 0 }}
-      {{ $sa_string := index $sa_bits 1 }}
-      {{ $sa_command := "" }}
-      {{ $sa_command_id := "" }}
-      {{ if ( hasPrefix $id (urlize $sa_id ) )}}
-      {{ $sa_command_id = urlize $sa_id }}
-      {{ $sa_command = strings.TrimPrefix "chef-automate " $sa_id }}
-      {{ else }}
-      {{ $sa_command_id = urlize (print $id "-" $sa_id) }}
-      {{ $sa_command = print $command " " $sa_id }}
-      {{ end }}
-      <tr>
-         <td>
-            <div class="cell medium-3"><a href="#{{ $sa_command_id }}">{{ $sa_command }}</a></div>
-         </td>
-         <td>
-            <div class="cell medium-9">{{ $sa_string }}</div>
-         </td>
-      </tr>
-      {{ end }}
-   </div>
-</table>
-{{ end }}
-{{ end }}
diff --git a/dev/populate_chef_backend_data.sh b/dev/populate_chef_backend_data.sh
index 64c973390..a5c84744e 100644
--- a/dev/populate_chef_backend_data.sh
+++ b/dev/populate_chef_backend_data.sh
@@ -58,7 +58,7 @@ do
   for file in *; 
   do
     # Extract the contents of the tar file
-    knife upload file -s $SERVER_URL/organizations/"org$i" --config-option cookbook_path=/home/ubuntu/cookbook
+    knife upload $file -s $SERVER_URL/organizations/"org$i" --config-option cookbook_path=/home/ubuntu/cookbook
   done
 done
 
diff --git a/lib/config/hadeployconfigverify.go b/lib/config/hadeployconfigverify.go
index 97a50aa5e..c46f0f67c 100644
--- a/lib/config/hadeployconfigverify.go
+++ b/lib/config/hadeployconfigverify.go
@@ -595,11 +595,7 @@ func validateAwsManagedServices(aws *ConfigAwsSettings) error {
 
 func validateCommonAwsSettings(aws *ConfigAwsSettings) error {
 	errorList := list.New()
-
-	if err := validateRequiredString(aws.Profile, "aws profile name"); err != nil {
-		errorList.PushBack(err)
-	}
-
+	
 	if err := validateRequiredString(aws.Region, "aws region"); err != nil {
 		errorList.PushBack(err)
 	}
diff --git a/lib/httputils/http.go b/lib/httputils/http.go
index 54d3fc826..e40093348 100644
--- a/lib/httputils/http.go
+++ b/lib/httputils/http.go
@@ -14,6 +14,7 @@ import (
 // HTTPClient interface represents an HTTP client.
 type HTTPClient interface {
 	MakeRequest(requestMethod, url string, body interface{}) (*http.Response, []byte, error)
+	MakeRequestWithHeaders(requestMethod, url string, body interface{}, headerkey string, headerValue string) (*http.Response, []byte, error)
 }
 
 // Client is a wrapper around http.Client that adds some convenience methods for making requests and handling responses.
@@ -24,13 +25,18 @@ type Client struct {
 
 // MockHTTPClient is a mock implementation of HTTPClient.
 type MockHTTPClient struct {
-	MakeRequestFunc func(requestMethod, url string, body interface{}) (*http.Response, []byte, error)
+	MakeRequestFunc            func(requestMethod, url string, body interface{}) (*http.Response, []byte, error)
+	MakeRequestWithHeadersfunc func(requestMethod, url string, body interface{}, headerkey string, headerValue string) (*http.Response, []byte, error)
 }
 
 func (m *MockHTTPClient) MakeRequest(requestMethod, url string, body interface{}) (*http.Response, []byte, error) {
 	return m.MakeRequestFunc(requestMethod, url, body)
 }
 
+func (m *MockHTTPClient) MakeRequestWithHeaders(requestMethod, url string, body interface{}, headerkey string, headerValue string) (*http.Response, []byte, error) {
+	return m.MakeRequestWithHeadersfunc(requestMethod, url, body, headerkey, headerValue)
+}
+
 // NewClient returns a new Client with sane defaults.
 func NewClient(logger logger.Logger) *Client {
 	return &Client{
@@ -85,3 +91,39 @@ func (c *Client) MakeRequest(requestMethod, url string, body interface{}) (*http
 	c.Logger.Debugf("Response body for MakeRequest: \n%s\n", string(responseBody))
 	return resp, responseBody, nil
 }
+
+func (c *Client) MakeRequestWithHeaders(requestMethod, url string, body interface{}, headerKey string, headerValue string) (*http.Response, []byte, error) {
+	var reader io.Reader
+
+	// Marshal the request body to JSON if it's not nil
+	if body != nil {
+		requestBody, err := json.Marshal(body)
+		if err != nil {
+			return nil, nil, fmt.Errorf("failed to marshal request body: %w", err)
+		}
+		c.Logger.Debugf("Request body for MakeRequest: \n%s\n", string(requestBody))
+		reader = bytes.NewReader(requestBody)
+	}
+
+	req, err := http.NewRequest(requestMethod, url, reader)
+	if err != nil {
+		return nil, nil, fmt.Errorf("failed to create HTTP request: %w", err)
+	}
+
+	req.Header.Set(headerKey, headerValue)
+
+	resp, err := c.Client.Do(req)
+	if err != nil {
+		return nil, nil, fmt.Errorf("failed to make HTTP request: %w", err)
+	}
+
+	defer resp.Body.Close()
+
+	responseBody, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, nil, fmt.Errorf("failed to read response body: %w", err)
+	}
+
+	c.Logger.Debugf("Response body for MakeRequest: \n%s\n", string(responseBody))
+	return resp, responseBody, nil
+}
diff --git a/lib/sshutils/sshutils.go b/lib/sshutils/sshutils.go
index d975ed336..f3289addc 100644
--- a/lib/sshutils/sshutils.go
+++ b/lib/sshutils/sshutils.go
@@ -1,20 +1,28 @@
 package sshutils
 
 import (
+	"fmt"
 	"net"
 	"os"
 	"path/filepath"
 	"regexp"
 	"strings"
+	"sync"
 	"time"
 
+	"github.com/chef/automate/lib/io/fileutils"
 	"github.com/chef/automate/lib/logger"
 	"github.com/chef/automate/lib/platform/command"
+	"github.com/chef/automate/lib/stringutils"
 	"github.com/pkg/errors"
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/crypto/ssh/knownhosts"
 )
 
+const AUTOMATE_KNOWN_HOSTS = ".automate_known_hosts"
+
+var mutex sync.Mutex
+
 type SSHConfig struct {
 	SshUser    string
 	SshPort    string
@@ -24,9 +32,10 @@ type SSHConfig struct {
 }
 
 type SSHUtilImpl struct {
-	SshClient ISshClient
-	logger    logger.Logger
-	Exec      command.Executor
+	SshClient  ISshClient
+	logger     logger.Logger
+	Exec       command.Executor
+	sshDirPath string
 }
 
 type SSHUtil interface {
@@ -108,6 +117,21 @@ func (s *SSHUtilImpl) GetClientConfig(sshConfig SSHConfig) (*ssh.ClientConfig, e
 		s.logger.Errorf("Parsing key failed: %v", err)
 		return nil, err
 	}
+	homePath := os.Getenv("HOME")
+	if homePath == "" {
+		return nil, errors.New("Environment variable HOME cannot be empty. Please set a value for HOME env")
+	}
+	sshDirPath := filepath.Join(os.Getenv("HOME"), ".ssh")
+	exists, err := fileutils.PathExists(sshDirPath)
+	if err != nil {
+		s.logger.Errorln("Error evaluating path:", err)
+		return nil, err
+	}
+	if !exists {
+		s.logger.Errorf("Path %s does not exist\n", sshDirPath)
+		return nil, errors.New(fmt.Sprintf("Path %s does not exist", sshDirPath))
+	}
+	s.sshDirPath = sshDirPath
 	// Client config
 	return &ssh.ClientConfig{
 		User:            sshConfig.SshUser,
@@ -117,8 +141,11 @@ func (s *SSHUtilImpl) GetClientConfig(sshConfig SSHConfig) (*ssh.ClientConfig, e
 }
 
 func (s *SSHUtilImpl) HostKeyCallback(host string, remote net.Addr, pubkey ssh.PublicKey) error {
+	mutex.Lock()
+	defer mutex.Unlock()
+	knownHostPath := filepath.Join(s.sshDirPath, AUTOMATE_KNOWN_HOSTS)
 	var keyErr *knownhosts.KeyError
-	kh, err := s.CheckKnownHosts()
+	kh, err := s.CheckKnownHosts(knownHostPath)
 	if err != nil {
 		s.logger.Error("Error while getting the list of knows Host:", err)
 		return err
@@ -134,7 +161,7 @@ func (s *SSHUtilImpl) HostKeyCallback(host string, remote net.Addr, pubkey ssh.P
 		return keyErr
 	} else if errors.As(hErr, &keyErr) && len(keyErr.Want) == 0 {
 		// host key not found in known_hosts then give a warning and continue to connect.
-		return s.AddHostKey(host, remote, pubkey)
+		return s.AddHostKey(host, knownHostPath, remote, pubkey)
 	}
 	return nil
 }
@@ -296,8 +323,8 @@ func (s *SSHUtilImpl) CopyFileToRemoteConcurrently(sshConfig SSHConfig, srcFileP
 	return results
 }
 
-func (s *SSHUtilImpl) CreateKnownHosts() error {
-	f, fErr := os.OpenFile(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"), os.O_CREATE, 0600)
+func (s *SSHUtilImpl) CreateKnownHosts(knownHostPath string) error {
+	f, fErr := os.OpenFile(knownHostPath, os.O_CREATE, 0600)
 	if fErr != nil {
 		return fErr
 	}
@@ -305,12 +332,12 @@ func (s *SSHUtilImpl) CreateKnownHosts() error {
 	return nil
 }
 
-func (s *SSHUtilImpl) CheckKnownHosts() (ssh.HostKeyCallback, error) {
-	err := s.CreateKnownHosts()
+func (s *SSHUtilImpl) CheckKnownHosts(knownHostPath string) (ssh.HostKeyCallback, error) {
+	err := s.CreateKnownHosts(knownHostPath)
 	if err != nil {
 		return nil, err
 	}
-	kh, e := s.SshClient.New(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"))
+	kh, e := s.SshClient.New(knownHostPath)
 	if e != nil {
 		s.logger.Error("Error while getting the known host: %v", e)
 		return nil, e
@@ -318,19 +345,35 @@ func (s *SSHUtilImpl) CheckKnownHosts() (ssh.HostKeyCallback, error) {
 	return kh, nil
 }
 
-func (s *SSHUtilImpl) AddHostKey(host string, remote net.Addr, pubKey ssh.PublicKey) error {
-	// add host key if host is not found in known_hosts, error object is return, if nil then connection proceeds,
+func (s *SSHUtilImpl) AddHostKey(host, known_hosts_path string, remote net.Addr, pubKey ssh.PublicKey) error {
+	// add host key if host is not found in known_hosts, error object is returned, if nil then connection proceeds,
 	// if not nil then connection stops.
-	khFilePath := filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts")
+	content, err := os.ReadFile(known_hosts_path)
+	if err != nil {
+		s.logger.Error("Error while reading the known hosts file:", err)
+		return err
+	}
+	lastLine := stringutils.GetLastLine(string(content))
 
-	f, fErr := os.OpenFile(khFilePath, os.O_APPEND|os.O_WRONLY, 0600)
-	if fErr != nil {
-		s.logger.Error("Error while opening file on the given path:", fErr)
-		return fErr
+	// Open the known hosts file in append mode
+	f, err := os.OpenFile(known_hosts_path, os.O_APPEND|os.O_WRONLY, 0600)
+	if err != nil {
+		s.logger.Error("Error while opening file on the given path:", err)
+		return err
 	}
 	defer f.Close()
+
+	// Write the host key entry to the known hosts file
 	knownHosts := s.SshClient.Normalize(remote.String())
-	_, fileErr := f.WriteString(knownhosts.Line([]string{knownHosts}, pubKey))
-	f.WriteString("\n")
-	return fileErr
+	line := knownhosts.Line([]string{knownHosts}, pubKey)
+	if lastLine != "" {
+		line = "\n" + line
+	}
+	_, err = f.WriteString(line)
+	if err != nil {
+		s.logger.Error("Error while writing to the known hosts file:", err)
+		return err
+	}
+
+	return nil
 }
diff --git a/lib/stringutils/strings.go b/lib/stringutils/strings.go
index 4a19ce794..07827d6a0 100644
--- a/lib/stringutils/strings.go
+++ b/lib/stringutils/strings.go
@@ -28,3 +28,9 @@ func TitleReplace(s string, sep string, rep string) string {
 func IsNumeric(word string) bool {
 	return regexp.MustCompile(`^[0-9]+$`).MatchString(word)
 }
+
+func GetLastLine(input string) string {
+	lines := strings.Split(input, "\n")
+	lastLine := lines[len(lines)-1]
+	return lastLine
+}
diff --git a/terraform/a2ha-terraform/modules/aws_metadata/main.tf b/terraform/a2ha-terraform/modules/aws_metadata/main.tf
index 411d5a67b..1a3c31053 100644
--- a/terraform/a2ha-terraform/modules/aws_metadata/main.tf
+++ b/terraform/a2ha-terraform/modules/aws_metadata/main.tf
@@ -15,3 +15,10 @@ data "http" "getEc2PrivateIP" {
     X-aws-ec2-metadata-token = chomp(data.local_file.readToken.content)
   }
 }
+
+data "http" "getBastionRole" {
+  url = "http://169.254.169.254/latest/meta-data/iam/info"
+  request_headers = {
+    X-aws-ec2-metadata-token = chomp(data.local_file.readToken.content)
+  }
+}
diff --git a/terraform/a2ha-terraform/modules/aws_metadata/output.tf b/terraform/a2ha-terraform/modules/aws_metadata/output.tf
index 4209b78bb..e606c7b8c 100644
--- a/terraform/a2ha-terraform/modules/aws_metadata/output.tf
+++ b/terraform/a2ha-terraform/modules/aws_metadata/output.tf
@@ -1,3 +1,7 @@
 output "json_data" {
   value = "${data.http.getEc2PrivateIP.response_body}/32"
 }
+
+output "bastion_role" {
+  value = "${data.http.getBastionRole.status_code}"
+}
diff --git a/terraform/a2ha-terraform/reference_architectures/aws/variables.tf b/terraform/a2ha-terraform/reference_architectures/aws/variables.tf
index cacbde55f..880942e6d 100644
--- a/terraform/a2ha-terraform/reference_architectures/aws/variables.tf
+++ b/terraform/a2ha-terraform/reference_architectures/aws/variables.tf
@@ -51,7 +51,7 @@ variable "aws_os_snapshot_role_arn" {
 }
 
 variable "aws_profile" {
-  default     = "default"
+  default     = ""
   description = "The AWS profile to use from your ~/.aws/credentials file."
 }
 


Test patch:
diff --git a/components/automate-cli/cmd/chef-automate/automateHAAwsDeployment_test.go b/components/automate-cli/cmd/chef-automate/automateHAAwsDeployment_test.go
new file mode 100644
index 000000000..4d010f8f9
--- /dev/null
+++ b/components/automate-cli/cmd/chef-automate/automateHAAwsDeployment_test.go
@@ -0,0 +1,106 @@
+package main
+
+import (
+	"net/http"
+	"testing"
+
+	"github.com/chef/automate/lib/httputils"
+	"github.com/pkg/errors"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGetBastionIamRole(t *testing.T) {
+	ser := newAwsDeployemnt("")
+	tests := []struct {
+		description        string
+		MockhttputilsToken httputils.HTTPClient
+		MockhttputilsIam   httputils.HTTPClient
+		wantError          error
+		testurl            string
+	}{
+		{
+			description: "Failed to get token value from the request",
+			MockhttputilsToken: &httputils.MockHTTPClient{
+				MakeRequestWithHeadersfunc: func(requestMethod, url string, body interface{}, headerkey, headerValue string) (*http.Response, []byte, error) {
+					return nil, nil, errors.New("failed to marshal request body")
+				},
+			},
+			wantError: errors.New("error while getting the token value: failed to marshal request body"),
+			testurl:   TOKEN_URL,
+		},
+		{
+			description: "Failed to get token value from the request (error in creation of request)",
+			MockhttputilsToken: &httputils.MockHTTPClient{
+				MakeRequestWithHeadersfunc: func(requestMethod, url string, body interface{}, headerkey, headerValue string) (*http.Response, []byte, error) {
+					return nil, nil, errors.New("failed to create HTTP request")
+				},
+			},
+			wantError: errors.New("error while getting the token value: failed to create HTTP request"),
+			testurl:   TOKEN_URL,
+		},
+		{
+			description: "Bastion IAM role is not attached",
+			MockhttputilsToken: &httputils.MockHTTPClient{
+				MakeRequestWithHeadersfunc: func(requestMethod, url string, body interface{}, headerkey, headerValue string) (*http.Response, []byte, error) {
+					return &http.Response{}, nil, nil
+				},
+			},
+			MockhttputilsIam: &httputils.MockHTTPClient{
+				MakeRequestWithHeadersfunc: func(requestMethod, url string, body interface{}, headerkey, headerValue string) (*http.Response, []byte, error) {
+					return &http.Response{}, []byte("Success"), nil
+				},
+			},
+			wantError: errors.New("Please check if Bastion has attached an IAM Role to it"),
+			testurl:   METADATA_URL,
+		},
+		{
+			description: "Bastion IAM role is not attached (Error)",
+			MockhttputilsToken: &httputils.MockHTTPClient{
+				MakeRequestWithHeadersfunc: func(requestMethod, url string, body interface{}, headerkey, headerValue string) (*http.Response, []byte, error) {
+					return &http.Response{}, nil, nil
+				},
+			},
+			MockhttputilsIam: &httputils.MockHTTPClient{
+				MakeRequestWithHeadersfunc: func(requestMethod, url string, body interface{}, headerkey, headerValue string) (*http.Response, []byte, error) {
+					if requestMethod == http.MethodPut {
+						return nil, nil, nil
+					}
+					return nil, nil, errors.New("failed to Make HTTP Request")
+				},
+			},
+			wantError: errors.New("error while getting the response for IAM role: failed to Make HTTP Request"),
+			testurl:   METADATA_URL,
+		},
+		{
+			description: "Bastion IAM role is attached",
+			MockhttputilsToken: &httputils.MockHTTPClient{
+				MakeRequestWithHeadersfunc: func(requestMethod, url string, body interface{}, headerkey, headerValue string) (*http.Response, []byte, error) {
+					return &http.Response{}, nil, nil
+				},
+			},
+			MockhttputilsIam: &httputils.MockHTTPClient{
+				MakeRequestWithHeadersfunc: func(requestMethod, url string, body interface{}, headerkey, headerValue string) (*http.Response, []byte, error) {
+					return &http.Response{
+						StatusCode: 200,
+					}, []byte("Success"), nil
+				},
+			},
+			wantError: nil,
+			testurl:   METADATA_URL,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.description, func(t *testing.T) {
+			if tt.testurl == TOKEN_URL {
+				ser.httpRequestClient = tt.MockhttputilsToken
+			} else if tt.testurl == METADATA_URL {
+				ser.httpRequestClient = tt.MockhttputilsIam
+			}
+			err := ser.isIamRolePresent()
+			if err != nil {
+				assert.Equal(t, err.Error(), tt.wantError.Error())
+			}
+		})
+	}
+}
diff --git a/components/automate-cli/cmd/chef-automate/certRotate_test.go b/components/automate-cli/cmd/chef-automate/certRotate_test.go
index cde597978..0dd202698 100644
--- a/components/automate-cli/cmd/chef-automate/certRotate_test.go
+++ b/components/automate-cli/cmd/chef-automate/certRotate_test.go
@@ -2,11 +2,14 @@ package main
 
 import (
 	"os"
+	"path/filepath"
 	"testing"
+	"time"
 
 	"github.com/chef/automate/api/config/deployment"
 	"github.com/chef/automate/api/config/shared"
 	"github.com/chef/automate/lib/io/fileutils"
+	"github.com/chef/automate/lib/sshutils"
 	"github.com/pkg/errors"
 	"github.com/stretchr/testify/assert"
 	"google.golang.org/protobuf/types/known/wrapperspb"
@@ -49,11 +52,247 @@ xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
 VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
 WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8xY5Z=
 -----END CERTIFICATE-----`
-	ValidCertPath = "./certRotate.go"
+	ValidCertPath                = "./certRotate.go"
+	TestOpensearchAdminAndRootCA = `
+	[tls]
+	rootCA = """-----BEGIN CERTIFICATE-----
+	MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
+	MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
+	U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
+	NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
+	ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
+	ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
+	DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
+	8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
+	+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
+	X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
+	K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
+	1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
+	A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
+	zt0fhvRbVazc1xDCDqmI56FspGowaaBCDEfGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
+	YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
+	bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
+	DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
+	L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
+	eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
+	xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
+	VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
+	WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8xY5Z=
+	-----END CERTIFICATE-----"""
+	admin_cert = """-----BEGIN CERTIFICATE-----
+	MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
+	MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
+	U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
+	NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
+	ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
+	ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
+	DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
+	8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
+	+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
+	X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
+	K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
+	1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
+	A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
+	zt0fhvRbVazc1xDCDqmI56FspGowaaBCDEfGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
+	YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
+	bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
+	DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
+	L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
+	eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
+	xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
+	VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
+	WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8xY5Z=
+	-----END CERTIFICATE-----"""
+	admin_key = """-----BEGIN CERTIFICATE-----
+	MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
+	MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
+	U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
+	NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
+	ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
+	ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
+	DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
+	8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
+	+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
+	X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
+	K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
+	1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
+	A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
+	zt0fhvRbVazc1xDCDqmI56FspGowaaBCDEfGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
+	YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
+	bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
+	DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
+	L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
+	eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
+	xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
+	VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
+	WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8xY5Z=
+	-----END CERTIFICATE-----"""
+	ssl_cert = """-----BEGIN CERTIFICATE-----
+	MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
+	MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
+	U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
+	NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
+	ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
+	ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
+	DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
+	8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
+	+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
+	X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
+	K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
+	1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
+	A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
+	zt0fhvRbVazc1xDCDqmI56FspGowaaBCDEfGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
+	YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
+	bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
+	DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
+	L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
+	eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
+	xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
+	VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
+	WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8xY5Z=
+	-----END CERTIFICATE-----"""
+	ssl_key = """-----BEGIN CERTIFICATE-----
+	MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
+	MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
+	U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
+	NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
+	ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
+	ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
+	DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
+	8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
+	+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
+	X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
+	K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
+	1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
+	A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
+	zt0fhvRbVazc1xDCDqmI56FspGowaaBCDEfGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
+	YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
+	bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
+	DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
+	L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
+	eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
+	xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
+	VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
+	WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8xY5Z=
+	-----END CERTIFICATE-----"""
+[plugins.security.authcz]
+	admin_dn = '- CN=chefadmin,O=Chef Software Inc,L=Seattle,ST=Washington,C=US'
+[plugins.security.ssl.transport]
+	enforce_hostname_verification = false
+	resolve_hostname = false
+[plugins.security]
+	nodes_dn = '- CN=chefnode1,O=Chef Software Inc,L=Seattle,ST=Washington,C=US'`
+	TestFrontendConfig = `[[load_balancer.v1.sys.frontend_tls]]
+	cert = """-----BEGIN CERTIFICATE-----
+	MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
+	MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
+	U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
+	NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
+	ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
+	ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
+	DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
+	8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
+	+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
+	X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
+	K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
+	1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
+	A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
+	zt0fhvRbVazc1xDCDqmI56FspGowaaBCDEfGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
+	YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
+	bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
+	DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
+	L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
+	eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
+	xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
+	VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
+	WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8xY5Z=
+	-----END CERTIFICATE-----"""
+	key = """-----BEGIN CERTIFICATE-----
+	MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
+	MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
+	U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
+	NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
+	ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
+	ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
+	DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
+	8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
+	+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
+	X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
+	K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
+	1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
+	A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
+	zt0fhvRbVazc1xDCDqmI56FspGowaaBCDEfGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
+	YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
+	bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
+	DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
+	L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
+	eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
+	xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
+	VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
+	WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8xY5Z=
+	-----END CERTIFICATE-----"""
+[[global.v1.frontend_tls]]
+	cert = """-----BEGIN CERTIFICATE-----
+	MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
+	MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
+	U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
+	NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
+	ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
+	ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
+	DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
+	8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
+	+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
+	X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
+	K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
+	1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
+	A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
+	zt0fhvRbVazc1xDCDqmI56FspGowaaBCDEfGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
+	YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
+	bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
+	DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
+	L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
+	eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
+	xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
+	VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
+	WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8xY5Z=
+	-----END CERTIFICATE-----"""
+	key = """-----BEGIN CERTIFICATE-----
+	MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
+	MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
+	U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
+	NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
+	ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
+	ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
+	DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
+	8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
+	+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
+	X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
+	K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
+	1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
+	A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
+	zt0fhvRbVazc1xDCDqmI56FspGowaaBCDEfGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
+	YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
+	bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
+	DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
+	L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
+	eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
+	xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
+	VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
+	WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8xY5Z=
+	-----END CERTIFICATE-----"""`
+	testfile = `./testfiles/ssh`
 )
 
+var sshConfig = sshutils.SSHConfig{
+	SshUser:    "ubuntu",
+	SshPort:    "22",
+	SshKeyFile: testfile,
+	HostIP:     ValidIP,
+	Timeout:    150,
+}
+
 func TestIsRemotePath(t *testing.T) {
-	c := certRotateFlow{FileUtils: mockFS()}
+	c := certRotateFlow{fileUtils: mockFS()}
 
 	type testCaseInfo struct {
 		testCaseDescription string
@@ -98,7 +337,7 @@ func TestIsRemotePath(t *testing.T) {
 }
 
 func TestGetIPV4(t *testing.T) {
-	c := certRotateFlow{FileUtils: mockFS()}
+	c := certRotateFlow{fileUtils: mockFS()}
 
 	type testCaseInfo struct {
 		testCaseDescription string
@@ -143,7 +382,7 @@ func TestGetIPV4(t *testing.T) {
 }
 
 func TestGetRemoteFileDetails(t *testing.T) {
-	c := certRotateFlow{FileUtils: mockFS()}
+	c := certRotateFlow{fileUtils: mockFS()}
 
 	type testCaseInfo struct {
 		testCaseDescription    string
@@ -238,7 +477,7 @@ func TestGetRemoteFileDetails(t *testing.T) {
 }
 
 func TestGetCerts(t *testing.T) {
-	c := certRotateFlow{FileUtils: mockFS()}
+	c := certRotateFlow{fileUtils: mockFS()}
 
 	infra := &AutomateHAInfraDetails{}
 
@@ -708,63 +947,6 @@ func TestCompareCurrentCertsWithNewCerts(t *testing.T) {
 	}
 }
 
-func TestGetFrontEndIpsForSkippingRootCAPatching(t *testing.T) {
-	c, infra := getMockCertRotateFlowAndInfra()
-	type testCaseInfo struct {
-		testCaseDescription string
-		remoteService       string
-		newRootCA           string
-		currentCertsInfo    *certShowCertificates
-		skipIpsList         []string
-	}
-
-	testCases := []testCaseInfo{
-		{
-			testCaseDescription: "Automate root-ca patching in chef-server | same root-ca",
-			remoteService:       AUTOMATE,
-			newRootCA:           FileContent,
-			currentCertsInfo: &certShowCertificates{
-				AutomateRootCert: FileContent,
-			},
-			skipIpsList: c.getIps(CHEF_SERVER, infra),
-		},
-		{
-			testCaseDescription: "Automate root-ca patching in chef-server | different root-ca",
-			remoteService:       AUTOMATE,
-			newRootCA:           FileContent + "a",
-			currentCertsInfo: &certShowCertificates{
-				AutomateRootCert: FileContent,
-			},
-			skipIpsList: []string{},
-		},
-		{
-			testCaseDescription: "Postgresql root-ca patching in frontend | same root-ca",
-			remoteService:       POSTGRESQL,
-			newRootCA:           FileContent,
-			currentCertsInfo: &certShowCertificates{
-				PostgresqlRootCert: FileContent,
-			},
-			skipIpsList: c.getIps("frontend", infra),
-		},
-		{
-			testCaseDescription: "Postgresql root-ca patching in frontEnd | different root-ca",
-			remoteService:       POSTGRESQL,
-			newRootCA:           FileContent + "a",
-			currentCertsInfo: &certShowCertificates{
-				PostgresqlRootCert: FileContent,
-			},
-			skipIpsList: []string{},
-		},
-	}
-
-	for _, testCase := range testCases {
-		t.Run(testCase.testCaseDescription, func(t *testing.T) {
-			skipIpsListGot := c.getFrontEndIpsForSkippingRootCAPatching(testCase.remoteService, testCase.newRootCA, infra, testCase.currentCertsInfo)
-			assert.Equal(t, testCase.skipIpsList, skipIpsListGot)
-		})
-	}
-}
-
 func TestGetFrontEndIpsForSkippingCnAndRootCaPatching(t *testing.T) {
 	c, infra := getMockCertRotateFlowAndInfra()
 	nodesDn, _ := getDistinguishedNameFromKey(FileContent)
@@ -775,70 +957,71 @@ func TestGetFrontEndIpsForSkippingCnAndRootCaPatching(t *testing.T) {
 		newRootCA           string
 		newCn               string
 		oldCn               string
+		oldRootCA           string
 		node                string
-		currentCertsInfo    *certShowCertificates
 		infra               *AutomateHAInfraDetails
-		skipIpsList         []string
+		isIpSkip            bool
 	}
 
 	testCases := []testCaseInfo{
 		{
-			testCaseDescription: "Opensearch root-ca and cn patching | same cn and root-ca",
+			testCaseDescription: "Comparing old cn nd old rootCA | same cn and root-ca",
 			newRootCA:           FileContent,
+			oldRootCA:           FileContent,
 			newCn:               newCn,
 			oldCn:               newCn,
-			currentCertsInfo: &certShowCertificates{
-				OpensearchRootCert: FileContent,
-			},
-			infra:       infra,
-			skipIpsList: c.getIps("frontend", infra),
+			infra:               infra,
+			isIpSkip:            true,
 		},
 		{
-			testCaseDescription: "Opensearch root-ca and cn patching | same root-ca different cn",
+			testCaseDescription: "Comparing old cn nd old rootCA | same root-ca different cn",
 			newRootCA:           FileContent,
+			oldRootCA:           FileContent,
 			newCn:               newCn + "n",
 			oldCn:               "",
-			currentCertsInfo: &certShowCertificates{
-				OpensearchRootCert: FileContent,
-			},
-			infra:       infra,
-			skipIpsList: []string{},
+			infra:               infra,
+			isIpSkip:            false,
 		},
 		{
-			testCaseDescription: "Opensearch root-ca and cn patching | same cn and node flag",
+			testCaseDescription: "Comparing old cn nd old rootCA | different root-ca",
+			newRootCA:           FileContent + "a",
+			oldRootCA:           FileContent,
 			newCn:               newCn,
 			oldCn:               newCn,
-			node:                ValidIP4,
-			currentCertsInfo:    &certShowCertificates{},
 			infra:               infra,
-			skipIpsList:         c.getIps("frontend", infra),
+			isIpSkip:            false,
 		},
 		{
-			testCaseDescription: "Opensearch root-ca and cn patching | different cn and node flag",
-			newCn:               newCn + "n",
+			testCaseDescription: "Comparing old cn nd old rootCA | different root-ca same cn",
+			newRootCA:           FileContent + "a",
+			oldRootCA:           FileContent,
+			newCn:               newCn,
 			oldCn:               newCn,
-			node:                ValidIP4,
-			currentCertsInfo:    &certShowCertificates{},
 			infra:               infra,
-			skipIpsList:         []string{},
+			isIpSkip:            false,
 		},
 		{
-			testCaseDescription: "Opensearch root-ca and cn patching | different root-ca",
-			newRootCA:           FileContent + "a",
+			testCaseDescription: "Comparing old cn | different cn with node flag",
+			newCn:               newCn + "a",
+			oldCn:               newCn,
+			infra:               infra,
+			node:                ValidIP,
+			isIpSkip:            false,
+		},
+		{
+			testCaseDescription: "Comparing old cn | same cn with node flag",
 			newCn:               newCn,
 			oldCn:               newCn,
-			currentCertsInfo: &certShowCertificates{
-				OpensearchRootCert: FileContent,
-			},
-			infra:       infra,
-			skipIpsList: []string{},
+			infra:               infra,
+			node:                ValidIP,
+			isIpSkip:            true,
 		},
 	}
 
 	for _, testCase := range testCases {
 		t.Run(testCase.testCaseDescription, func(t *testing.T) {
-			skipIpsListGot := c.getFrontEndIpsForSkippingCnAndRootCaPatching(testCase.newRootCA, testCase.newCn, testCase.oldCn, testCase.node, testCase.currentCertsInfo, infra)
-			assert.Equal(t, testCase.skipIpsList, skipIpsListGot)
+			isIpSkipped := c.getFrontEndIpsForSkippingCnAndRootCaPatching(testCase.newRootCA, testCase.newCn, testCase.oldCn, testCase.oldRootCA, testCase.node)
+			assert.Equal(t, testCase.isIpSkip, isIpSkipped)
 		})
 	}
 }
@@ -878,16 +1061,23 @@ func TestGetFilteredIps(t *testing.T) {
 	}
 }
 
-func TestGetOldCn(t *testing.T) {
+func TestGetFrontIpsToSkipRootCAandCNPatchingForOs(t *testing.T) {
+	c, infra := getMockCertRotateFlowAndInfra()
 	type testCaseInfo struct {
 		testCaseDescription string
 		automatesConfig     *deployment.AutomateConfig
-		OldCnExpected       string
+		newRootCA           string
+		oldRootCA           string
+		newCn               string
+		oldCn               string
+		node                string
+		infra               *AutomateHAInfraDetails
+		skipIpsList         []string
 	}
 
 	testCases := []testCaseInfo{
 		{
-			testCaseDescription: "Getting cn value",
+			testCaseDescription: "Opensearch root-ca and cn patching | same rootca and CN ",
 			automatesConfig: &deployment.AutomateConfig{
 				Global: &shared.GlobalConfig{
 					V1: &shared.V1{
@@ -897,72 +1087,1245 @@ func TestGetOldCn(t *testing.T) {
 									ServerName: &wrapperspb.StringValue{
 										Value: "chefnode",
 									},
+									RootCert: &wrapperspb.StringValue{
+										Value: FileContent,
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			oldRootCA:   FileContent,
+			newRootCA:   FileContent,
+			newCn:       "chefnode",
+			oldCn:       "chefnode",
+			infra:       infra,
+			skipIpsList: []string{ValidIP},
+		},
+		{
+			testCaseDescription: "Opensearch root-ca and cn patching | diff rootca and CN",
+			automatesConfig: &deployment.AutomateConfig{
+				Global: &shared.GlobalConfig{
+					V1: &shared.V1{
+						External: &shared.External{Opensearch: &shared.External_Opensearch{
+							Ssl: &shared.External_Opensearch_SSL{
+								ServerName: &wrapperspb.StringValue{
+									Value: "chefnode",
+								},
+								RootCert: &wrapperspb.StringValue{
+									Value: FileContent,
+								},
+							},
+						},
+						},
+					},
+				},
+			},
+			oldRootCA:   FileContent,
+			newRootCA:   FileContent + "a",
+			newCn:       "chefnode1",
+			oldCn:       "chefnode",
+			infra:       infra,
+			skipIpsList: []string{},
+		},
+		{
+			testCaseDescription: "Opensearch root-ca and cn patching | same rootca and diff CN",
+			automatesConfig: &deployment.AutomateConfig{
+				Global: &shared.GlobalConfig{
+					V1: &shared.V1{
+						External: &shared.External{Opensearch: &shared.External_Opensearch{
+							Ssl: &shared.External_Opensearch_SSL{
+								ServerName: &wrapperspb.StringValue{
+									Value: "chefnode",
+								},
+								RootCert: &wrapperspb.StringValue{
+									Value: FileContent,
+								},
+							},
+						},
+						},
+					},
+				},
+			},
+			oldRootCA:   FileContent,
+			newRootCA:   FileContent,
+			newCn:       "chefnode1",
+			oldCn:       "chefnode",
+			infra:       infra,
+			skipIpsList: []string{},
+		},
+		{
+			testCaseDescription: "Opensearch root-ca and cn patching | diff rootca and same CN",
+			automatesConfig: &deployment.AutomateConfig{
+				Global: &shared.GlobalConfig{
+					V1: &shared.V1{
+						External: &shared.External{Opensearch: &shared.External_Opensearch{
+							Ssl: &shared.External_Opensearch_SSL{
+								ServerName: &wrapperspb.StringValue{
+									Value: "chefnode",
+								},
+								RootCert: &wrapperspb.StringValue{
+									Value: FileContent,
 								},
 							},
 						},
+						},
 					},
 				},
 			},
-			OldCnExpected: "chefnode",
+			oldRootCA:   FileContent,
+			newRootCA:   FileContent + "a",
+			newCn:       "chefnode",
+			oldCn:       "chefnode",
+			infra:       infra,
+			skipIpsList: []string{},
 		},
 		{
-			testCaseDescription: "Getting cn value with field are nil",
+			testCaseDescription: "Opensearch root-ca and cn patching | diff CN with node flag",
 			automatesConfig: &deployment.AutomateConfig{
 				Global: &shared.GlobalConfig{
 					V1: &shared.V1{
-						External: &shared.External{},
+						External: &shared.External{Opensearch: &shared.External_Opensearch{
+							Ssl: &shared.External_Opensearch_SSL{
+								ServerName: &wrapperspb.StringValue{
+									Value: "chefnode",
+								},
+							},
+						},
+						},
 					},
 				},
 			},
-			OldCnExpected: "",
+			newCn:       "chefnode1",
+			oldCn:       "chefnode",
+			node:        ValidIP,
+			infra:       infra,
+			skipIpsList: []string{},
 		},
 		{
-			testCaseDescription: "Getting cn value server name as nil",
+			testCaseDescription: "Opensearch root-ca and cn patching | same CN with node flag",
+			automatesConfig: &deployment.AutomateConfig{
+				Global: &shared.GlobalConfig{
+					V1: &shared.V1{
+						External: &shared.External{Opensearch: &shared.External_Opensearch{
+							Ssl: &shared.External_Opensearch_SSL{
+								ServerName: &wrapperspb.StringValue{
+									Value: "chefnode",
+								},
+							},
+						},
+						},
+					},
+				},
+			},
+			newCn:       "chefnode",
+			oldCn:       "chefnode",
+			node:        ValidIP,
+			infra:       infra,
+			skipIpsList: []string{ValidIP},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.testCaseDescription, func(t *testing.T) {
+			configMap := map[string]*deployment.AutomateConfig{
+				ValidIP: testCase.automatesConfig,
+			}
+			skipIpsListGot := c.getFrontIpsToSkipRootCAandCNPatchingForOs(configMap, testCase.newRootCA, testCase.newCn, testCase.node, infra)
+			assert.Equal(t, testCase.skipIpsList, skipIpsListGot)
+
+		})
+	}
+}
+
+func TestGetFrontendIPsToSkipRootCAPatchingForPg(t *testing.T) {
+	c, infra := getMockCertRotateFlowAndInfra()
+	type testCaseInfo struct {
+		testCaseDescription string
+		automatesConfig     *deployment.AutomateConfig
+		newRootCA           string
+		oldRootCA           string
+		infra               *AutomateHAInfraDetails
+		skipIpsList         []string
+	}
+
+	testCases := []testCaseInfo{
+		{
+			testCaseDescription: "Postgresql root-ca comparing | same rootca ",
 			automatesConfig: &deployment.AutomateConfig{
 				Global: &shared.GlobalConfig{
 					V1: &shared.V1{
 						External: &shared.External{
-							Opensearch: &shared.External_Opensearch{
-								Ssl: &shared.External_Opensearch_SSL{},
+							Postgresql: &shared.External_Postgresql{
+								Ssl: &shared.External_Postgresql_SSL{
+									RootCert: &wrapperspb.StringValue{
+										Value: FileContent,
+									},
+								},
 							},
 						},
 					},
 				},
 			},
-			OldCnExpected: "",
+			oldRootCA:   FileContent,
+			newRootCA:   FileContent,
+			infra:       infra,
+			skipIpsList: []string{ValidIP},
 		},
 		{
-			testCaseDescription: "Getting cn value server name as nil",
+			testCaseDescription: "Postgresql root-ca comparing | diff rootca ",
 			automatesConfig: &deployment.AutomateConfig{
 				Global: &shared.GlobalConfig{
 					V1: &shared.V1{
-						External: &shared.External{},
+						External: &shared.External{
+							Postgresql: &shared.External_Postgresql{
+								Ssl: &shared.External_Postgresql_SSL{
+									RootCert: &wrapperspb.StringValue{
+										Value: FileContent,
+									},
+								},
+							},
+						},
 					},
 				},
 			},
-			OldCnExpected: "",
+			oldRootCA:   FileContent,
+			newRootCA:   FileContent + "a",
+			infra:       infra,
+			skipIpsList: []string{},
 		},
 	}
 
 	for _, testCase := range testCases {
-		t.Run("GetOldCn", func(t *testing.T) {
+		t.Run(testCase.testCaseDescription, func(t *testing.T) {
 			configMap := map[string]*deployment.AutomateConfig{
 				ValidIP: testCase.automatesConfig,
 			}
-			oldCn := getOldCn(configMap)
-			assert.Equal(t, testCase.OldCnExpected, oldCn)
+			skipIpsListGot := c.getFrontendIPsToSkipRootCAPatchingForPg(configMap, testCase.newRootCA, infra)
+			assert.Equal(t, testCase.skipIpsList, skipIpsListGot)
+
 		})
 	}
 }
 
-func getMockCertRotateFlowAndInfra() (certRotateFlow, *AutomateHAInfraDetails) {
-	c := certRotateFlow{FileUtils: mockFS()}
+func TestGetSkipIpsListForPgRootCAPatching(t *testing.T) {
+	infra := NewMockInfra()
+	c := NewCertRotate()
+
+	type testCaseInfo struct {
+		description     string
+		sshUtil         SSHUtil
+		certs           *certificates
+		infra           *AutomateHAInfraDetails
+		MockPullConfigs PullConfigs
+		skipIpsList     []string
+		ExpectedError   string
+		wantError       bool
+	}
+	testCases := []testCaseInfo{
+		{
+			description: "same root ca",
+			sshUtil:     GetMockSSHUtil(&SSHConfig{}, nil, completedMessage, nil, "", nil),
+			certs: &certificates{
+				publicCert:  FileContent,
+				privateCert: FileContent,
+				rootCA:      FileContent,
+			},
+			infra: infra,
+			MockPullConfigs: &MockPullConfigs{
+				pullAutomateConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP7: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Postgresql: &shared.External_Postgresql{
+											Ssl: &shared.External_Postgresql_SSL{
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+				pullChefServerConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP7: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Postgresql: &shared.External_Postgresql{
+											Ssl: &shared.External_Postgresql_SSL{
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+			},
+
+			skipIpsList:   []string{ValidIP7, ValidIP7},
+			ExpectedError: "",
+			wantError:     false,
+		},
+		{
+			description: "Different rootca",
+			sshUtil:     GetMockSSHUtil(&SSHConfig{}, nil, completedMessage, nil, "", nil),
+			certs: &certificates{
+				rootCA: FileContent + "a",
+			},
+			infra: infra,
+			MockPullConfigs: &MockPullConfigs{
+				pullAutomateConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP7: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Postgresql: &shared.External_Postgresql{
+											Ssl: &shared.External_Postgresql_SSL{
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+				pullChefServerConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP7: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Postgresql: &shared.External_Postgresql{
+											Ssl: &shared.External_Postgresql_SSL{
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+			},
+
+			skipIpsList:   []string{},
+			ExpectedError: "",
+			wantError:     false,
+		},
+		{
+			description: "Error while pulling chefserver config",
+			sshUtil:     GetMockSSHUtil(&SSHConfig{}, nil, completedMessage, nil, "", nil),
+			certs: &certificates{
+				publicCert:  FileContent,
+				privateCert: FileContent,
+				rootCA:      FileContent,
+			},
+			infra: infra,
+			MockPullConfigs: &MockPullConfigs{
+				pullAutomateConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP7: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Postgresql: &shared.External_Postgresql{
+											Ssl: &shared.External_Postgresql_SSL{
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+				pullChefServerConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return nil, errors.New("ERROR")
+				},
+			},
+
+			skipIpsList:   []string{},
+			ExpectedError: "ERROR",
+			wantError:     true,
+		},
+		{
+			description: "Error while pulling Automate config",
+			sshUtil:     GetMockSSHUtil(&SSHConfig{}, nil, completedMessage, nil, "", nil),
+			certs: &certificates{
+				publicCert:  FileContent,
+				privateCert: FileContent,
+				rootCA:      FileContent,
+			},
+			infra: infra,
+			MockPullConfigs: &MockPullConfigs{
+				pullAutomateConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return nil, errors.New("ERROR")
+				},
+				pullChefServerConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return nil, errors.New("ERROR")
+				},
+			},
+
+			skipIpsList:   []string{},
+			ExpectedError: "ERROR",
+			wantError:     true,
+		},
+	}
+	for _, testCase := range testCases {
+		t.Run(testCase.description, func(t *testing.T) {
+			c.pullConfigs = testCase.MockPullConfigs
+			skipIpsListGot, err := c.getSkipIpsListForPgRootCAPatching(testCase.infra, testCase.sshUtil, testCase.certs)
+			if !testCase.wantError {
+				assert.Equal(t, testCase.skipIpsList, skipIpsListGot)
+			} else {
+				assert.Error(t, err)
+			}
+
+		})
+	}
+}
+
+func TestGetSkipIpsListForOsRootCACNPatching(t *testing.T) {
+	infra := NewMockInfra()
+	c := NewCertRotate()
+
+	type testCaseInfo struct {
+		description     string
+		sshUtil         SSHUtil
+		certs           *certificates
+		infra           *AutomateHAInfraDetails
+		MockPullConfigs PullConfigs
+		nodesCn         string
+		flagsObj        *certRotateFlags
+		skipIpsList     []string
+		ExpectedError   string
+		wantError       bool
+	}
+	testCases := []testCaseInfo{
+		{
+			description: "same root ca and cn in all ips",
+			sshUtil:     GetMockSSHUtil(&SSHConfig{}, nil, completedMessage, nil, "", nil),
+			certs: &certificates{
+				rootCA: FileContent,
+			},
+			infra:    infra,
+			nodesCn:  "chefnode",
+			flagsObj: &certRotateFlags{},
+			MockPullConfigs: &MockPullConfigs{
+				pullAutomateConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Opensearch: &shared.External_Opensearch{
+											Ssl: &shared.External_Opensearch_SSL{
+												ServerName: &wrapperspb.StringValue{
+													Value: "chefnode",
+												},
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+				pullChefServerConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP1: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Opensearch: &shared.External_Opensearch{
+											Ssl: &shared.External_Opensearch_SSL{
+												ServerName: &wrapperspb.StringValue{
+													Value: "chefnode",
+												},
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+			},
+
+			skipIpsList:   []string{ValidIP, ValidIP1},
+			ExpectedError: "",
+			wantError:     false,
+		},
+		{
+			description: "same root ca in all ips but different cn in one ip",
+			sshUtil:     GetMockSSHUtil(&SSHConfig{}, nil, completedMessage, nil, "", nil),
+			certs: &certificates{
+				rootCA: FileContent,
+			},
+			infra:    infra,
+			nodesCn:  "chefnode",
+			flagsObj: &certRotateFlags{},
+			MockPullConfigs: &MockPullConfigs{
+				pullAutomateConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Opensearch: &shared.External_Opensearch{
+											Ssl: &shared.External_Opensearch_SSL{
+												ServerName: &wrapperspb.StringValue{
+													Value: "chefnode",
+												},
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+				pullChefServerConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP1: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Opensearch: &shared.External_Opensearch{
+											Ssl: &shared.External_Opensearch_SSL{
+												ServerName: &wrapperspb.StringValue{
+													Value: "chefnodee",
+												},
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+			},
+
+			skipIpsList:   []string{ValidIP},
+			ExpectedError: "",
+			wantError:     false,
+		},
+		{
+			description: "diff root ca and cn in all ips",
+			sshUtil:     GetMockSSHUtil(&SSHConfig{}, nil, completedMessage, nil, "", nil),
+			certs: &certificates{
+				rootCA: FileContent + "a",
+			},
+			infra:    infra,
+			nodesCn:  "chefnodee",
+			flagsObj: &certRotateFlags{},
+			MockPullConfigs: &MockPullConfigs{
+				pullAutomateConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Opensearch: &shared.External_Opensearch{
+											Ssl: &shared.External_Opensearch_SSL{
+												ServerName: &wrapperspb.StringValue{
+													Value: "chefnode",
+												},
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+				pullChefServerConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP1: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Opensearch: &shared.External_Opensearch{
+											Ssl: &shared.External_Opensearch_SSL{
+												ServerName: &wrapperspb.StringValue{
+													Value: "chefnode",
+												},
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+			},
+
+			skipIpsList:   []string{},
+			ExpectedError: "",
+			wantError:     false,
+		},
+		{
+			description: "same CN with node flag",
+			sshUtil:     GetMockSSHUtil(&SSHConfig{}, nil, completedMessage, nil, "", nil),
+			certs: &certificates{
+				rootCA: FileContent,
+			},
+			infra: infra,
+			flagsObj: &certRotateFlags{
+				node: ValidIP4,
+			},
+			nodesCn: "chefnode",
+			MockPullConfigs: &MockPullConfigs{
+				pullAutomateConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Opensearch: &shared.External_Opensearch{
+											Ssl: &shared.External_Opensearch_SSL{
+												ServerName: &wrapperspb.StringValue{
+													Value: "chefnode",
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+				pullChefServerConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP1: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Opensearch: &shared.External_Opensearch{
+											Ssl: &shared.External_Opensearch_SSL{
+												ServerName: &wrapperspb.StringValue{
+													Value: "chefnode",
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+			},
+
+			skipIpsList:   []string{ValidIP, ValidIP1},
+			ExpectedError: "",
+			wantError:     false,
+		},
+		{
+			description: "diff CN with node flag",
+			sshUtil:     GetMockSSHUtil(&SSHConfig{}, nil, completedMessage, nil, "", nil),
+			certs: &certificates{
+				rootCA: FileContent + "a",
+			},
+			infra: infra,
+			flagsObj: &certRotateFlags{
+				node: ValidIP4,
+			},
+			nodesCn: "chefnode",
+			MockPullConfigs: &MockPullConfigs{
+				pullAutomateConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Opensearch: &shared.External_Opensearch{
+											Ssl: &shared.External_Opensearch_SSL{
+												ServerName: &wrapperspb.StringValue{
+													Value: "chefnodee",
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+				pullChefServerConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP1: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Opensearch: &shared.External_Opensearch{
+											Ssl: &shared.External_Opensearch_SSL{
+												ServerName: &wrapperspb.StringValue{
+													Value: "chefnodee",
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+			},
+
+			skipIpsList:   []string{},
+			ExpectedError: "",
+			wantError:     false,
+		},
+		{
+			description: "Error while fetching chefserver config",
+			sshUtil:     GetMockSSHUtil(&SSHConfig{}, nil, completedMessage, nil, "", nil),
+			certs: &certificates{
+				publicCert:  FileContent,
+				privateCert: FileContent,
+				rootCA:      FileContent,
+			},
+			infra: infra,
+			flagsObj: &certRotateFlags{
+				node: ValidIP4,
+			},
+			MockPullConfigs: &MockPullConfigs{
+				pullAutomateConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP7: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Postgresql: &shared.External_Postgresql{
+											Ssl: &shared.External_Postgresql_SSL{
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+				pullChefServerConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return nil, errors.New("ERROR")
+				},
+			},
+
+			skipIpsList:   []string{},
+			ExpectedError: "ERROR",
+			wantError:     true,
+		},
+		{
+			description: "Error while fetching automate config",
+			sshUtil:     GetMockSSHUtil(&SSHConfig{}, nil, completedMessage, nil, "", nil),
+			certs: &certificates{
+				publicCert:  FileContent,
+				privateCert: FileContent,
+				rootCA:      FileContent,
+			},
+			infra: infra,
+			MockPullConfigs: &MockPullConfigs{
+				pullAutomateConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return nil, errors.New("ERROR")
+				},
+				pullChefServerConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP7: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Postgresql: &shared.External_Postgresql{
+											Ssl: &shared.External_Postgresql_SSL{
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+			},
+
+			skipIpsList:   []string{},
+			ExpectedError: "ERROR",
+			wantError:     true,
+		},
+		{
+			description: "same root ca and cn in one of the automate and chefserver ips ",
+			sshUtil:     GetMockSSHUtil(&SSHConfig{}, nil, completedMessage, nil, "", nil),
+			certs: &certificates{
+				rootCA: FileContent,
+			},
+			infra:    infra,
+			nodesCn:  "chefnode",
+			flagsObj: &certRotateFlags{},
+			MockPullConfigs: &MockPullConfigs{
+				pullAutomateConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Opensearch: &shared.External_Opensearch{
+											Ssl: &shared.External_Opensearch_SSL{
+												ServerName: &wrapperspb.StringValue{
+													Value: "chefnodee",
+												},
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent + "a",
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+						ValidIP1: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Opensearch: &shared.External_Opensearch{
+											Ssl: &shared.External_Opensearch_SSL{
+												ServerName: &wrapperspb.StringValue{
+													Value: "chefnode",
+												},
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+				pullChefServerConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP2: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Opensearch: &shared.External_Opensearch{
+											Ssl: &shared.External_Opensearch_SSL{
+												ServerName: &wrapperspb.StringValue{
+													Value: "chefnode",
+												},
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+						ValidIP3: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Opensearch: &shared.External_Opensearch{
+											Ssl: &shared.External_Opensearch_SSL{
+												ServerName: &wrapperspb.StringValue{
+													Value: "chefnodee",
+												},
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent + "a",
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+			},
+
+			skipIpsList:   []string{ValidIP1, ValidIP2},
+			ExpectedError: "",
+			wantError:     false,
+		},
+		{
+			description: "same rootca and cn in chefserver",
+			sshUtil:     GetMockSSHUtil(&SSHConfig{}, nil, completedMessage, nil, "", nil),
+			certs: &certificates{
+				rootCA: FileContent,
+			},
+			infra:    infra,
+			nodesCn:  "chefnode",
+			flagsObj: &certRotateFlags{},
+			MockPullConfigs: &MockPullConfigs{
+				pullAutomateConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Opensearch: &shared.External_Opensearch{
+											Ssl: &shared.External_Opensearch_SSL{
+												ServerName: &wrapperspb.StringValue{
+													Value: "chefnodee",
+												},
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent + "a",
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+				pullChefServerConfigsFunc: func() (map[string]*deployment.AutomateConfig, error) {
+					return map[string]*deployment.AutomateConfig{
+						ValidIP2: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Opensearch: &shared.External_Opensearch{
+											Ssl: &shared.External_Opensearch_SSL{
+												ServerName: &wrapperspb.StringValue{
+													Value: "chefnode",
+												},
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+						ValidIP3: {
+							Global: &shared.GlobalConfig{
+								V1: &shared.V1{
+									External: &shared.External{
+										Opensearch: &shared.External_Opensearch{
+											Ssl: &shared.External_Opensearch_SSL{
+												ServerName: &wrapperspb.StringValue{
+													Value: "chefnode",
+												},
+												RootCert: &wrapperspb.StringValue{
+													Value: FileContent,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					}, nil
+				},
+			},
+
+			skipIpsList:   []string{ValidIP2, ValidIP3},
+			ExpectedError: "",
+			wantError:     false,
+		},
+	}
+	for _, testCase := range testCases {
+		t.Run(testCase.description, func(t *testing.T) {
+			c.pullConfigs = testCase.MockPullConfigs
+			skipIpsListGot, err := c.getSkipIpsListForOsRootCACNPatching(testCase.infra, testCase.sshUtil, testCase.certs, testCase.nodesCn, testCase.flagsObj)
+			if !testCase.wantError {
+				assert.Equal(t, testCase.skipIpsList, skipIpsListGot)
+			} else {
+				assert.Error(t, err)
+			}
+
+		})
+	}
+}
+
+func TestPatchConfig(t *testing.T) {
+	_, infra := getMockCertRotateFlowAndInfra()
+	type testCaseInfo struct {
+		description   string
+		param         *patchFnParameters
+		MockSSHUtil   sshutils.SSHUtil
+		isError       bool
+		ExpectedError string
+	}
+	testCases := []testCaseInfo{
+		{
+			description: "Test Case to rotate on backend",
+			param: &patchFnParameters{
+				sshUtil:       getMockSSHUtil(&SSHConfig{}, nil, "", nil),
+				config:        TestOpensearchAdminAndRootCA,
+				fileName:      "cert-rotate-os.toml",
+				timestamp:     time.Now().Format("20060102150405"),
+				remoteService: OPENSEARCH,
+				concurrent:    false,
+				infra:         infra,
+				flagsObj: &certRotateFlags{
+					opensearch: true,
+				},
+				skipIpsList: []string{},
+			},
+			MockSSHUtil: &sshutils.MockSSHUtilsImpl{
+				CopyFileToRemoteConcurrentlyFunc: func(sshConfig sshutils.SSHConfig, srcFilePath string, destFileName string, removeFile bool, hostIPs []string) []sshutils.Result {
+					return []sshutils.Result{
+						{
+							HostIP: "",
+							Error:  nil,
+							Output: "",
+						},
+					}
+				},
+			},
+			isError:       false,
+			ExpectedError: "",
+		},
+		{
+			description: "Test Case to rotate on frontend",
+			param: &patchFnParameters{
+				sshUtil:       getMockSSHUtil(&SSHConfig{}, nil, "", nil),
+				config:        TestFrontendConfig,
+				fileName:      "cert-rotate-fe.toml",
+				timestamp:     time.Now().Format("20060102150405"),
+				remoteService: AUTOMATE,
+				concurrent:    true,
+				infra:         infra,
+				flagsObj: &certRotateFlags{
+					automate: true,
+				},
+				skipIpsList: []string{},
+			},
+			MockSSHUtil: &sshutils.MockSSHUtilsImpl{
+				CopyFileToRemoteConcurrentlyFunc: func(sshConfig sshutils.SSHConfig, srcFilePath string, destFileName string, removeFile bool, hostIPs []string) []sshutils.Result {
+					return []sshutils.Result{
+						{
+							HostIP: "",
+							Error:  nil,
+							Output: "",
+						},
+					}
+				},
+				ExecuteConcurrentlyFunc: func(sshConfig sshutils.SSHConfig, cmd string, hostIPs []string) []sshutils.Result {
+					return []sshutils.Result{
+						{
+							HostIP: "",
+							Error:  nil,
+							Output: "",
+						},
+					}
+				},
+			},
+			isError:       false,
+			ExpectedError: "",
+		},
+		{
+			description: "remote copying failed on on frontend",
+			param: &patchFnParameters{
+				config:        TestFrontendConfig,
+				fileName:      "cert-rotate-fe.toml",
+				timestamp:     time.Now().Format("20060102150405"),
+				remoteService: AUTOMATE,
+				concurrent:    true,
+				infra:         infra,
+				flagsObj: &certRotateFlags{
+					automate: true,
+				},
+				skipIpsList: []string{},
+			},
+			MockSSHUtil: &sshutils.MockSSHUtilsImpl{
+				CopyFileToRemoteConcurrentlyFunc: func(sshConfig sshutils.SSHConfig, srcFilePath string, destFileName string, removeFile bool, hostIPs []string) []sshutils.Result {
+					return []sshutils.Result{
+						{
+							HostIP: "",
+							Error:  errors.New("remote copying failed on node"),
+							Output: "",
+						},
+					}
+				},
+				ExecuteConcurrentlyFunc: func(sshConfig sshutils.SSHConfig, cmd string, hostIPs []string) []sshutils.Result {
+					return []sshutils.Result{
+						{
+							HostIP: "",
+							Error:  nil,
+							Output: "",
+						},
+					}
+				},
+			},
+			isError:       true,
+			ExpectedError: "remote copying failed on node",
+		},
+		{
+			description: "remote execution failed on on frontend",
+			param: &patchFnParameters{
+				config:        TestFrontendConfig,
+				fileName:      "cert-rotate-fe.toml",
+				timestamp:     time.Now().Format("20060102150405"),
+				remoteService: AUTOMATE,
+				concurrent:    true,
+				infra:         infra,
+				flagsObj: &certRotateFlags{
+					automate: true,
+				},
+				skipIpsList: []string{},
+			},
+			MockSSHUtil: &sshutils.MockSSHUtilsImpl{
+				CopyFileToRemoteConcurrentlyFunc: func(sshConfig sshutils.SSHConfig, srcFilePath string, destFileName string, removeFile bool, hostIPs []string) []sshutils.Result {
+					return []sshutils.Result{
+						{
+							HostIP: "",
+							Error:  nil,
+							Output: "",
+						},
+					}
+				},
+				ExecuteConcurrentlyFunc: func(sshConfig sshutils.SSHConfig, cmd string, hostIPs []string) []sshutils.Result {
+					return []sshutils.Result{
+						{
+							HostIP: "",
+							Error:  errors.New("remote execution failed"),
+							Output: "",
+						},
+					}
+				},
+			},
+			isError:       false,
+			ExpectedError: "",
+		},
+		{
+			description: "Deployment service error on frontend",
+			param: &patchFnParameters{
+				config:        TestFrontendConfig,
+				fileName:      "cert-rotate-fe.toml",
+				timestamp:     time.Now().Format("20060102150405"),
+				remoteService: AUTOMATE,
+				concurrent:    true,
+				infra:         infra,
+				flagsObj: &certRotateFlags{
+					automate: true,
+				},
+				skipIpsList: []string{},
+			},
+			MockSSHUtil: &sshutils.MockSSHUtilsImpl{
+				CopyFileToRemoteConcurrentlyFunc: func(sshConfig sshutils.SSHConfig, srcFilePath string, destFileName string, removeFile bool, hostIPs []string) []sshutils.Result {
+					return []sshutils.Result{
+						{
+							HostIP: "",
+							Error:  nil,
+							Output: "",
+						},
+					}
+				},
+				ExecuteConcurrentlyFunc: func(sshConfig sshutils.SSHConfig, cmd string, hostIPs []string) []sshutils.Result {
+					return []sshutils.Result{
+						{
+							HostIP: "",
+							Error:  nil,
+							Output: "DeploymentServiceCallError",
+						},
+					}
+				},
+			},
+			isError:       false,
+			ExpectedError: "",
+		},
+		{
+			description: "Error occured while reading infra details on backend",
+			param: &patchFnParameters{
+				sshUtil:       getMockSSHUtil(&SSHConfig{}, nil, "", errors.Errorf("Error occured while reading infra details")),
+				config:        TestOpensearchAdminAndRootCA,
+				fileName:      "cert-rotate-os.toml",
+				timestamp:     time.Now().Format("20060102150405"),
+				remoteService: OPENSEARCH,
+				concurrent:    false,
+				infra:         infra,
+				flagsObj: &certRotateFlags{
+					opensearch: true,
+				},
+				skipIpsList: []string{},
+			},
+			isError:       true,
+			ExpectedError: "Error occured while reading infra details",
+		},
+	}
+	for _, testCase := range testCases {
+		t.Run(testCase.description, func(t *testing.T) {
+			c := certRotateFlow{fileUtils: mockFS(),
+				sshUtil: testCase.MockSSHUtil,
+				writer:  getMockWriterImpl()}
+			testDir := t.TempDir()
+			fPath := filepath.Join(testDir, testCase.param.fileName)
+			testCase.param.fileName = fPath
+			output := c.patchConfig(testCase.param)
+			if testCase.isError {
+				assert.EqualError(t, output, testCase.ExpectedError)
+			} else {
+				assert.NoError(t, output)
+			}
+		})
+	}
+}
+
+func getMockCertRotateFlowAndInfra() (*certRotateFlow, *AutomateHAInfraDetails) {
+	return NewCertRotate(), NewMockInfra()
+}
+
+func NewMockInfra() *AutomateHAInfraDetails {
 	infra := &AutomateHAInfraDetails{}
 	infra.Outputs.AutomatePrivateIps.Value = []string{ValidIP, ValidIP1}
 	infra.Outputs.ChefServerPrivateIps.Value = []string{ValidIP2, ValidIP3}
 	infra.Outputs.OpensearchPrivateIps.Value = []string{ValidIP4, ValidIP5, ValidIP6}
 	infra.Outputs.PostgresqlPrivateIps.Value = []string{ValidIP7, ValidIP8, ValidIP9}
-	return c, infra
+	infra.Outputs.SSHUser.Value = "ubuntu"
+	infra.Outputs.SSHKeyFile.Value = "new.pem"
+	infra.Outputs.SSHPort.Value = "22"
+	return infra
+}
+
+func NewCertRotate() *certRotateFlow {
+	c := NewCertRotateFlow(mockFS(), &sshutils.MockSSHUtilsImpl{}, writer, &MockPullConfigs{})
+	return c
 }
 
 func mockFS() *fileutils.MockFileSystemUtils {
diff --git a/components/automate-cli/cmd/chef-automate/deployedConfig_test.go b/components/automate-cli/cmd/chef-automate/deployedConfig_test.go
index ab9e9838c..c317beed1 100644
--- a/components/automate-cli/cmd/chef-automate/deployedConfig_test.go
+++ b/components/automate-cli/cmd/chef-automate/deployedConfig_test.go
@@ -295,6 +295,10 @@ type MockPullConfigs struct {
 	getExceptionIpsFunc       func() []string
 	setExceptionIpsFunc       func(ips []string)
 	getOsCertsByIpFunc        func(map[string]*ConfigKeys) []CertByIP
+	setInfraAndSSHUtilFunc    func(*AutomateHAInfraDetails, SSHUtil)
+}
+
+func (m *MockPullConfigs) setInfraAndSSHUtil(*AutomateHAInfraDetails, SSHUtil) {
 }
 
 func (m *MockPullConfigs) fetchInfraConfig() (*ExistingInfraConfigToml, error) {
diff --git a/components/automate-cli/cmd/chef-automate/summary_test.go b/components/automate-cli/cmd/chef-automate/summary_test.go
index 25ffdd136..7ba3456ef 100644
--- a/components/automate-cli/cmd/chef-automate/summary_test.go
+++ b/components/automate-cli/cmd/chef-automate/summary_test.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"errors"
 	"fmt"
 	"strings"
 	"testing"
@@ -27,13 +28,17 @@ Incorrect postgresql IP, 127.0.0 IP address validation failed
 Incorrect postgresql IP, 127.0.1 IP address validation failed
 Incorrect postgresql IP, 127.0.2 IP address validation failed
 Incorrect postgresql IP, 127.0.3 IP address validation failed`
-	displayBE = `+------------+--------------+--------+--------------+-------------+---------+
-| NAME       | IP ADDRESS   | HEALTH | PROCESS      | UPTIME      | ROLE    |
-+------------+--------------+--------+--------------+-------------+---------+
-| postgresql | 198.51.100.7 | ERROR  | down (pid: ) | 0d 0h 0m 0s | Unknown |
-+------------+--------------+--------+--------------+-------------+---------+`
-	displayFEAutomate         = `| automate    | 198.51.100.1 | ERROR  | Unknown    |`
-	displayFECS               = `| chef-server | 198.51.100.2 | ERROR  | Unknown    |`
+	displayBE = `+------------+--------------+--------+--------------+-----+-------------+---------+
+| NAME       | IP ADDRESS   | HEALTH | PROCESS      | LAG | UPTIME      | ROLE    |
++------------+--------------+--------+--------------+-----+-------------+---------+
+| postgresql | 198.51.100.7 | ERROR  | down (pid: ) | NA  | 0d 0h 0m 0s | Unknown |
++------------+--------------+--------+--------------+-----+-------------+---------+`
+	pgFollowerNode = `+------------+--------------+--------+----------------+-----------------------+---------------+----------+
+| NAME       | IP ADDRESS   | HEALTH | PROCESS        | LAG                   | UPTIME        | ROLE     |
++------------+--------------+--------+----------------+-----------------------+---------------+----------+
+| postgresql | 198.51.100.7 | OK     | up (pid: 2760) | 0 bytes and 1 seconds |`
+	displayFEAutomate         = `| automate    | 198.51.100.1 | OK     | "green" (Active: 100.0) |`
+	displayFECS               = `| chef-server | 198.51.100.2 | OK     | "green" (Active: 100.0) |`
 	mockA2haHabitatAutoTfvars = "../../pkg/testfiles/a2ha_habitat.auto.tfvars"
 )
 
@@ -43,7 +48,11 @@ func TestCheckIPAddressesFromInfra(t *testing.T) {
 	infra.Outputs.ChefServerPrivateIps.Value = []string{ValidIP2, ValidIP3}
 	infra.Outputs.OpensearchPrivateIps.Value = []string{ValidIP4, ValidIP5, ValidIP6}
 	infra.Outputs.PostgresqlPrivateIps.Value = []string{ValidIP7, ValidIP8, ValidIP9}
-	ss := NewStatusSummary(infra, FeStatus{}, BeStatus{}, 10, time.Second, &StatusSummaryCmdFlags{}, getMockSSHUtilRunSummary(&SSHConfig{}, nil, nil))
+	ss := NewStatusSummary(infra, FeStatus{}, BeStatus{}, 10, time.Second, &StatusSummaryCmdFlags{}, &MockRemoteCmdExecutor{
+		ExecuteWithNodeMapFunc: func(nodeMap *NodeTypeAndCmd) (map[string][]*CmdResult, error) {
+			return nil, nil
+		},
+	})
 
 	automateIps, chefServerIps, opensearchIps, postgresqlIps, errList := ss.(*Summary).getIPAddressesFromFlagOrInfra()
 	assert.Equal(t, errList.Len(), 0)
@@ -66,7 +75,11 @@ func TestCheckIPAddressesByServicesAndIpFromFlag(t *testing.T) {
 		isChefServer: true,
 		isOpenSearch: true,
 		isPostgresql: true,
-	}, getMockSSHUtilRunSummary(&SSHConfig{}, nil, nil))
+	}, &MockRemoteCmdExecutor{
+		ExecuteWithNodeMapFunc: func(nodeMap *NodeTypeAndCmd) (map[string][]*CmdResult, error) {
+			return nil, nil
+		},
+	})
 
 	automateIps, chefServerIps, opensearchIps, postgresqlIps, errList := ss.(*Summary).getIPAddressesFromFlagOrInfra()
 	assert.Equal(t, errList.Len(), 0)
@@ -88,7 +101,11 @@ func TestCheckIPAddressesOnlyByServices(t *testing.T) {
 		isChefServer: true,
 		isOpenSearch: true,
 		isPostgresql: true,
-	}, getMockSSHUtilRunSummary(&SSHConfig{}, nil, nil))
+	}, &MockRemoteCmdExecutor{
+		ExecuteWithNodeMapFunc: func(nodeMap *NodeTypeAndCmd) (map[string][]*CmdResult, error) {
+			return nil, nil
+		},
+	})
 
 	automateIps, chefServerIps, opensearchIps, postgresqlIps, errList := ss.(*Summary).getIPAddressesFromFlagOrInfra()
 	assert.Equal(t, errList.Len(), 0)
@@ -111,7 +128,11 @@ func TestCheckIPAddressesError(t *testing.T) {
 		isChefServer: true,
 		isOpenSearch: true,
 		isPostgresql: true,
-	}, getMockSSHUtilRunSummary(&SSHConfig{}, nil, nil))
+	}, &MockRemoteCmdExecutor{
+		ExecuteWithNodeMapFunc: func(nodeMap *NodeTypeAndCmd) (map[string][]*CmdResult, error) {
+			return nil, nil
+		},
+	})
 
 	_, _, _, _, errList := ss.(*Summary).getIPAddressesFromFlagOrInfra()
 	assert.Equal(t, errList.Len(), 1)
@@ -131,7 +152,11 @@ func TestCheckIPAddressesValidation(t *testing.T) {
 		isChefServer: true,
 		isOpenSearch: true,
 		isPostgresql: true,
-	}, getMockSSHUtilRunSummary(&SSHConfig{}, nil, nil))
+	}, &MockRemoteCmdExecutor{
+		ExecuteWithNodeMapFunc: func(nodeMap *NodeTypeAndCmd) (map[string][]*CmdResult, error) {
+			return nil, nil
+		},
+	})
 
 	automateIps, chefServerIps, opensearchIps, postgresqlIps, errList := ss.(*Summary).getIPAddressesFromFlagOrInfra()
 	assert.Equal(t, errList.Len(), 16)
@@ -141,13 +166,57 @@ func TestCheckIPAddressesValidation(t *testing.T) {
 	assert.Equal(t, postgresqlIps, []string(nil))
 	assert.Contains(t, getSingleErrorFromList(errList).Error(), invalidIpAddress)
 }
+
+// {OsStatus 10.0.192.172 []  <nil>}
 func TestRunFENodeDiaplay(t *testing.T) {
 	a2haHabitatAutoTfvars = mockA2haHabitatAutoTfvars
 	infra := &AutomateHAInfraDetails{}
 	infra.Outputs.AutomatePrivateIps.Value = []string{ValidIP1}
 	infra.Outputs.ChefServerPrivateIps.Value = []string{ValidIP2}
-	sshUtil := getMockSSHUtilRunSummary(&SSHConfig{}, nil, nil)
-	ss := NewStatusSummary(infra, FeStatus{}, BeStatus{}, 10, time.Second, &StatusSummaryCmdFlags{}, sshUtil)
+	ss := NewStatusSummary(infra, FeStatus{}, BeStatus{}, 10, time.Second, &StatusSummaryCmdFlags{}, &MockRemoteCmdExecutor{
+		ExecuteWithNodeMapFunc: func(nodeMap *NodeTypeAndCmd) (map[string][]*CmdResult, error) {
+			if strings.Contains(strings.Join(nodeMap.Automate.CmdInputs.NodeIps, ","), ValidIP1) {
+
+				return map[string][]*CmdResult{
+					ValidIP1: {
+						&CmdResult{
+							ScriptName:  "OsStatus",
+							HostIP:      ValidIP1,
+							OutputFiles: []string{},
+							Error:       nil,
+							Output:      `{"cluster_name":"opensearch","status":"green","timed_out":false,"number_of_nodes":3,"number_of_data_nodes":3,"discovered_master":true,"active_primary_shards":35,"active_shards":74,"relocating_shards":0,"initializing_shards":0,"unassigned_shards":0,"delayed_unassigned_shards":0,"number_of_pending_tasks":0,"number_of_in_flight_fetch":0,"task_max_waiting_in_queue_millis":0,"active_shards_percent_as_number":100.0}`,
+						},
+						&CmdResult{
+							ScriptName:  "Status",
+							HostIP:      ValidIP1,
+							OutputFiles: []string{},
+							Error:       nil,
+							Output:      ``,
+						},
+					},
+				}, nil
+				// displayFEAutomate, nil
+			}
+			return map[string][]*CmdResult{
+				ValidIP2: {
+					&CmdResult{
+						ScriptName:  "OsStatus",
+						HostIP:      ValidIP2,
+						OutputFiles: []string{},
+						Error:       nil,
+						Output:      `{"cluster_name":"opensearch","status":"green","timed_out":false,"number_of_nodes":3,"number_of_data_nodes":3,"discovered_master":true,"active_primary_shards":35,"active_shards":74,"relocating_shards":0,"initializing_shards":0,"unassigned_shards":0,"delayed_unassigned_shards":0,"number_of_pending_tasks":0,"number_of_in_flight_fetch":0,"task_max_waiting_in_queue_millis":0,"active_shards_percent_as_number":100.0}`,
+					},
+					&CmdResult{
+						ScriptName:  "Status",
+						HostIP:      ValidIP2,
+						OutputFiles: []string{},
+						Error:       nil,
+						Output:      ``,
+					},
+				},
+			}, nil
+		},
+	})
 	err := ss.Prepare()
 	assert.NoError(t, err)
 	fe := ss.ShowFEStatus()
@@ -155,22 +224,6 @@ func TestRunFENodeDiaplay(t *testing.T) {
 	assert.Contains(t, fe, displayFECS)
 
 }
-func TestRunBENodeDiaplay(t *testing.T) {
-	a2haHabitatAutoTfvars = mockA2haHabitatAutoTfvars
-	infra := &AutomateHAInfraDetails{}
-	// infra.Outputs.OpensearchPrivateIps.Value = []string{ValidIP4, ValidIP5, ValidIP6}
-	infra.Outputs.PostgresqlPrivateIps.Value = []string{ValidIP7}
-	sshUtil := getMockSSHUtilRunSummary(&SSHConfig{hostIP: ValidIP7}, nil, nil)
-
-	ss := NewStatusSummary(infra, FeStatus{}, BeStatus{}, 10, time.Second, &StatusSummaryCmdFlags{}, sshUtil)
-	// Mock the time to a specific time
-	mockTime := time.Date(2023, 3, 15, 12, 0, 0, 0, time.UTC)
-	nowFunc = func() time.Time { return mockTime }
-	err := ss.Prepare()
-	assert.NoError(t, err)
-	be := ss.ShowBEStatus()
-	assert.Equal(t, be, displayBE)
-}
 
 func getMockSSHUtilRunSummary(sshConfig *SSHConfig, CFTRError error, CSECORError error) *MockSSHUtilsImpl {
 	return &MockSSHUtilsImpl{
@@ -266,3 +319,141 @@ func getMockSSHUtilRunSummary(sshConfig *SSHConfig, CFTRError error, CSECORError
 		},
 	}
 }
+
+func TestGetFollowerLag(t *testing.T) {
+	a2haHabitatAutoTfvars = mockA2haHabitatAutoTfvars
+
+	testCases := []struct {
+		description string
+		beOutput    map[string][]*CmdResult
+		expectedErr error
+		ip          string
+		wantErr     bool
+	}{
+		{
+			description: "Showing Lag with No Error for Follower node",
+			beOutput: map[string][]*CmdResult{
+				ValidIP7: {
+					&CmdResult{
+						ScriptName:  censusDetails,
+						HostIP:      ValidIP7,
+						OutputFiles: []string{},
+						Output: `{
+							"changed": false,
+							"census_groups": {
+								"automate-ha-postgresql.default": {
+									"population": {
+										"8cfc79a0be3c4e74ab717bac25a1d185": {
+											"leader": false,
+											"follower": true
+											}
+										}
+									}
+								}
+							}`,
+						Error: nil,
+					},
+					&CmdResult{
+						ScriptName:  defaultServiceDetails,
+						HostIP:      ValidIP7,
+						OutputFiles: []string{},
+						Output: `{
+							"process": { "pid": 2760, "state": "up", "state_entered": 1689602436 },
+							"sys": {
+								"member_id": "8cfc79a0be3c4e74ab717bac25a1d185"
+							}
+						}`,
+						Error: nil,
+					},
+					&CmdResult{
+						ScriptName:  defaultServiceHealthDetails,
+						HostIP:      ValidIP7,
+						OutputFiles: []string{},
+						Output:      `{"status":"OK","stdout":"Primary server is available at 10.0.192.111 with uptime: 0s\nLocal replica 10.0.192.209 is 0 bytes and 1 seconds behind the primary\n","stderr":""}`,
+						Error:       nil,
+					},
+				},
+			},
+			ip:          ValidIP7,
+			expectedErr: nil,
+			wantErr:     false,
+		},
+		{
+			description: "Showing Lag with Parsing Error",
+			beOutput: map[string][]*CmdResult{
+				ValidIP7: {
+					&CmdResult{
+						ScriptName:  censusDetails,
+						HostIP:      ValidIP7,
+						OutputFiles: []string{},
+						Output: `{
+							"changed": false,
+							"census_groups": {
+								"automate-ha-postgresql.default": {
+									"population": {
+										"8cfc79a0be3c4e74ab717bac25a1d185": {
+											"leader": false,
+											"follower": true
+											
+										}
+									}
+								}
+							}`,
+						Error: nil,
+					},
+					&CmdResult{
+						ScriptName:  defaultServiceDetails,
+						HostIP:      ValidIP7,
+						OutputFiles: []string{},
+						Output: `{
+							"process": { "pid": 2760, "state": "up", "state_entered": 1689602436 ,
+							"sys": {
+								"member_id": "8cfc79a0be3c4e74ab717bac25a1d185"
+							}
+						}`,
+						Error: nil,
+					},
+					&CmdResult{
+						ScriptName:  defaultServiceHealthDetails,
+						HostIP:      ValidIP7,
+						OutputFiles: []string{},
+						Output:      `{"status":"OK","stdout":"Primary server is available at 10.0.192.111 with uptime: 0s\nLocal replica 10.0.192.209 is 0 bytes and 1 seconds behind the primary\n","stderr":"}`,
+						Error:       nil,
+					},
+				},
+			},
+			ip:          ValidIP7,
+			expectedErr: errors.New("ERROR"),
+			wantErr:     true,
+		},
+	}
+
+	for _, test := range testCases {
+
+		infra := &AutomateHAInfraDetails{}
+		infra.Outputs.PostgresqlPrivateIps.Value = []string{test.ip}
+		ss := NewStatusSummary(infra, FeStatus{}, BeStatus{}, 10, time.Second, &StatusSummaryCmdFlags{
+			node:         test.ip,
+			isPostgresql: true,
+		}, &MockRemoteCmdExecutor{
+			ExecuteWithNodeMapFunc: func(nodeMap *NodeTypeAndCmd) (map[string][]*CmdResult, error) {
+				return test.beOutput, nil
+			},
+		})
+
+		err := ss.Prepare()
+		assert.NoError(t, err)
+		be := ss.ShowBEStatus()
+
+		if test.wantErr {
+			assert.Contains(t, be, ValidIP7)
+			assert.Contains(t, be, test.expectedErr.Error())
+
+		} else {
+			assert.Contains(t, be, ValidIP7)
+			assert.Contains(t, be, "0 bytes and 1 seconds")
+			assert.Contains(t, be, "up (pid: 2760)")
+			assert.Contains(t, be, "postgresql")
+		}
+	}
+}
diff --git a/components/automate-cli/pkg/verifyserver/services/batchcheckservice/batchcheckservice_test.go b/components/automate-cli/pkg/verifyserver/services/batchcheckservice/batchcheckservice_test.go
index 370889ea1..343e54a58 100644
--- a/components/automate-cli/pkg/verifyserver/services/batchcheckservice/batchcheckservice_test.go
+++ b/components/automate-cli/pkg/verifyserver/services/batchcheckservice/batchcheckservice_test.go
@@ -1117,20 +1117,20 @@ func TestConstructResult(t *testing.T) {
 	result = constructResult(ipMap)
 	assert.Len(t, result, 2, "Result should have two items")
 
-	for _, v := range result {
+	for i, v := range result {
 		// Check the nodeType1
 		if v.NodeType == "nodeType1" {
-			assert.Equal(t, "host1", result[0].Ip, "IP should be 'host1'")
-			assert.Len(t, result[0].Tests, 1, "Tests should have one item")
-			assert.Equal(t, true, result[0].Tests[0].Passed, "Passed should be true")
-			assert.Equal(t, "Test message 1", result[0].Tests[0].Message, "Message should be 'Test message 1'")
+			assert.Equal(t, "host1", result[i].Ip, "IP should be 'host1'")
+			assert.Len(t, result[i].Tests, 1, "Tests should have one item")
+			assert.Equal(t, true, result[i].Tests[0].Passed, "Passed should be true")
+			assert.Equal(t, "Test message 1", result[i].Tests[0].Message, "Message should be 'Test message 1'")
 		}
 		if v.NodeType == "nodeType2" {
-			assert.Equal(t, "host2", result[1].Ip, "IP should be 'host2'")
-			assert.Equal(t, "nodeType2", result[1].NodeType, "NodeType should be 'nodeType2'")
-			assert.Len(t, result[1].Tests, 1, "Tests should have one item")
-			assert.Equal(t, true, result[1].Tests[0].Passed, "Passed should be true")
-			assert.Equal(t, "Test message 2", result[1].Tests[0].Message, "Message should be 'Test message 2'")
+			assert.Equal(t, "host2", result[i].Ip, "IP should be 'host2'")
+			assert.Equal(t, "nodeType2", result[i].NodeType, "NodeType should be 'nodeType2'")
+			assert.Len(t, result[i].Tests, 1, "Tests should have one item")
+			assert.Equal(t, true, result[i].Tests[0].Passed, "Passed should be true")
+			assert.Equal(t, "Test message 2", result[i].Tests[0].Message, "Message should be 'Test message 2'")
 		}
 
 	}
diff --git a/components/automate-cli/pkg/verifyserver/services/batchcheckservice/trigger/hardwareresourcechecktrigger/hardwareresourcecountchecktrigger_test.go b/components/automate-cli/pkg/verifyserver/services/batchcheckservice/trigger/hardwareresourcechecktrigger/hardwareresourcecountchecktrigger_test.go
index 6071c13ce..c2455b543 100644
--- a/components/automate-cli/pkg/verifyserver/services/batchcheckservice/trigger/hardwareresourcechecktrigger/hardwareresourcecountchecktrigger_test.go
+++ b/components/automate-cli/pkg/verifyserver/services/batchcheckservice/trigger/hardwareresourcechecktrigger/hardwareresourcecountchecktrigger_test.go
@@ -295,7 +295,7 @@ func TestHardwareResourceCountCheck_TriggerHardwareResourceCountCheck(t *testing
 	t.Run("cannot reach", func(t *testing.T) {
 		// create the CheckTrigger instance to be tested
 		hrc := HardwareResourceCountCheck{log: logger.NewTestLogger(), httpRequestClient: &httputils.MockHTTPClient{
-			func(requestMethod, url string, body interface{}) (*http.Response, []byte, error) {
+			MakeRequestFunc : func(requestMethod, url string, body interface{}) (*http.Response, []byte, error) {
 				return nil, []byte(""), errors.New("")
 			},
 		},host: "invalid-url"}
diff --git a/components/automate-cli/pkg/verifyserver/services/batchcheckservice/trigger/s3backupchecktrigger/s3backupconfigchecktrigger_test.go b/components/automate-cli/pkg/verifyserver/services/batchcheckservice/trigger/s3backupchecktrigger/s3backupconfigchecktrigger_test.go
index 987bb3ab3..cb6bc35e2 100644
--- a/components/automate-cli/pkg/verifyserver/services/batchcheckservice/trigger/s3backupchecktrigger/s3backupconfigchecktrigger_test.go
+++ b/components/automate-cli/pkg/verifyserver/services/batchcheckservice/trigger/s3backupchecktrigger/s3backupconfigchecktrigger_test.go
@@ -8,11 +8,10 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/stretchr/testify/assert"
-
 	"github.com/chef/automate/components/automate-cli/pkg/verifyserver/constants"
 	"github.com/chef/automate/components/automate-cli/pkg/verifyserver/models"
 	"github.com/chef/automate/lib/logger"
+	"github.com/stretchr/testify/assert"
 )
 
 const (
@@ -429,3 +428,60 @@ func TestGetPortsForMockServer(t *testing.T) {
 
 	assert.Equal(t, 0, len(resp))
 }
+
+func TestS3ConfigSkippedResponse(t *testing.T) {
+	type args struct {
+		config    *models.Config
+		checkType string
+	}
+	tests := []struct {
+		name string
+		args args
+		want []models.CheckTriggerResponse
+	}{
+		{
+			name: "Make the skip Response",
+			args: args{
+				config: &models.Config{
+					Hardware: &models.Hardware{
+						AutomateNodeCount:        1,
+						AutomateNodeIps:          []string{constants.LOCALHOST},
+						ChefInfraServerNodeCount: 1,
+						ChefInfraServerNodeIps:   []string{constants.LOCALHOST},
+					},
+				},
+				checkType: "s3-backup-config",
+			},
+			want: []models.CheckTriggerResponse{
+				{
+					NodeType:  "automate",
+					CheckType: "s3-backup-config",
+					Result: models.ApiResult{
+						Passed:      false,
+						Skipped:     true,
+						Check:       "s3-backup-config",
+						SkipMessage: constants.SKIP_BACKUP_TEST_MESSAGE_S3,
+					},
+					Host: constants.LOCALHOST,
+				},
+				{
+					NodeType:  "chef-infra-server",
+					CheckType: "s3-backup-config",
+					Result: models.ApiResult{
+						Passed:      false,
+						Skipped:     true,
+						Check:       "s3-backup-config",
+						SkipMessage: constants.SKIP_BACKUP_TEST_MESSAGE_S3,
+					},
+					Host: constants.LOCALHOST,
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := s3ConfigSkippedResponse(tt.args.config, tt.args.checkType, constants.SKIP_BACKUP_TEST_MESSAGE_S3)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
diff --git a/lib/config/hadeployconfigverify_test.go b/lib/config/hadeployconfigverify_test.go
index 22ae5b900..087e584b8 100644
--- a/lib/config/hadeployconfigverify_test.go
+++ b/lib/config/hadeployconfigverify_test.go
@@ -21,7 +21,7 @@ func TestParseAndVerify(t *testing.T) {
 			name:    "Verify AWS Config",
 			args:    args{configFile: "./testdata/HaAws.toml"},
 			wantErr: true,
-			err:     errors.New("invalid or empty: ssh_user\ninvalid or empty: ssh_key_file\ninvalid or empty s3_bucketName\ninvalid or empty: aws profile name\ninvalid or empty: aws region\ninvalid or empty: aws aws_vpc_id\ninvalid or empty: aws private_custom_subnets\ninvalid or empty: aws public_custom_subnets\ninvalid or empty: aws ssh_key_pair_name\ninvalid or empty: aws ami_id\ninvalid or empty: aws automate_server_instance_type\ninvalid or empty: aws automate_lb_certificate_arn\nempty value: aws automate_ebs_volume_iops\nempty value: aws automate_ebs_volume_size\ninvalid or empty: aws automate_ebs_volume_type\nempty value: aws chef_ebs_volume_iops\nempty value: aws chef_ebs_volume_size\ninvalid or empty: aws chef_ebs_volume_type\ninvalid or empty: aws chef_server_instance_type\ninvalid or empty: aws chef_server_lb_certificate_arn\ninvalid or empty: aws opensearch_server_instance_type\ninvalid or empty: aws postgresql_server_instance_type\nempty value: aws opensearch_ebs_volume_iops\nempty value: aws opensearch_ebs_volume_size\ninvalid or empty: aws opensearch_ebs_volume_type\nempty value: aws postgresql_ebs_volume_iops\nempty value: aws postgresql_ebs_volume_size\ninvalid or empty: aws postgresql_ebs_volume_type\ninvalid or empty URL: automate fqdn\nempty value: automate instance_count\nempty value: chef server instance_count\nempty value: opensearch instance_count\nempty value: postgresql instance_count"),
+			err:     errors.New("invalid or empty: ssh_user\ninvalid or empty: ssh_key_file\ninvalid or empty s3_bucketName\ninvalid or empty: aws region\ninvalid or empty: aws aws_vpc_id\ninvalid or empty: aws private_custom_subnets\ninvalid or empty: aws public_custom_subnets\ninvalid or empty: aws ssh_key_pair_name\ninvalid or empty: aws ami_id\ninvalid or empty: aws automate_server_instance_type\ninvalid or empty: aws automate_lb_certificate_arn\nempty value: aws automate_ebs_volume_iops\nempty value: aws automate_ebs_volume_size\ninvalid or empty: aws automate_ebs_volume_type\nempty value: aws chef_ebs_volume_iops\nempty value: aws chef_ebs_volume_size\ninvalid or empty: aws chef_ebs_volume_type\ninvalid or empty: aws chef_server_instance_type\ninvalid or empty: aws chef_server_lb_certificate_arn\ninvalid or empty: aws opensearch_server_instance_type\ninvalid or empty: aws postgresql_server_instance_type\nempty value: aws opensearch_ebs_volume_iops\nempty value: aws opensearch_ebs_volume_size\ninvalid or empty: aws opensearch_ebs_volume_type\nempty value: aws postgresql_ebs_volume_iops\nempty value: aws postgresql_ebs_volume_size\ninvalid or empty: aws postgresql_ebs_volume_type\ninvalid or empty URL: automate fqdn\nempty value: automate instance_count\nempty value: chef server instance_count\nempty value: opensearch instance_count\nempty value: postgresql instance_count"),
 		},
 		{
 			name:    "Verify AWS Managed Config",
@@ -51,13 +51,13 @@ func TestParseAndVerify(t *testing.T) {
 			name:    "Verify Aws Chef Managed Config",
 			args:    args{configFile: "./testdata/HaAwsChefManaged.toml"},
 			wantErr: true,
-			err:     errors.New("invalid value '1234567' for field 'ssh_port' port number must be between 1 and 65535\ninvalid or empty: ssh_key_file\ninvalid or empty: aws profile name\ninvalid or empty: aws region\ninvalid or empty: aws aws_vpc_id\nminimum number of aws private_custom_subnets required is 3\ninvalid or empty: aws public_custom_subnets\ninvalid or empty: aws ssh_key_pair_name\ninvalid or empty: aws ami_id\ninvalid or empty: aws automate_server_instance_type\ninvalid or empty: aws automate_lb_certificate_arn\nempty value: aws automate_ebs_volume_iops\nempty value: aws automate_ebs_volume_size\ninvalid or empty: aws automate_ebs_volume_type\nempty value: aws chef_ebs_volume_iops\nempty value: aws chef_ebs_volume_size\ninvalid or empty: aws chef_ebs_volume_type\ninvalid or empty: aws chef_server_instance_type\ninvalid or empty: aws chef_server_lb_certificate_arn\ninvalid or empty: aws opensearch_server_instance_type\ninvalid or empty: aws postgresql_server_instance_type\nempty value: aws opensearch_ebs_volume_iops\nempty value: aws opensearch_ebs_volume_size\ninvalid or empty: aws opensearch_ebs_volume_type\nempty value: aws postgresql_ebs_volume_iops\nempty value: aws postgresql_ebs_volume_size\ninvalid or empty: aws postgresql_ebs_volume_type\ninvalid or empty URL: automate fqdn\npassword is too short (must be at least 8 characters)\nempty value: automate instance_count\nautomate public_key and/or private_key are missing. Otherwise set enable_custom_certs to false\nempty value: chef server instance_count\nchefServer public_key and/or private_key are missing. Otherwise set enable_custom_certs to false\nempty value: opensearch instance_count\nopensearch root_ca and/or admin_key and/or admin_cert and/or public_key and/or private_key are missing. Otherwise set enable_custom_certs to false\nempty value: postgresql instance_count\npostgresql root_ca and/or public_key and/or private_key are missing. Otherwise set enable_custom_certs to false"),
+			err:     errors.New("invalid value '1234567' for field 'ssh_port' port number must be between 1 and 65535\ninvalid or empty: ssh_key_file\ninvalid or empty: aws region\ninvalid or empty: aws aws_vpc_id\nminimum number of aws private_custom_subnets required is 3\ninvalid or empty: aws public_custom_subnets\ninvalid or empty: aws ssh_key_pair_name\ninvalid or empty: aws ami_id\ninvalid or empty: aws automate_server_instance_type\ninvalid or empty: aws automate_lb_certificate_arn\nempty value: aws automate_ebs_volume_iops\nempty value: aws automate_ebs_volume_size\ninvalid or empty: aws automate_ebs_volume_type\nempty value: aws chef_ebs_volume_iops\nempty value: aws chef_ebs_volume_size\ninvalid or empty: aws chef_ebs_volume_type\ninvalid or empty: aws chef_server_instance_type\ninvalid or empty: aws chef_server_lb_certificate_arn\ninvalid or empty: aws opensearch_server_instance_type\ninvalid or empty: aws postgresql_server_instance_type\nempty value: aws opensearch_ebs_volume_iops\nempty value: aws opensearch_ebs_volume_size\ninvalid or empty: aws opensearch_ebs_volume_type\nempty value: aws postgresql_ebs_volume_iops\nempty value: aws postgresql_ebs_volume_size\ninvalid or empty: aws postgresql_ebs_volume_type\ninvalid or empty URL: automate fqdn\npassword is too short (must be at least 8 characters)\nempty value: automate instance_count\nautomate public_key and/or private_key are missing. Otherwise set enable_custom_certs to false\nempty value: chef server instance_count\nchefServer public_key and/or private_key are missing. Otherwise set enable_custom_certs to false\nempty value: opensearch instance_count\nopensearch root_ca and/or admin_key and/or admin_cert and/or public_key and/or private_key are missing. Otherwise set enable_custom_certs to false\nempty value: postgresql instance_count\npostgresql root_ca and/or public_key and/or private_key are missing. Otherwise set enable_custom_certs to false"),
 		},
 		{
 			name:    "Verify Failed Ha Aws Managed Config",
 			args:    args{configFile: "./testdata/HaAwsManagedFailed.toml"},
 			wantErr: true,
-			err:     errors.New("invalid or empty: secrets_key_file\ninvalid or empty: secrets_store_file\ninvalid value 'awss' for field 'architecture'. Expected values are: aws, existing_nodes, deployment\ninvalid value '/hab/a3_deploy_workspace' for field 'workspace_path'. Expected values are: /hab/a2_deploy_workspace\ninvalid or empty: ssh_user\ninvalid or empty: ssh_key_file\ninvalid or empty s3_bucketName\ninvalid or empty: aws profile name\ninvalid or empty: aws region\ninvalid or empty: aws private_custom_subnets\ninvalid or empty: aws public_custom_subnets\ninvalid or empty: aws ssh_key_pair_name\ninvalid value 'aws lb_access_logs' for field 'fals'\ninvalid or empty: aws ami_id\ninvalid or empty: aws automate_server_instance_type\ninvalid or empty: aws automate_lb_certificate_arn\nempty value: aws automate_ebs_volume_iops\nempty value: aws automate_ebs_volume_size\ninvalid or empty: aws automate_ebs_volume_type\nempty value: aws chef_ebs_volume_iops\nempty value: aws chef_ebs_volume_size\ninvalid or empty: aws chef_ebs_volume_type\ninvalid or empty: aws chef_server_instance_type\ninvalid or empty: aws chef_server_lb_certificate_arn\ninvalid or empty: aws managed_opensearch_domain_name\ninvalid or empty URL: aws managed_opensearch_domain_url\ninvalid or empty: aws managed_opensearch_user_password\ninvalid or empty: aws managed_opensearch_username\ninvalid or empty: aws managed_rds_dbuser_password\ninvalid or empty: aws managed_rds_dbuser_username\ninvalid or empty URL: aws managed_rds_instance_url\ninvalid or empty: aws managed_rds_superuser_password\ninvalid or empty: aws managed_rds_superuser_username\ninvalid or empty URL: automate fqdn\nempty value: automate instance_count\nempty value: chef server instance_count"),
+			err:     errors.New("invalid or empty: secrets_key_file\ninvalid or empty: secrets_store_file\ninvalid value 'awss' for field 'architecture'. Expected values are: aws, existing_nodes, deployment\ninvalid value '/hab/a3_deploy_workspace' for field 'workspace_path'. Expected values are: /hab/a2_deploy_workspace\ninvalid or empty: ssh_user\ninvalid or empty: ssh_key_file\ninvalid or empty s3_bucketName\ninvalid or empty: aws region\ninvalid or empty: aws private_custom_subnets\ninvalid or empty: aws public_custom_subnets\ninvalid or empty: aws ssh_key_pair_name\ninvalid value 'aws lb_access_logs' for field 'fals'\ninvalid or empty: aws ami_id\ninvalid or empty: aws automate_server_instance_type\ninvalid or empty: aws automate_lb_certificate_arn\nempty value: aws automate_ebs_volume_iops\nempty value: aws automate_ebs_volume_size\ninvalid or empty: aws automate_ebs_volume_type\nempty value: aws chef_ebs_volume_iops\nempty value: aws chef_ebs_volume_size\ninvalid or empty: aws chef_ebs_volume_type\ninvalid or empty: aws chef_server_instance_type\ninvalid or empty: aws chef_server_lb_certificate_arn\ninvalid or empty: aws managed_opensearch_domain_name\ninvalid or empty URL: aws managed_opensearch_domain_url\ninvalid or empty: aws managed_opensearch_user_password\ninvalid or empty: aws managed_opensearch_username\ninvalid or empty: aws managed_rds_dbuser_password\ninvalid or empty: aws managed_rds_dbuser_username\ninvalid or empty URL: aws managed_rds_instance_url\ninvalid or empty: aws managed_rds_superuser_password\ninvalid or empty: aws managed_rds_superuser_username\ninvalid or empty URL: automate fqdn\nempty value: automate instance_count\nempty value: chef server instance_count"),
 		},
 		{
 			name:    "Verify Failed Ha On Prem Aws Managed Config",
diff --git a/lib/httputils/http_test.go b/lib/httputils/http_test.go
index 92d2b8ccd..86dae82a0 100644
--- a/lib/httputils/http_test.go
+++ b/lib/httputils/http_test.go
@@ -95,3 +95,86 @@ func TestMakeRequestConnectionError(t *testing.T) {
 	assert.Nil(t, responseBody)
 	assert.Contains(t, err.Error(), "context deadline exceeded")
 }
+
+func TestMakeRequestWithHeaderSuccessfulRequest(t *testing.T) {
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+		assert.Equal(t, http.MethodPost, r.Method)
+		assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
+
+		body, err := io.ReadAll(r.Body)
+		assert.NoError(t, err)
+
+		var requestBody map[string]interface{}
+		err = json.Unmarshal(body, &requestBody)
+		assert.NoError(t, err)
+
+		expectedBody := map[string]interface{}{
+			"key1": "value1",
+			"key2": "42",
+		}
+		assert.Equal(t, expectedBody, requestBody)
+
+		w.WriteHeader(http.StatusOK)
+		w.Write([]byte("Success"))
+	}))
+	defer server.Close()
+
+	logger, err := logger.NewLogger("text", "debug")
+	assert.NoError(t, err)
+	client := httputils.NewClient(logger)
+
+	url := server.URL
+	requestBody := map[string]interface{}{
+		"key1": "value1",
+		"key2": "42",
+	}
+	resp, responseBody, err := client.MakeRequestWithHeaders(http.MethodPost, url, requestBody, "Content-Type", "application/json")
+
+	assert.NoError(t, err)
+	assert.NotNil(t, resp)
+	assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+	expectedResponseBody := "Success"
+	assert.Equal(t, expectedResponseBody, string(responseBody))
+}
+
+func TestMakeRequestWithHeaderConnectionError(t *testing.T) {
+	logger, err := logger.NewLogger("text", "debug")
+	assert.NoError(t, err)
+
+	client := httputils.NewClientWithTimeout(1*time.Millisecond, logger)
+
+	resp, responseBody, err := client.MakeRequestWithHeaders(http.MethodGet, "http://non-existent-server", nil, "Content-Type", "application/json")
+
+	assert.Error(t, err)
+	assert.Nil(t, resp)
+	assert.Nil(t, responseBody)
+	assert.Contains(t, err.Error(), "context deadline exceeded")
+}
+
+func TestMakeRequestWithHeaderRequestError(t *testing.T) {
+	logger, err := logger.NewLogger("text", "debug")
+	assert.NoError(t, err)
+	client := httputils.NewClient(logger)
+
+	resp, responseBody, err := client.MakeRequestWithHeaders(http.MethodGet, "invalid-url", nil, "Content-Type", "application/json")
+
+	assert.Error(t, err)
+	assert.Nil(t, resp)
+	assert.Nil(t, responseBody)
+	assert.Contains(t, err.Error(), "failed to make HTTP request")
+}
+
+func TestMakeRequestWithHeaderRequestBodyError(t *testing.T) {
+	logger, err := logger.NewLogger("text", "debug")
+	assert.NoError(t, err)
+	client := httputils.NewClient(logger)
+
+	resp, responseBody, err := client.MakeRequestWithHeaders(http.MethodPost, "https://example.com", make(chan int), "Content-Type", "application/json")
+
+	assert.Error(t, err)
+	assert.Nil(t, resp)
+	assert.Nil(t, responseBody)
+	assert.Contains(t, err.Error(), "failed to marshal request body")
+}
diff --git a/lib/sshutils/sshutils_test.go b/lib/sshutils/sshutils_test.go
index ccb52cfca..cba3057a0 100644
--- a/lib/sshutils/sshutils_test.go
+++ b/lib/sshutils/sshutils_test.go
@@ -2,7 +2,9 @@ package sshutils_test
 
 import (
 	"errors"
+	"io/ioutil"
 	"net"
+	"path/filepath"
 
 	"testing"
 
@@ -652,7 +654,8 @@ func TestCheckKnownHosts(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.description, func(t *testing.T) {
 			sshu.SshClient = tt.args.MockSshClient
-			_, gotError := sshu.CheckKnownHosts()
+			khdir := t.TempDir()
+			_, gotError := sshu.CheckKnownHosts(filepath.Join(khdir, sshutils.AUTOMATE_KNOWN_HOSTS))
 			assert.Equal(t, gotError, tt.wantedError)
 		})
 	}
@@ -672,7 +675,8 @@ func TestCreateKnownHosts(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.description, func(t *testing.T) {
-			gotError := sshu.CreateKnownHosts()
+			khdir := t.TempDir()
+			gotError := sshu.CreateKnownHosts(filepath.Join(khdir, sshutils.AUTOMATE_KNOWN_HOSTS))
 			assert.Equal(t, gotError, tt.wantedError)
 		})
 	}
@@ -682,10 +686,12 @@ func TestAddHostKey(t *testing.T) {
 	log, _ := logger.NewLogger("text", "debug")
 	sshu := sshutils.NewSSHUtil(&sshutils.SshClient{}, log)
 	type args struct {
-		host          string
-		remote        *netAddressTest
-		pubkey        ssh.PublicKey
-		MockSshClient sshutils.ISshClient
+		host            string
+		remote          *netAddressTest
+		pubkey          ssh.PublicKey
+		MockSshClient   sshutils.ISshClient
+		existingContent string
+		expectedContent string
 	}
 	tests := []struct {
 		description string
@@ -693,19 +699,71 @@ func TestAddHostKey(t *testing.T) {
 		wantErr     error
 	}{
 		{
-			description: "If the Adding host key was successfully done",
+			description: "If the Adding host key was successfully done, empty known host file",
 			args: args{
 				host: nodeIp1,
 				remote: &netAddressTest{
 					Address: nodeIp1,
 				},
 				pubkey: &sshPublicKeyTest{
-					key:  "",
-					data: nil,
+					key:  "test-key",
+					data: []byte("test-data"),
 				},
 				MockSshClient: &sshutils.MockSshClient{
 					Normalizefunc: func(address string) string {
-						return ""
+						return nodeIp1
+					},
+					Newfunc: func(files ...string) (ssh.HostKeyCallback, error) {
+						return func(host string, remote net.Addr, pubkey ssh.PublicKey) error {
+							return nil
+						}, nil
+					},
+				},
+				existingContent: "",
+				expectedContent: nodeIp1 + " test-key dGVzdC1kYXRh",
+			},
+			wantErr: nil,
+		},
+		{
+			description: "If the Adding host key was successfully done, existing known host file",
+			args: args{
+				host: nodeIp1,
+				remote: &netAddressTest{
+					Address: nodeIp1,
+				},
+				pubkey: &sshPublicKeyTest{
+					key:  "test-key",
+					data: []byte("test-data"),
+				},
+				MockSshClient: &sshutils.MockSshClient{
+					Normalizefunc: func(address string) string {
+						return nodeIp1
+					},
+					Newfunc: func(files ...string) (ssh.HostKeyCallback, error) {
+						return func(host string, remote net.Addr, pubkey ssh.PublicKey) error {
+							return nil
+						}, nil
+					},
+				},
+				existingContent: nodeIp2 + " test-key dGVzdC1kYXRh",
+				expectedContent: nodeIp2 + " test-key dGVzdC1kYXRh\n" + nodeIp1 + " test-key dGVzdC1kYXRh",
+			},
+			wantErr: nil,
+		},
+		{
+			description: "If the Adding host key was successfully done, existing known host file with new line at end",
+			args: args{
+				host: nodeIp1,
+				remote: &netAddressTest{
+					Address: nodeIp1,
+				},
+				pubkey: &sshPublicKeyTest{
+					key:  "test-key",
+					data: []byte("test-data"),
+				},
+				MockSshClient: &sshutils.MockSshClient{
+					Normalizefunc: func(address string) string {
+						return nodeIp1
 					},
 					Newfunc: func(files ...string) (ssh.HostKeyCallback, error) {
 						return func(host string, remote net.Addr, pubkey ssh.PublicKey) error {
@@ -713,6 +771,8 @@ func TestAddHostKey(t *testing.T) {
 						}, nil
 					},
 				},
+				existingContent: nodeIp2 + " test-key dGVzdC1kYXRh\n",
+				expectedContent: nodeIp2 + " test-key dGVzdC1kYXRh\n" + nodeIp1 + " test-key dGVzdC1kYXRh",
 			},
 			wantErr: nil,
 		},
@@ -720,8 +780,18 @@ func TestAddHostKey(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.description, func(t *testing.T) {
 			sshu.SshClient = tt.args.MockSshClient
-			gotError := sshu.AddHostKey(tt.args.host, tt.args.remote, tt.args.pubkey)
+			khdir := t.TempDir()
+			f, err := ioutil.TempFile(khdir, "")
+			assert.NoError(t, err)
+			content := []byte(tt.args.existingContent)
+			_, err = f.Write(content)
+			assert.NoError(t, err)
+			gotError := sshu.AddHostKey(tt.args.host, f.Name(), tt.args.remote, tt.args.pubkey)
 			assert.Equal(t, gotError, tt.wantErr)
+			content, err = ioutil.ReadFile(f.Name())
+			assert.NoError(t, err)
+			actualContent := string(content)
+			assert.Equal(t, tt.args.expectedContent, actualContent)
 		})
 	}
 }
diff --git a/lib/stringutils/strings_test.go b/lib/stringutils/strings_test.go
index cb5687a55..6ae82ec97 100644
--- a/lib/stringutils/strings_test.go
+++ b/lib/stringutils/strings_test.go
@@ -33,3 +33,23 @@ func TestTitleReplace(t *testing.T) {
 	assert.Equal(t, "Helloworld", stringutils.TitleReplace("helloworld", "-", " "))
 	assert.Equal(t, "Hello Chef", stringutils.TitleReplace("hello chef", "_", "-"))
 }
+
+func TestGetLastLine(t *testing.T) {
+	// Test case 1: Empty input string
+	input := ""
+	expected := ""
+	actual := stringutils.GetLastLine(input)
+	assert.Equal(t, actual, expected)
+
+	// Test case 2: Single line input
+	input = "Hello, World!"
+	expected = "Hello, World!"
+	actual = stringutils.GetLastLine(input)
+	assert.Equal(t, actual, expected)
+
+	// Test case 3: Multiple lines input
+	input = "Line 1\nLine 2\nLine 3\nLast line"
+	expected = "Last line"
+	actual = stringutils.GetLastLine(input)
+	assert.Equal(t, actual, expected)
+}