diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts
index 966ce3098d6a7..ef9c7f49cb371 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts
@@ -60,12 +60,15 @@ import {
 } from '../../tasks/alerts';
 import {
   changeRowsPerPageTo300,
+  duplicateFirstRule,
+  duplicateRuleFromMenu,
   filterByCustomRules,
   goToCreateNewRule,
   goToRuleDetails,
   waitForRulesTableToBeLoaded,
 } from '../../tasks/alerts_detection_rules';
-import { cleanKibana } from '../../tasks/common';
+import { createCustomIndicatorRule } from '../../tasks/api_calls/rules';
+import { cleanKibana, reload } from '../../tasks/common';
 import {
   createAndActivateRule,
   fillAboutRuleAndContinue,
@@ -92,8 +95,10 @@ import {
   waitForAlertsToPopulate,
   waitForTheRuleToBeExecuted,
 } from '../../tasks/create_new_rule';
+import { waitForKibana } from '../../tasks/edit_rule';
 import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver';
 import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
+import { goBackToAllRulesTable } from '../../tasks/rule_details';
 
 import { DETECTIONS_URL, RULE_CREATION } from '../../urls/navigation';
 
@@ -465,5 +470,30 @@ describe('indicator match', () => {
         cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', newThreatIndicatorRule.riskScore);
       });
     });
+
+    describe('Duplicates the indicator rule', () => {
+      beforeEach(() => {
+        cleanKibana();
+        loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
+        goToManageAlertsDetectionRules();
+        createCustomIndicatorRule(newThreatIndicatorRule);
+        reload();
+      });
+
+      it('Allows the rule to be duplicated from the table', () => {
+        waitForKibana();
+        duplicateFirstRule();
+        cy.contains(RULE_NAME, `${newThreatIndicatorRule.name} [Duplicate]`);
+      });
+
+      it('Allows the rule to be duplicated from the edit screen', () => {
+        waitForKibana();
+        goToRuleDetails();
+        duplicateRuleFromMenu();
+        goBackToAllRulesTable();
+        reload();
+        cy.contains(RULE_NAME, `${newThreatIndicatorRule.name} [Duplicate]`);
+      });
+    });
   });
 });
diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts
index 68baad7d3d259..30365c9bd4c70 100644
--- a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts
@@ -17,6 +17,12 @@ export const DELETE_RULE_ACTION_BTN = '[data-test-subj="deleteRuleAction"]';
 
 export const EDIT_RULE_ACTION_BTN = '[data-test-subj="editRuleAction"]';
 
+export const DUPLICATE_RULE_ACTION_BTN = '[data-test-subj="duplicateRuleAction"]';
+
+export const DUPLICATE_RULE_MENU_PANEL_BTN = '[data-test-subj="rules-details-duplicate-rule"]';
+
+export const REFRESH_BTN = '[data-test-subj="refreshRulesAction"] button';
+
 export const DELETE_RULE_BULK_BTN = '[data-test-subj="deleteRuleBulk"]';
 
 export const ELASTIC_RULES_BTN = '[data-test-subj="showElasticRulesFilterButton"]';
diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts
index 3553889449e6d..529ef4afdfa63 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts
@@ -31,6 +31,8 @@ import {
   RULE_AUTO_REFRESH_IDLE_MODAL_CONTINUE,
   rowsPerPageSelector,
   pageSelector,
+  DUPLICATE_RULE_ACTION_BTN,
+  DUPLICATE_RULE_MENU_PANEL_BTN,
 } from '../screens/alerts_detection_rules';
 import { ALL_ACTIONS, DELETE_RULE } from '../screens/rule_details';
 
@@ -45,6 +47,33 @@ export const editFirstRule = () => {
   cy.get(EDIT_RULE_ACTION_BTN).click();
 };
 
