Skip to content

Commit f8f5d92

Browse files
authored
Merge pull request #113 from cloudgraphdev/feature/CG-1293-GCP-CIS-130-116
feat(CG-1293): add GCP CIS 1.30 1.16 rule
2 parents 3040547 + 3e992cf commit f8f5d92

4 files changed

Lines changed: 208 additions & 0 deletions

File tree

src/gcp/cis-1.3.0/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ Policy Pack based on the GCP Foundations 1.3.0 benchmark provided by the [Center
7070
| GCP CIS 1.13 | Ensure API keys are restricted to use by only specified Hosts and Apps |
7171
| GCP CIS 1.14 | Ensure API keys are restricted to only APIs that application needs access |
7272
| GCP CIS 1.15 | Ensure API keys are rotated every 90 days |
73+
| GCP CIS 1.16 | Ensure Essential Contacts is Configured for Organization |
7374
| GCP CIS 2.1 | Ensure that Cloud Audit Logging is configured properly across all services and all users from a project |
7475
| GCP CIS 2.2 | Ensure that sinks are configured for all log entries |
7576
| GCP CIS 2.3 | Ensure that retention policies on log buckets are configured using Bucket Lock |
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
2+
/* eslint-disable @typescript-eslint/no-explicit-any */
3+
4+
export default {
5+
id: 'gcp-cis-1.3.0-1.16',
6+
title: 'GCP CIS 1.16 Ensure Essential Contacts is Configured for Organization',
7+
description:
8+
'It is recommended that Essential Contacts is configured to designate email addresses for Google Cloud services to notify of important technical or security information.',
9+
audit: `**From Console:**
10+
11+
1. Go to Essential Contacts by visiting https://console.cloud.google.com/iam-admin/essential-contacts
12+
2. Make sure the organization appears in the resource selector at the top of the page. The resource selector tells you what project, folder, or organization you are currently managing contacts for.
13+
3. Ensure that appropriate email addresses are configured for each of the following notification categories:
14+
15+
- Legal
16+
- Security
17+
- Suspension
18+
- Technical
19+
- Technical Incidents
20+
21+
Alternatively, appropriate email addresses can be configured for the All notification category to receive all possible important notifications.
22+
23+
**From Command Line:**
24+
25+
1. To list all configured organization Essential Contacts run a command:
26+
27+
gcloud essential-contacts list --organization=<ORGANIZATION_ID>
28+
29+
2. Ensure at least one appropriate email address is configured for each of the following notification categories:
30+
31+
- LEGAL
32+
- SECURITY
33+
- SUSPENSION
34+
- TECHNICAL
35+
- TECHNICAL_INCIDENTS
36+
37+
Alternatively, appropriate email addresses can be configured for the ALL notification category to receive all possible important notifications.
38+
`,
39+
rationale:
40+
'Many Google Cloud services, such as Cloud Billing, send out notifications to share important information with Google Cloud users. By default, these notifications are sent to members with certain Identity and Access Management (IAM) roles. With Essential Contacts, you can customize who receives notifications by providing your own list of contacts.',
41+
remediation: `**From Console:**
42+
1. Go to Essential Contacts by visiting https://console.cloud.google.com/iam-admin/essential-contacts
43+
2. Make sure the organization appears in the resource selector at the top of the page. The resource selector tells you what project, folder, or organization you are currently managing contacts for.
44+
3. Click +Add contact
45+
4. In the Email and Confirm Email fields, enter the email address of the contact.
46+
5. From the Notification categories drop-down menu, select the notification categories that you want the contact to receive communications for.
47+
6. Click Save
48+
49+
**From Command Line:**
50+
51+
1. To add an organization Essential Contacts run a command:
52+
53+
gcloud essential-contacts create --email="<EMAIL>" \
54+
--notification-categories="<NOTIFICATION_CATEGORIES>" \
55+
--organization=<ORGANIZATION_ID>
56+
57+
**Default Value:**
58+
59+
By default, there are no Essential Contacts configured.
60+
61+
In the absence of an Essential Contact, the following IAM roles are used to identify users to
62+
notify for the following categories:
63+
64+
• Legal: roles/billing.admin
65+
• Security: roles/resourcemanager.organizationAdmin
66+
• Suspension: roles/owner
67+
• Technical: roles/owner
68+
• Technical Incidents: roles/owner
69+
`,
70+
references: [
71+
'https://cloud.google.com/resource-manager/docs/managing-notification-contacts',
72+
],
73+
gql: `{
74+
querygcpEssentialContact {
75+
id
76+
__typename
77+
notificationCategorySubscriptions
78+
email
79+
}
80+
}`,
81+
resource: 'querygcpProject[*]',
82+
severity: 'unknown',
83+
84+
check: ({ resource }: any): boolean => {
85+
const { essentialContacts } = resource
86+
87+
if (!essentialContacts || essentialContacts.length === 0) {
88+
return false
89+
}
90+
91+
const requiredCategories = ['LEGAL', 'SECURITY', 'SUSPENSION', 'TECHNICAL', 'TECHNICAL_INCIDENTS']
92+
const categoryAll = 'ALL'
93+
94+
const subscribedCategories = essentialContacts
95+
.flatMap(({notificationCategorySubscriptions}: any) => notificationCategorySubscriptions)
96+
97+
const result = requiredCategories.every((category: any) => subscribedCategories.includes(category))
98+
99+
return result || subscribedCategories.includes(categoryAll)
100+
}
101+
}

src/gcp/cis-1.3.0/rules/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Gcp_CIS_130_112 from './gcp-cis-1.3.0-1.12'
1313
import Gcp_CIS_130_113 from './gcp-cis-1.3.0-1.13'
1414
import Gcp_CIS_130_114 from './gcp-cis-1.3.0-1.14'
1515
import Gcp_CIS_130_115 from './gcp-cis-1.3.0-1.15'
16+
import Gcp_CIS_130_116 from './gcp-cis-1.3.0-1.16'
1617
import Gcp_CIS_130_21 from './gcp-cis-1.3.0-2.1'
1718
import Gcp_CIS_130_22 from './gcp-cis-1.3.0-2.2'
1819
import Gcp_CIS_130_23 from './gcp-cis-1.3.0-2.3'
@@ -89,6 +90,7 @@ export default [
8990
Gcp_CIS_130_113,
9091
Gcp_CIS_130_114,
9192
Gcp_CIS_130_115,
93+
Gcp_CIS_130_116,
9294
Gcp_CIS_130_21,
9395
Gcp_CIS_130_22,
9496
Gcp_CIS_130_23,

src/gcp/cis-1.3.0/tests/gcp-cis-1.3.0-1.x.test.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import Gcp_CIS_130_111 from '../rules/gcp-cis-1.3.0-1.11'
1515
import Gcp_CIS_130_112 from '../rules/gcp-cis-1.3.0-1.12'
1616
import Gcp_CIS_130_113 from '../rules/gcp-cis-1.3.0-1.13'
1717
import Gcp_CIS_130_115 from '../rules/gcp-cis-1.3.0-1.15'
18+
import Gcp_CIS_130_116 from '../rules/gcp-cis-1.3.0-1.16'
1819
import { initRuleEngine } from '../../../utils/test'
1920

2021
export interface MetricDescriptor {
@@ -31,6 +32,11 @@ export interface IamPolicy {
3132
export interface ApiKey {
3233
id: string
3334
}
35+
export interface EssentialContact {
36+
id: string
37+
notificationCategorySubscriptions: string[]
38+
email: string
39+
}
3440
export interface Folder {
3541
iamPolicies: IamPolicy[]
3642
}
@@ -81,6 +87,7 @@ export interface QuerygcpProject {
8187
id: string
8288
iamPolicies?: IamPolicy[]
8389
apiKeys?: ApiKey[]
90+
essentialContacts?: EssentialContact[]
8491
}
8592

8693
export interface QuerygcpServiceAccount {
@@ -103,6 +110,7 @@ export interface QuerygcpIamPolicy {
103110
id: string
104111
bindings: Bindings[]
105112
}
113+
106114
export interface CIS1xQueryResponse {
107115
querygcpOrganization?: QuerygcpOrganization[]
108116
querygcpProject?: QuerygcpProject[]
@@ -881,4 +889,100 @@ describe('CIS Google Cloud Platform Foundations: 1.3.0', () => {
881889
await testRule(data, Result.FAIL)
882890
})
883891
})
892+
893+
describe('GCP CIS 1.16 Ensure Essential Contacts is Configured for Organizations', () => {
894+
const testRule = async (
895+
data: CIS1xQueryResponse,
896+
expectedResult: Result
897+
): Promise<void> => {
898+
// Act
899+
const [processedRule] = await rulesEngine.processRule(
900+
Gcp_CIS_130_116 as Rule,
901+
{ ...data }
902+
)
903+
904+
// Asserts
905+
expect(processedRule.result).toBe(expectedResult)
906+
}
907+
908+
test('No Security Issue when Emails subscribed all required categories', async () => {
909+
const data: CIS1xQueryResponse = {
910+
querygcpProject: [
911+
{
912+
id: cuid(),
913+
essentialContacts: [
914+
{
915+
id: cuid(),
916+
notificationCategorySubscriptions: ['LEGAL', 'TECHNICAL', 'SUSPENSION', 'SECURITY'],
917+
email: 'a@gmail.com'
918+
},
919+
{
920+
id: cuid(),
921+
notificationCategorySubscriptions: ['TECHNICAL_INCIDENTS', 'SECURITY', 'BILLING'],
922+
email: 'b@gmail.com'
923+
},
924+
],
925+
},
926+
],
927+
}
928+
await testRule(data, Result.PASS)
929+
})
930+
test('Security Issue when Emails missed one required subscription category', async () => {
931+
const data: CIS1xQueryResponse = {
932+
querygcpProject: [{
933+
id: cuid(),
934+
essentialContacts: [
935+
{
936+
id: cuid(),
937+
notificationCategorySubscriptions: ['LEGAL', 'SUSPENSION', 'SECURITY'],
938+
email: 'a@gmail.com'
939+
},
940+
{
941+
id: cuid(),
942+
notificationCategorySubscriptions: ['TECHNICAL_INCIDENTS', 'SECURITY', 'BILLING'],
943+
email: 'b@gmail.com'
944+
},
945+
],
946+
}]
947+
}
948+
await testRule(data, Result.FAIL)
949+
})
950+
test('No Security Issue when an email subscribed ALL category', async () => {
951+
const data: CIS1xQueryResponse = {
952+
querygcpProject: [{
953+
id: cuid(),
954+
essentialContacts: [
955+
{
956+
id: cuid(),
957+
notificationCategorySubscriptions: ['LEGAL', 'TECHNICAL', 'SUSPENSION', 'SECURITY'],
958+
email: 'a@gmail.com'
959+
},
960+
{
961+
id: cuid(),
962+
notificationCategorySubscriptions: ['ALL'],
963+
email: 'b@gmail.com'
964+
},
965+
],
966+
}]
967+
}
968+
await testRule(data, Result.PASS)
969+
})
970+
test('Security Issue when Essential contact API is not enabled', async () => {
971+
const data: CIS1xQueryResponse = {
972+
querygcpProject: [{
973+
id: cuid(),
974+
}]
975+
}
976+
await testRule(data, Result.FAIL)
977+
})
978+
test('Security Issue when Essential contact is either not configured', async () => {
979+
const data: CIS1xQueryResponse = {
980+
querygcpProject: [{
981+
id: cuid(),
982+
essentialContacts: [],
983+
}]
984+
}
985+
await testRule(data, Result.FAIL)
986+
})
987+
})
884988
})

0 commit comments

Comments
 (0)