Skip to content

Commit 375a96a

Browse files
Check for federated identity providers (#83)
Update existing lambdas to check for federated identity providers and adjust the compliance annotation accordingly.
1 parent e07b0f0 commit 375a96a

File tree

9 files changed

+129
-96
lines changed

9 files changed

+129
-96
lines changed

arch/lza_extensions/customizations/GCGuardrailsRoles.yaml

+3-9
Original file line numberDiff line numberDiff line change
@@ -247,15 +247,9 @@ Resources:
247247
Statement:
248248
- Sid: AllowIAMQueries
249249
Action:
250-
- "iam:GenerateCredentialReport"
251-
- "iam:GetAccountPasswordPolicy"
252-
- "iam:GetCredentialReport"
253-
- "iam:GetLoginProfile"
254-
- "iam:GetRole"
255-
- "iam:GetUser"
256-
- "iam:ListAttachedRolePolicies"
257-
- "iam:ListMFADevices"
258-
- "iam:ListUsers"
250+
- "iam:Generate*"
251+
- "iam:Get*"
252+
- "iam:List*"
259253
- "iam:Simulate*"
260254
Resource: "*"
261255
Effect: Allow

arch/templates/AuditAccountAuditManager.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ Resources:
6969
Effect: Allow
7070
- Sid: AllowIAM
7171
Action:
72-
- "iam:ListRoles"
72+
- "iam:List*"
7373
Resource: "*"
7474
Effect: Allow
7575
PolicyName: gc_setup_amresources_lambda_execution_role_policy

arch/templates/AuditAccountPreRequisitesPart1.yaml

+3-9
Original file line numberDiff line numberDiff line change
@@ -201,15 +201,9 @@ Resources:
201201
Effect: Allow
202202
- Sid: AllowIAMQueries
203203
Action:
204-
- "iam:GenerateCredentialReport"
205-
- "iam:GetAccountPasswordPolicy"
206-
- "iam:GetCredentialReport"
207-
- "iam:GetLoginProfile"
208-
- "iam:GetRole"
209-
- "iam:GetUser"
210-
- "iam:ListAttachedRolePolicies"
211-
- "iam:ListMFADevices"
212-
- "iam:ListUsers"
204+
- "iam:Generate*"
205+
- "iam:Get*"
206+
- "iam:List*"
213207
- "iam:Simulate*"
214208
Resource: "*"
215209
Effect: Allow

arch/templates/AuditAccountPreRequisitesPartN.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ Resources:
9595
Properties:
9696
ServiceToken: !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${OrganizationName}aws_lambda_permissions_setup"
9797
UpdateVariable: QVdTb21lCg8
98-
TriggerProperty: 31
98+
TriggerProperty: 32
9999
DependsOn:
100100
- LambdaPermissionsLambda
101101

arch/templates/main.yaml

+5-11
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ Resources:
212212
Type: Custom::InvokeCustomLambda
213213
Condition: DeployRoles
214214
Properties:
215-
Version: 31
215+
Version: 32
216216
ServiceToken: !GetAtt CreateRoleLambda.Arn
217217
TrustPrincipal: !Sub arn:${AWS::Partition}:iam::${AuditAccountID}:root
218218
SwitchRole: !Ref AcceleratorRole
@@ -337,7 +337,7 @@ Resources:
337337
Type: Custom::InvokeCustomLambda
338338
Condition: DeployRoles
339339
Properties:
340-
Version: 31
340+
Version: 32
341341
ServiceToken: !GetAtt CreateRoleLambda.Arn
342342
TrustPrincipal: !Sub arn:${AWS::Partition}:iam::${AuditAccountID}:root
343343
SwitchRole: !Ref AcceleratorRole
@@ -1175,15 +1175,9 @@ Resources:
11751175
Statement:
11761176
- Sid: AllowIAMQueries
11771177
Action:
1178-
- "iam:GenerateCredentialReport"
1179-
- "iam:GetAccountPasswordPolicy"
1180-
- "iam:GetCredentialReport"
1181-
- "iam:GetLoginProfile"
1182-
- "iam:GetRole"
1183-
- "iam:GetUser"
1184-
- "iam:ListAttachedRolePolicies"
1185-
- "iam:ListMFADevices"
1186-
- "iam:ListUsers"
1178+
- "iam:Generate*"
1179+
- "iam:Get*"
1180+
- "iam:List*"
11871181
- "iam:Simulate*"
11881182
Resource: "*"
11891183
Effect: Allow

src/lambda/gc01_check_federated_users_mfa/app.py

+36-26
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
""" GC01 - Check Federated Users MFA
22
https://canada-ca.github.io/cloud-guardrails/EN/01_Protect-Root-Account.html
33
"""
4+
45
import json
56
import logging
67

@@ -43,10 +44,7 @@ def get_assume_role_credentials(role_arn):
4344
"""
4445
sts_client = boto3.client("sts")
4546
try:
46-
assume_role_response = sts_client.assume_role(
47-
RoleArn=role_arn,
48-
RoleSessionName="configLambdaExecution"
49-
)
47+
assume_role_response = sts_client.assume_role(RoleArn=role_arn, RoleSessionName="configLambdaExecution")
5048
return assume_role_response["Credentials"]
5149
except botocore.exceptions.ClientError as ex:
5250
# Scrub error message for any internal account info leaks
@@ -73,6 +71,7 @@ def evaluate_parameters(rule_parameters):
7371
"""
7472
return rule_parameters
7573

74+
7675
# This generate an evaluation for config
7776
def build_evaluation(
7877
resource_id,
@@ -96,12 +95,28 @@ def build_evaluation(
9695
eval_cc["ComplianceResourceType"] = resource_type
9796
eval_cc["ComplianceResourceId"] = resource_id
9897
eval_cc["ComplianceType"] = compliance_type
99-
eval_cc["OrderingTimestamp"] = str(
100-
json.loads(event["invokingEvent"])["notificationCreationTime"]
101-
)
98+
eval_cc["OrderingTimestamp"] = str(json.loads(event["invokingEvent"])["notificationCreationTime"])
10299
return eval_cc
103100

104101

102+
def account_has_federated_users(iam_client) -> bool:
103+
response = iam_client.list_open_id_connect_providers()
104+
if not response:
105+
raise Exception("Request to list OIDC providers returned an invalid response")
106+
providers = response.get("OpenIDConnectProviderList", [])
107+
if providers:
108+
return True
109+
110+
response = iam_client.list_saml_providers()
111+
if not response:
112+
raise Exception("Request to list SAML providers returned an invalid response")
113+
providers = response.get("SAMLProviderList", [])
114+
if providers:
115+
return True
116+
117+
return False
118+
119+
105120
def lambda_handler(event, context):
106121
"""This function is the main entry point for Lambda.
107122
Keyword arguments:
@@ -138,22 +153,17 @@ def lambda_handler(event, context):
138153
else:
139154
AUDIT_ACCOUNT_ID = ""
140155

141-
AWS_CONFIG_CLIENT = get_client("config", event)
142-
143-
# is this a scheduled invokation?
144-
if is_scheduled_notification(invoking_event["messageType"]):
145-
# yes, proceed
146-
# Update AWS Config with the evaluation result
147-
evaluations = [
148-
build_evaluation(
149-
AWS_ACCOUNT_ID,
150-
"COMPLIANT",
151-
event,
152-
DEFAULT_RESOURCE_TYPE,
153-
"Dependent on the compliance of Federated IdP"
154-
)
155-
]
156-
AWS_CONFIG_CLIENT.put_evaluations(
157-
Evaluations=evaluations,
158-
ResultToken=event["resultToken"]
159-
)
156+
if not is_scheduled_notification(invoking_event["messageType"]):
157+
return
158+
159+
aws_config_client = get_client("config", event)
160+
aws_iam_client = get_client("iam", event)
161+
162+
annotation = (
163+
"Dependent on the compliance of the Federated IdP."
164+
if account_has_federated_users(aws_iam_client)
165+
else "No federated IdP found."
166+
)
167+
168+
evaluations = [build_evaluation(AWS_ACCOUNT_ID, "COMPLIANT", event, DEFAULT_RESOURCE_TYPE, annotation)]
169+
aws_config_client.put_evaluations(Evaluations=evaluations, ResultToken=event["resultToken"])

src/lambda/gc01_check_mfa_digital_policy/app.py

+36-27
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
""" GC01 - Check Federated Users MFA
22
https://canada-ca.github.io/cloud-guardrails/EN/01_Protect-Root-Account.html
33
"""
4+
45
import json
56
import logging
67

@@ -43,10 +44,7 @@ def get_assume_role_credentials(role_arn):
4344
"""
4445
sts_client = boto3.client("sts")
4546
try:
46-
assume_role_response = sts_client.assume_role(
47-
RoleArn=role_arn,
48-
RoleSessionName="configLambdaExecution"
49-
)
47+
assume_role_response = sts_client.assume_role(RoleArn=role_arn, RoleSessionName="configLambdaExecution")
5048
return assume_role_response["Credentials"]
5149
except botocore.exceptions.ClientError as ex:
5250
# Scrub error message for any internal account info leaks
@@ -73,6 +71,7 @@ def evaluate_parameters(rule_parameters):
7371
"""
7472
return rule_parameters
7573

74+
7675
# This generate an evaluation for config
7776
def build_evaluation(
7877
resource_id,
@@ -96,12 +95,28 @@ def build_evaluation(
9695
eval_cc["ComplianceResourceType"] = resource_type
9796
eval_cc["ComplianceResourceId"] = resource_id
9897
eval_cc["ComplianceType"] = compliance_type
99-
eval_cc["OrderingTimestamp"] = str(
100-
json.loads(event["invokingEvent"])["notificationCreationTime"]
101-
)
98+
eval_cc["OrderingTimestamp"] = str(json.loads(event["invokingEvent"])["notificationCreationTime"])
10299
return eval_cc
103100

104101

102+
def account_has_federated_users(iam_client) -> bool:
103+
response = iam_client.list_open_id_connect_providers()
104+
if not response:
105+
raise Exception("Request to list OIDC providers returned an invalid response")
106+
providers = response.get("OpenIDConnectProviderList", [])
107+
if providers:
108+
return True
109+
110+
response = iam_client.list_saml_providers()
111+
if not response:
112+
raise Exception("Request to list SAML providers returned an invalid response")
113+
providers = response.get("SAMLProviderList", [])
114+
if providers:
115+
return True
116+
117+
return False
118+
119+
105120
def lambda_handler(event, context):
106121
"""This function is the main entry point for Lambda.
107122
Keyword arguments:
@@ -110,7 +125,6 @@ def lambda_handler(event, context):
110125
"""
111126
logger.debug("Received event: %s", event)
112127

113-
global AWS_CONFIG_CLIENT
114128
global AWS_ACCOUNT_ID
115129
global EXECUTION_ROLE_NAME
116130
global AUDIT_ACCOUNT_ID
@@ -138,22 +152,17 @@ def lambda_handler(event, context):
138152
else:
139153
AUDIT_ACCOUNT_ID = ""
140154

141-
AWS_CONFIG_CLIENT = get_client("config", event)
142-
143-
# is this a scheduled invokation?
144-
if is_scheduled_notification(invoking_event["messageType"]):
145-
# yes, proceed
146-
# Update AWS Config with the evaluation result
147-
evaluations = [
148-
build_evaluation(
149-
AWS_ACCOUNT_ID,
150-
"COMPLIANT",
151-
event,
152-
DEFAULT_RESOURCE_TYPE,
153-
"Dependent on the compliance of Federated IdP"
154-
)
155-
]
156-
AWS_CONFIG_CLIENT.put_evaluations(
157-
Evaluations=evaluations,
158-
ResultToken=event["resultToken"]
159-
)
155+
if not is_scheduled_notification(invoking_event["messageType"]):
156+
return
157+
158+
aws_config_client = get_client("config", event)
159+
aws_iam_client = get_client("iam", event)
160+
161+
annotation = (
162+
"Dependent on the compliance of the Federated IdP."
163+
if account_has_federated_users(aws_iam_client)
164+
else "No federated IdP found."
165+
)
166+
167+
evaluations = [build_evaluation(AWS_ACCOUNT_ID, "COMPLIANT", event, DEFAULT_RESOURCE_TYPE, annotation)]
168+
aws_config_client.put_evaluations(Evaluations=evaluations, ResultToken=event["resultToken"])

src/lambda/gc03_check_endpoint_access_config/app.py

+18-3
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,22 @@ def build_evaluation(resource_id, compliance_type, event, resource_type=DEFAULT_
9494
return eval_cc
9595

9696

97-
def account_has_federated_entra_id_users() -> bool:
98-
return True
97+
def account_has_federated_users(iam_client) -> bool:
98+
response = iam_client.list_open_id_connect_providers()
99+
if not response:
100+
raise Exception("Request to list OIDC providers returned an invalid response")
101+
providers = response.get("OpenIDConnectProviderList", [])
102+
if providers:
103+
return True
104+
105+
response = iam_client.list_saml_providers()
106+
if not response:
107+
raise Exception("Request to list SAML providers returned an invalid response")
108+
providers = response.get("SAMLProviderList", [])
109+
if providers:
110+
return True
111+
112+
return False
99113

100114

101115
def lambda_handler(event, context):
@@ -126,9 +140,10 @@ def lambda_handler(event, context):
126140
# parse parameters
127141
AWS_ACCOUNT_ID = event["accountId"]
128142
AWS_CONFIG_CLIENT = get_client("config", event)
143+
aws_iam_client = get_client("iam", event)
129144

130145
annotation = "Configuration for devices and policies are implemented."
131-
if account_has_federated_entra_id_users():
146+
if account_has_federated_users(aws_iam_client):
132147
annotation = f"{annotation} Dependent on the compliance of the Federated IdP."
133148
evaluations.append(build_evaluation(AWS_ACCOUNT_ID, "COMPLIANT", event, DEFAULT_RESOURCE_TYPE, annotation))
134149
logger.info(annotation)

src/lambda/gc03_check_trusted_devices_admin_access/app.py

+26-9
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,22 @@ def ip_is_within_ranges(ip_addr: str, ip_cidr_ranges: list[str]) -> bool:
251251
return False
252252

253253

254-
def account_has_federated_entra_id_users() -> bool:
255-
return True
254+
def account_has_federated_users(iam_client) -> bool:
255+
response = iam_client.list_open_id_connect_providers()
256+
if not response:
257+
raise Exception("Request to list OIDC providers returned an invalid response")
258+
providers = response.get("OpenIDConnectProviderList", [])
259+
if providers:
260+
return True
261+
262+
response = iam_client.list_saml_providers()
263+
if not response:
264+
raise Exception("Request to list SAML providers returned an invalid response")
265+
providers = response.get("SAMLProviderList", [])
266+
if providers:
267+
return True
268+
269+
return False
256270

257271

258272
def lambda_handler(event, context):
@@ -292,10 +306,13 @@ def lambda_handler(event, context):
292306
AWS_S3_CLIENT = boto3.client("s3")
293307
AWS_CLOUDTRAIL_CLIENT = get_client("cloudtrail", event)
294308
AWS_ORGANIZATIONS_CLIENT = get_client("organizations", event)
309+
aws_iam_client = get_client("iam", event)
295310

296311
if AWS_ACCOUNT_ID != get_organizations_mgmt_account_id():
297312
# We're not in the Management Account
298-
logger.info("Cloud Trail events not checked in account %s as this is not the Management Account", AWS_ACCOUNT_ID)
313+
logger.info(
314+
"Cloud Trail events not checked in account %s as this is not the Management Account", AWS_ACCOUNT_ID
315+
)
299316
return
300317

301318
file_param_name = "s3ObjectPath"
@@ -328,18 +345,18 @@ def lambda_handler(event, context):
328345
ct_event = json.loads(lookup_event.get("CloudTrailEvent", "{}"))
329346

330347
if not ip_is_within_ranges(ct_event["sourceIPAddress"], vpn_ip_ranges):
331-
annotation = f"Cloud Trail Event '{ct_event.get("EventId", "")}' has a source IP address OUTSIDE of the allowed ranges."
348+
annotation = f"Cloud Trail Event '{ct_event.get("eventID", "")}' has a source IP address OUTSIDE of the allowed ranges."
332349
else:
333350
num_compliant_rules = num_compliant_rules + 1
334-
annotation = f"Cloud Trail Event '{ct_event.get("EventId", "")}' has a source IP address inside of the allowed ranges."
335-
if account_has_federated_entra_id_users():
351+
annotation = f"Cloud Trail Event '{ct_event.get("eventID", "")}' has a source IP address inside of the allowed ranges."
352+
if account_has_federated_users(aws_iam_client):
336353
annotation = f"{annotation} Dependent on the compliance of the Federated IdP."
337354
logger.info(annotation)
338355

339356
if len(cloud_trail_events) == num_compliant_rules:
340-
annotation = "All Cloud Trail Events are within the allowed source IP address ranges or are dependant on the federated identity provider."
341-
if account_has_federated_entra_id_users():
342-
annotation = f"{annotation} Dependent on the compliance of the Federated IdP."
357+
annotation = "All Cloud Trail Events are within the allowed source IP address ranges."
358+
if account_has_federated_users(aws_iam_client):
359+
annotation = f"All Cloud Trail Events are within the allowed source IP address ranges or are dependant on the federated identity provider."
343360
evaluations.append(build_evaluation(AWS_ACCOUNT_ID, "COMPLIANT", event, DEFAULT_RESOURCE_TYPE, annotation))
344361
else:
345362
annotation = "NOT all Cloud Trail Events are within the allowed source IP address ranges."

0 commit comments

Comments
 (0)