Skip to content

Commit bf9cd9f

Browse files
authored
Merge pull request #24 from SPHTech-Platform/feat/add-cspm-sechub-integration
PFMENG-743 : Add CSPM sechub integration
2 parents 63075d8 + 7042884 commit bf9cd9f

File tree

9 files changed

+341
-2
lines changed

9 files changed

+341
-2
lines changed

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
| Name | Version |
1313
|------|---------|
14-
| <a name="provider_aws"></a> [aws](#provider\_aws) | 4.55.0 |
14+
| <a name="provider_aws"></a> [aws](#provider\_aws) | 4.65.0 |
1515
| <a name="provider_time"></a> [time](#provider\_time) | 0.9.1 |
1616

1717
## Modules
@@ -21,27 +21,38 @@
2121
| <a name="module_kms"></a> [kms](#module\_kms) | terraform-aws-modules/kms/aws | ~> 1.5.0 |
2222
| <a name="module_lambda"></a> [lambda](#module\_lambda) | terraform-aws-modules/lambda/aws | ~> 4.10.1 |
2323
| <a name="module_lambda_role"></a> [lambda\_role](#module\_lambda\_role) | terraform-aws-modules/iam/aws//modules/iam-assumable-role | ~> 5.9.0 |
24+
| <a name="module_sechub_integration_lambda"></a> [sechub\_integration\_lambda](#module\_sechub\_integration\_lambda) | terraform-aws-modules/lambda/aws | ~> 4.10.1 |
2425

2526
## Resources
2627

2728
| Name | Type |
2829
|------|------|
2930
| [aws_iam_policy.aqua_cspm_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
3031
| [aws_iam_policy.aqua_cspm_supplemental](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
32+
| [aws_iam_policy.aquasec_importfindings](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
3133
| [aws_iam_role.aqua_cspm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
34+
| [aws_iam_role.aqua_cspm_sechub](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
3235
| [aws_iam_role_policy_attachment.aqua_cspm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
36+
| [aws_iam_role_policy_attachment.aqua_cspm_sechub](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
3337
| [aws_lambda_invocation.external_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_invocation) | resource |
3438
| [aws_lambda_invocation.onboarding](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_invocation) | resource |
39+
| [aws_lambda_invocation.sechub_integration_external_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_invocation) | resource |
40+
| [aws_lambda_invocation.sechub_integration_onboarding](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_invocation) | resource |
3541
| [aws_secretsmanager_secret.aqua_cspm_secret](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret) | resource |
3642
| [aws_secretsmanager_secret_policy.aqua_cspm_secret](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_policy) | resource |
3743
| [aws_secretsmanager_secret_version.aqua_cspm_secret](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_version) | resource |
44+
| [time_sleep.sechub_integration_wait_10_aqua_cspm_secret](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource |
45+
| [time_sleep.sechub_integration_wait_10_seconds](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource |
46+
| [time_sleep.wait_10_aqua_cspm_secret](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource |
3847
| [time_sleep.wait_10_seconds](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource |
3948
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
4049
| [aws_iam_policy_document.aqua_cspm_control_tower_kms_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
4150
| [aws_iam_policy_document.aqua_cspm_custom_trust](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
4251
| [aws_iam_policy_document.aqua_cspm_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
4352
| [aws_iam_policy_document.aqua_cspm_secret](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
4453
| [aws_iam_policy_document.aqua_cspm_supplemental](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
54+
| [aws_iam_policy_document.aquahub_sechub_trust](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
55+
| [aws_iam_policy_document.aquasec_importfindings](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
4556
| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source |
4657
| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
4758

@@ -52,6 +63,7 @@
5263
| <a name="input_aqua_cspm_apikey"></a> [aqua\_cspm\_apikey](#input\_aqua\_cspm\_apikey) | Aqua CSPM API key: Account Management > API Keys > Generate Key | `string` | n/a | yes |
5364
| <a name="input_aqua_cspm_secretkey"></a> [aqua\_cspm\_secretkey](#input\_aqua\_cspm\_secretkey) | Aqua CSPM Secret | `string` | n/a | yes |
5465
| <a name="input_aqua_group_name"></a> [aqua\_group\_name](#input\_aqua\_group\_name) | Aqua CSPM Group Name from the Aqua Wave console | `string` | `"Default"` | no |
66+
| <a name="input_aqua_sechub_integration"></a> [aqua\_sechub\_integration](#input\_aqua\_sechub\_integration) | Enables aqua security hub integration. If enabled, findings from Aquasec will be pushed to security hub.<br>Notification type can be either "send\_all" or "send\_only\_failed". Default is "send\_all" | <pre>object({<br> enabled = bool<br> notification_type = optional(string, "send_all")<br> })</pre> | <pre>{<br> "enabled": false<br>}</pre> | no |
5567
| <a name="input_enable_kms_key_rotation"></a> [enable\_kms\_key\_rotation](#input\_enable\_kms\_key\_rotation) | Specifies whether key rotation is enabled. Defaults to true | `bool` | `true` | no |
5668
| <a name="input_kms_aliases"></a> [kms\_aliases](#input\_kms\_aliases) | A list of aliases to create. Note - due to the use of toset(), values must be static strings and not computed values | `list(string)` | <pre>[<br> "alias/AquaCSPM-Control-Tower-AquaSec"<br>]</pre> | no |
5769
| <a name="input_tags"></a> [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no |

data.tf

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,3 +353,65 @@ data "aws_iam_policy_document" "aqua_cspm_custom_trust" {
353353
aws_lambda_invocation.external_id,
354354
]
355355
}
356+
357+
data "aws_iam_policy_document" "aquahub_sechub_trust" {
358+
statement {
359+
effect = "Allow"
360+
361+
actions = [
362+
"sts:AssumeRole",
363+
]
364+
365+
principals {
366+
type = "AWS"
367+
identifiers = ["arn:aws:iam::${local.aquasec_account_id}:role/uwbwh-lambda-cloudsploit-api"]
368+
}
369+
370+
condition {
371+
test = "StringEquals"
372+
variable = "sts:ExternalId"
373+
values = [
374+
local.sechub_external_id,
375+
]
376+
}
377+
}
378+
379+
statement {
380+
effect = "Allow"
381+
382+
actions = [
383+
"sts:AssumeRole",
384+
]
385+
386+
principals {
387+
type = "AWS"
388+
identifiers = ["arn:aws:iam::${local.aquasec_account_id}:role/uwbwh-lambda-cloudsploit-executor"]
389+
}
390+
391+
condition {
392+
test = "StringEquals"
393+
variable = "sts:ExternalId"
394+
values = [
395+
local.sechub_external_id,
396+
]
397+
}
398+
}
399+
400+
depends_on = [
401+
aws_lambda_invocation.sechub_integration_external_id,
402+
]
403+
}
404+
405+
#tfsec:ignore:aws-iam-no-policy-wildcards
406+
data "aws_iam_policy_document" "aquasec_importfindings" {
407+
#checkov:skip=CKV_AWS_111
408+
#checkov:skip=CKV_AWS_108
409+
statement {
410+
actions = [
411+
"securityhub:BatchImportFindings"
412+
]
413+
resources = [
414+
"*",
415+
]
416+
}
417+
}

iam.tf

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,34 @@ resource "aws_iam_role_policy_attachment" "aqua_cspm" {
5252
policy_arn = element(local.aqua_cspm_role_policy_arns, count.index)
5353
role = aws_iam_role.aqua_cspm.name
5454
}
55+
56+
57+
resource "aws_iam_policy" "aquasec_importfindings" {
58+
count = local.enable_security_hub_integration ? 1 : 0
59+
60+
name_prefix = "${local.name_prefix}-sechub-import-findings-"
61+
policy = data.aws_iam_policy_document.aquasec_importfindings.json
62+
}
63+
64+
resource "aws_iam_role" "aqua_cspm_sechub" {
65+
count = local.enable_security_hub_integration ? 1 : 0
66+
67+
depends_on = [
68+
aws_lambda_invocation.sechub_integration_external_id,
69+
]
70+
71+
name = "${local.name_prefix}-sechub-import-role"
72+
description = "Role assumed by AquaSec for importing Sechub findings from CSPM"
73+
74+
path = "/"
75+
max_session_duration = "3600"
76+
77+
assume_role_policy = data.aws_iam_policy_document.aquahub_sechub_trust.json
78+
}
79+
80+
resource "aws_iam_role_policy_attachment" "aqua_cspm_sechub" {
81+
count = local.enable_security_hub_integration ? 1 : 0
82+
83+
policy_arn = aws_iam_policy.aquasec_importfindings[0].arn
84+
role = aws_iam_role.aqua_cspm_sechub[0].name
85+
}

locals.tf

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,19 @@ locals {
33

44
secret_name = "/aquacspm/secret-cspm"
55

6-
external_id = jsondecode(aws_lambda_invocation.external_id.result)["status"] == "FAILED" ? jsondecode(aws_lambda_invocation.external_id.result)["message"] : jsondecode(aws_lambda_invocation.external_id.result)["ExternalId"]
6+
external_id = jsondecode(aws_lambda_invocation.external_id.result)["status"] == "FAILED" ? jsondecode(aws_lambda_invocation.external_id.result)["message"] : jsondecode(aws_lambda_invocation.external_id.result)["ExternalId"]
7+
sechub_external_id = jsondecode(aws_lambda_invocation.sechub_integration_external_id[0].result)["status"] == "FAILED" ? jsondecode(aws_lambda_invocation.sechub_integration_external_id[0].result)["message"] : jsondecode(aws_lambda_invocation.sechub_integration_external_id[0].result)["ExternalId"]
8+
9+
aquasec_account_id = "057012691312"
710
# public_ip = "13.215.18.141/32"
811

12+
enable_security_hub_integration = var.aqua_sechub_integration.enabled
13+
notification_type = {
14+
send_all = "Send All Scan Reports"
15+
send_only_failed = "Send Only Failed Scan Reports"
16+
}
17+
sechub_notification_type = lookup(local.notification_type, var.aqua_sechub_integration.notification_type)
18+
919
aqua_cspm_role_policy_arns = [
1020
"arn:aws:iam::aws:policy/SecurityAudit",
1121
aws_iam_policy.aqua_cspm_supplemental.arn,

security_hub_integration.tf

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
module "sechub_integration_lambda" {
2+
source = "terraform-aws-modules/lambda/aws"
3+
version = "~> 4.10.1"
4+
5+
count = local.enable_security_hub_integration ? 1 : 0
6+
7+
function_name = "${local.name_prefix}-sechub-integration-function"
8+
description = "Retrieves the External ID from Aqua CSPM"
9+
handler = "index.lambda_handler"
10+
runtime = "python3.9"
11+
12+
memory_size = 128
13+
timeout = 30
14+
15+
create_package = false
16+
local_existing_package = "${path.module}/src/security_hub_integration_function/lambda_function.zip"
17+
18+
create_role = false
19+
lambda_role = module.lambda_role.iam_role_arn
20+
21+
tags = var.tags
22+
}
23+
24+
resource "aws_lambda_invocation" "sechub_integration_external_id" {
25+
count = local.enable_security_hub_integration ? 1 : 0
26+
27+
function_name = module.sechub_integration_lambda[0].lambda_function_name
28+
input = jsonencode({
29+
ResourceProperties = {
30+
Secret = local.secret_name
31+
},
32+
LogicalResourceId = "ExternalIDInvoke"
33+
})
34+
35+
depends_on = [
36+
module.sechub_integration_lambda,
37+
aws_secretsmanager_secret_version.aqua_cspm_secret,
38+
time_sleep.sechub_integration_wait_10_aqua_cspm_secret,
39+
]
40+
}
41+
42+
resource "aws_lambda_invocation" "sechub_integration_onboarding" {
43+
count = local.enable_security_hub_integration ? 1 : 0
44+
45+
function_name = module.sechub_integration_lambda[0].lambda_function_name
46+
input = jsonencode({
47+
ResourceProperties = {
48+
Secret = local.secret_name,
49+
RoleArn = aws_iam_role.aqua_cspm_sechub[0].arn,
50+
ExtId = local.sechub_external_id,
51+
AccId = data.aws_caller_identity.current.account_id,
52+
Region = data.aws_region.current.name,
53+
ScanNotifications = local.sechub_notification_type,
54+
},
55+
LogicalResourceId = "IntegrationInvoke"
56+
})
57+
58+
depends_on = [
59+
time_sleep.sechub_integration_wait_10_seconds,
60+
aws_lambda_invocation.sechub_integration_external_id,
61+
aws_iam_role.aqua_cspm,
62+
]
63+
}
64+
65+
resource "time_sleep" "sechub_integration_wait_10_seconds" {
66+
count = local.enable_security_hub_integration ? 1 : 0
67+
68+
depends_on = [
69+
aws_lambda_invocation.sechub_integration_external_id,
70+
]
71+
72+
create_duration = "10s"
73+
}
74+
75+
resource "time_sleep" "sechub_integration_wait_10_aqua_cspm_secret" {
76+
count = local.enable_security_hub_integration ? 1 : 0
77+
78+
depends_on = [
79+
aws_secretsmanager_secret_version.aqua_cspm_secret,
80+
]
81+
82+
create_duration = "10s"
83+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import json
2+
import boto3
3+
import os
4+
import logging
5+
import urllib3.request
6+
import hashlib
7+
import time
8+
import hmac
9+
import base64
10+
11+
LOGGER = logging.getLogger()
12+
LOGGER.setLevel(logging.INFO)
13+
sess = boto3.session.Session()
14+
sm_client = sess.client('secretsmanager')
15+
16+
def lambda_handler(event, ctxt):
17+
LOGGER.info('Lambda started :{}'.format(event))
18+
aqua_url = 'https://asia-1.api.cloudsploit.com'
19+
resData = {}
20+
rp = event['ResourceProperties']
21+
secret = rp['Secret']
22+
try:
23+
conf = get_conf(secret)
24+
aqua_api_key = conf['aqua_api_key']
25+
aqua_secret = conf['aqua_secret']
26+
except Exception as e:
27+
LOGGER.error('Error retrieving Keys: {e}')
28+
failRetKey = {'status': 'FAILED', 'message': 'Error retrieving Keys'}
29+
return failRetKey
30+
if event['LogicalResourceId'] == 'ExternalIDInvoke':
31+
LOGGER.info('ExtID creation started :{}'.format(event))
32+
try:
33+
extid = get_ext_id(aqua_url, aqua_api_key, aqua_secret)
34+
sucExtID = {'status': 'SUCCESS', 'ExternalId': extid}
35+
return sucExtID
36+
except Exception as e:
37+
LOGGER.error(e)
38+
failExtID = {'status': 'FAILED', 'message': str(e)}
39+
return failExtID
40+
elif event['LogicalResourceId'] == 'IntegrationInvoke':
41+
LOGGER.info('Started integrating :{}'.format(event))
42+
extid = rp['ExtId']
43+
region = rp['Region']
44+
role_arn = rp['RoleArn']
45+
notifications = rp['ScanNotifications']
46+
acc = rp['AccId']
47+
intData = {}
48+
try:
49+
integrate(aqua_url, aqua_api_key, aqua_secret, acc, role_arn, extid, region, notifications)
50+
LOGGER.info(f'Integration successful {acc}')
51+
sucMsg = {'status': 'SUCCESS', 'AccountId': acc, 'Integrating': True}
52+
return sucMsg
53+
except Exception as e:
54+
LOGGER.error(e)
55+
errMsg = {'status': 'FAILED', 'message': str(e)}
56+
return errMsg
57+
58+
def get_conf(secret):
59+
val = sm_client.get_secret_value(SecretId=secret)
60+
resp = val['SecretString']
61+
return json.loads(resp)
62+
63+
def get_ext_id(url, api_key, aqua_secret):
64+
path = "/v2/generatedids"
65+
method = "POST"
66+
tstmp = str(int(time.time() * 1000))
67+
enc = tstmp + method + path
68+
enc_b = bytes(enc, 'utf-8')
69+
secret = bytes(aqua_secret, 'utf-8')
70+
sig = hmac.new(secret, enc_b, hashlib.sha256).hexdigest()
71+
hdr = {
72+
"Accept": "application/json",
73+
"X-API-Key": api_key,
74+
"X-Signature": sig,
75+
"X-Timestamp": tstmp,
76+
"content-type": "application/json"
77+
}
78+
http = urllib3.PoolManager()
79+
req = http.request('POST', url + path, headers=hdr)
80+
res = json.loads(req.data.decode('utf-8'))
81+
return res['data'][0]['generated_id']
82+
83+
def integrate(url, api_key, aqua_secret, acc, role, ext_id, region, notificationType):
84+
path = "/v2/integrations"
85+
method = "POST"
86+
tstmp = str(int(time.time() * 1000))
87+
body = {
88+
"enabled": True,
89+
"name": "security_hub_" + acc,
90+
"settings": {
91+
"role_arn": role,
92+
"external_id": ext_id,
93+
"product_arn": "arn:aws:securityhub:" + region + "::product/aquasecurity/aquasecurity"
94+
},
95+
"type": "securityhub"
96+
}
97+
if notificationType == 'Send New Risks Only':
98+
body['send_new_risks'] = True
99+
else:
100+
body['send_scan_results'] = True
101+
body_str = json.dumps(body, separators=(',', ':'))
102+
LOGGER.info(body_str)
103+
enc = tstmp + method + path + body_str
104+
enc_b = bytes(enc, 'utf-8')
105+
secret = bytes(aqua_secret, 'utf-8')
106+
sig = hmac.new(secret, enc_b, hashlib.sha256).hexdigest()
107+
hdr = {
108+
"Accept": "application/json",
109+
"X-API-Key": api_key,
110+
"X-Signature": sig,
111+
"X-Timestamp": tstmp,
112+
"content-type": "application/json"
113+
}
114+
http = urllib3.PoolManager()
115+
req = http.request('POST', url + path, headers=hdr, body=body_str)
116+
res = req.data
117+
LOGGER.info(f'Registration: {res}')
Binary file not shown.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
boto3==1.26.75
2+
urllib3==1.26.14

0 commit comments

Comments
 (0)