+export const duplicateFirstRule = () => {
+  cy.get(COLLAPSED_ACTION_BTN).should('be.visible');
+  cy.get(COLLAPSED_ACTION_BTN).first().click({ force: true });
+  cy.get(DUPLICATE_RULE_ACTION_BTN).should('be.visible');
+  cy.get(DUPLICATE_RULE_ACTION_BTN).click();
+};
+
+/**
+ * Duplicates the rule from the menu and does additional
+ * pipes and checking that the elements are present on the
+ * page as well as removed when doing the clicks to help reduce
+ * flake.
+ */
+export const duplicateRuleFromMenu = () => {
+  cy.get(ALL_ACTIONS).should('be.visible');
+  cy.root()
+    .pipe(($el) => {
+      $el.find(ALL_ACTIONS).trigger('click');
+      return $el.find(DUPLICATE_RULE_MENU_PANEL_BTN);
+    })
+    .should(($el) => expect($el).to.be.visible);
+  // Because of a fade effect and fast clicking this can produce more than one click
+  cy.get(DUPLICATE_RULE_MENU_PANEL_BTN)
+    .pipe(($el) => $el.trigger('click'))
+    .should('not.be.visible');
+};
+
 export const deleteFirstRule = () => {
   cy.get(COLLAPSED_ACTION_BTN).first().click({ force: true });
   cy.get(DELETE_RULE_ACTION_BTN).click();
diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts
index ab6063f5809c4..99f5bd9c20230 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts
@@ -5,7 +5,7 @@
  * 2.0.
  */
 
-import { CustomRule } from '../../objects/rule';
+import { CustomRule, ThreatIndicatorRule } from '../../objects/rule';
 
 export const createCustomRule = (rule: CustomRule, ruleId = 'rule_testing') =>
   cy.request({
@@ -29,6 +29,44 @@ export const createCustomRule = (rule: CustomRule, ruleId = 'rule_testing') =>
     failOnStatusCode: false,
   });
 
+export const createCustomIndicatorRule = (rule: ThreatIndicatorRule, ruleId = 'rule_testing') =>
+  cy.request({
+    method: 'POST',
+    url: 'api/detection_engine/rules',
+    body: {
+      rule_id: ruleId,
+      risk_score: parseInt(rule.riskScore, 10),
+      description: rule.description,
+      interval: '10s',
+      name: rule.name,
+      severity: rule.severity.toLocaleLowerCase(),
+      type: 'threat_match',
+      threat_mapping: [
+        {
+          entries: [
+            {
+              field: rule.indicatorMapping,
+              type: 'mapping',
+              value: rule.indicatorMapping,
+            },
+          ],
+        },
+      ],
+      threat_query: '*:*',
+      threat_language: 'kuery',
+      threat_filters: [],
+      threat_index: ['mock*'],
+      threat_indicator_path: '',
+      from: 'now-17520h',
+      index: ['exceptions-*'],
+      query: rule.customQuery || '*:*',
+      language: 'kuery',
+      enabled: false,
+    },
+    headers: { 'kbn-xsrf': 'cypress-creds' },
+    failOnStatusCode: false,
+  });
+
 export const createCustomRuleActivated = (rule: CustomRule, ruleId = '1') =>
   cy.request({
     method: 'POST',
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx
index 3b1f9e620127d..6cc75a3fda03c 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx
@@ -8,6 +8,7 @@
 import * as H from 'history';
 import React, { Dispatch } from 'react';
 
+import { CreateRulesSchema } from '../../../../../../common/detection_engine/schemas/request';
 import {
   deleteRules,
   duplicateRules,
@@ -28,6 +29,7 @@ import { track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../../../common/lib/t
 
 import * as i18n from '../translations';
 import { bucketRulesResponse } from './helpers';
+import { transformOutput } from '../../../../containers/detection_engine/rules/transforms';
 
 export const editRuleAction = (rule: Rule, history: H.History) => {
   history.push(getEditRuleUrl(rule.id));
@@ -41,7 +43,11 @@ export const duplicateRulesAction = async (
 ) => {
   try {
     dispatch({ type: 'loadingRuleIds', ids: ruleIds, actionType: 'duplicate' });
-    const response = await duplicateRules({ rules });
+    const response = await duplicateRules({
+      // We cast this back and forth here as the front end types are not really the right io-ts ones
+      // and the two types conflict with each other.
+      rules: rules.map((rule) => transformOutput(rule as CreateRulesSchema) as Rule),
+    });
     const { errors } = bucketRulesResponse(response);
     if (errors.length > 0) {
       displayErrorToast(
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx
index d2488bd3d043c..d2eadef48d9c7 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx
@@ -67,6 +67,7 @@ export const getActions = (
     enabled: (rowItem: Rule) => canEditRuleWithActions(rowItem, actionsPrivileges),
   },
   {
+    'data-test-subj': 'duplicateRuleAction',
     description: i18n.DUPLICATE_RULE,
     icon: 'copy',
     name: !actionsPrivileges ? (