Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a check for managed_policy conflicts on aws.iam.Role #107

Merged
merged 8 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
## Pending

## 0.5.0

- Added in checks for `alb` and `lb` to the compute and network policies
- add new securityGroupNoRuleManagementConflicts rule [#108](https://github.com/pulumi/pulumi-policy-aws/pull/108)
- Add checks for `alb` and `lb` to the compute and network policies [#903](https://github.com/pulumi/pulumi-policy-aws/pull/93)
- Update to latest `@pulumi/policy` [#103](https://github.com/pulumi/pulumi-policy-aws/pull/103)
- Add checks for iam.Role policy usage with aws.iam.RolePolicy [#107](https://github.com/pulumi/pulumi-policy-aws/pull/107)
- Add checks for SecurityGroup resource best practices [#108](https://github.com/pulumi/pulumi-policy-aws/pull/108)

## 0.4.0 (2022-09-22)

Expand Down
3 changes: 3 additions & 0 deletions integration-tests/iam/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: awsguard-test-compute
runtime: nodejs
description: Tests for policy rules related to AWS Compute.
123 changes: 123 additions & 0 deletions integration-tests/iam/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2016-2024, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import * as aws from "@pulumi/aws";
import * as pulumi from "@pulumi/pulumi";

const config = new pulumi.Config();
const testScenario = config.getNumber("scenario");

export let result: pulumi.Output<string>;

console.log(`Running test scenario #${testScenario}`);

switch (testScenario) {
case 1: // Role with managedPolicyArns by itself - OK
const role1 = new aws.iam.Role("role1", {
assumeRolePolicy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Action: "sts:AssumeRole",
Effect: "Allow",
Sid: "",
Principal: {
Service: "lambda.amazonaws.com",
},
}],
}),
managedPolicyArns: [
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
],
});

result = role1.arn;
break;
case 2: // Role with RolePolicyAttachment - OK
const role2 = new aws.iam.Role("role1", {
assumeRolePolicy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Action: "sts:AssumeRole",
Effect: "Allow",
Sid: "",
Principal: {
Service: "lambda.amazonaws.com",
},
}],
}),
});

const policyDoc2 = aws.iam.getPolicyDocument({
statements: [{
effect: "Allow",
actions: ["ec2:Describe*"],
resources: ["*"],
}],
});

const policy2 = new aws.iam.Policy("policy", {
description: "A test policy",
policy: policyDoc2.then(policy => policy.json),
});

const roleAttach2 = new aws.iam.RolePolicyAttachment("rpa", {
role: role2.name,
policyArn: policy2.arn,
});

result = roleAttach2.urn;

break;
case 3: // Role with managedPolicyArns conflicts with RolePolicyAttachment
const role3 = new aws.iam.Role("role1", {
assumeRolePolicy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Action: "sts:AssumeRole",
Effect: "Allow",
Sid: "",
Principal: {
Service: "lambda.amazonaws.com",
},
}],
}),
managedPolicyArns: [
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
],
});

const policyDoc3 = aws.iam.getPolicyDocument({
statements: [{
effect: "Allow",
actions: ["ec2:Describe*"],
resources: ["*"],
}],
});

const policy3 = new aws.iam.Policy("policy", {
description: "A test policy",
policy: policyDoc3.then(policy => policy.json),
});

const roleAttach3 = new aws.iam.RolePolicyAttachment("rpa", {
role: role3.name,
policyArn: policy3.arn,
});

result = roleAttach3.urn;

break;
default:
throw new Error(`Unexpected test scenario ${testScenario}`);
}
12 changes: 12 additions & 0 deletions integration-tests/iam/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "awsguard-test-iam",
"main": "index.ts",
"dependencies": {
"@pulumi/pulumi": "^3.0.0",
"@pulumi/aws": "^6.0.0",
"@pulumi/awsx": "^2.9.0"
},
"resolutions": {
"@pulumi/aws": "^6.0.0"
}
}
18 changes: 18 additions & 0 deletions integration-tests/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,24 @@ func TestElasticSearch(t *testing.T) {
})
}

func TestIAM(t *testing.T) {
runPolicyPackIntegrationTest(
t, "iam",
awsGuardSettings{},
map[string]string{
"aws:region": "us-west-2",
},
[]policyTestScenario{
// Test scenario 1 and 2 - happy path.
{}, {},
// Test scenario 3 - managedPolicyArns conflict.
{
WantErrors: []string{"RolePolicyAttachment should not be used with a role"},
},
},
)
}

func TestComputeEC2(t *testing.T) {
runPolicyPackIntegrationTest(
t, "compute",
Expand Down
86 changes: 86 additions & 0 deletions src/iam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2016-2024, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {
EnforcementLevel,
ReportViolation,
StackValidationArgs,
StackValidationPolicy,
} from "@pulumi/policy";

import { registerPolicy } from "./awsGuard";

// Mixin additional properties onto AwsGuardArgs.
declare module "./awsGuard" {
interface AwsGuardArgs {
iamRoleNoPolicyManagementConflicts?: EnforcementLevel;
}
}


/** @internal */
// Enforce the note on aws.iam.Role:
//
// NOTE: If you use this resource’s managed_policy_arns argument or inline_policy configuration blocks, this resource
// will take over exclusive management of the role's respective policy types (e.g., both policy types if both arguments
// are used). These arguments are incompatible with other ways of managing a role's policies, such as
// aws.iam.PolicyAttachment, aws.iam.RolePolicyAttachment, and aws.iam.RolePolicy. If you attempt to manage a role’s
// policies by multiple means, you will get resource cycling and/or errors.
export const iamRoleNoPolicyManagementConflicts: StackValidationPolicy = {
name: "iam-role-no-policy-management-conflicts",
description: "Checks that iam.Role resources do not conflict with iam.PolicyAttachment, iam.RolePolicyAttachment, iam.RolePolicy",
validateStack: (args: StackValidationArgs, reportViolation: ReportViolation) => {
args.resources.forEach(r => {
let roleProp: string;
let currentType: string;
switch (r.type) {
case "aws:iam/policyAttachment:PolicyAttachment": {
roleProp = "roles";
currentType = "PolicyAttachment";
break;
}
case "aws:iam/rolePolicyAttachment:RolePolicyAttachment": {
roleProp = "role";
currentType = "RolePolicyAttachment";
break;
}
case "aws:iam/rolePolicy:RolePolicy": {
roleProp = "role";
currentType = "RolePolicy";
break;
}
default: {
return;
}
}

if (r.propertyDependencies[roleProp]) {
r.propertyDependencies[roleProp].forEach(dep => {
if (dep.type !== "aws:iam/role:Role" || !dep.props) {
return;
}
if (dep.props["managedPolicyArns"] && dep.props["managedPolicyArns"].length > 0) {
reportViolation(`${currentType} should not be used with a role ${dep.urn} that defines managedPolicyArns`, r.urn);
}
if (dep.props["inlinePolicies"] && dep.props["inlinePolicies"].length > 0) {
reportViolation(`${currentType} should not be used with a role ${dep.urn} that defines inlinePolicies ${JSON.stringify(dep.props["inlinePolicies"])}`, r.urn);
}
});
}
return;
});
},
};

registerPolicy("iamRoleNoPolicyManagementConflicts", iamRoleNoPolicyManagementConflicts);
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import "./apiGateway";
import "./compute";
import "./database";
import "./elasticsearch";
import "./iam";
import "./network";
import "./security";
import "./storage";
Expand Down
1 change: 1 addition & 0 deletions src/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"database.ts",
"elasticsearch.ts",
"enforcementLevel.ts",
"iam.ts",
"index.ts",
"network.ts",
"policyArgs.ts",
Expand Down
Loading