Skip to content
This repository has been archived by the owner on Jun 25, 2024. It is now read-only.

Commit

Permalink
Merge pull request #296 from JupiterOne/INT-1561-add-is-public-to-bin…
Browse files Browse the repository at this point in the history
…dings

INT-1561 - Add isPublic and isOpenToTheInternet to IAM bindings
  • Loading branch information
mknoedel authored Aug 27, 2021
2 parents a15954d + 4170225 commit a61aacc
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 30 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ and this project adheres to

## [Unreleased]

### Added

- New properties added to resources:

| Entity | Properties |
| -------------------- | ---------- |
| `google_iam_binding` | `readonly` |

## 0.47.5

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion docs/jupiterone.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ NOTE: ALL OF THE FOLLOWING DOCUMENTATION IS GENERATED USING THE
"j1-integration document" COMMAND. DO NOT EDIT BY HAND! PLEASE SEE THE DEVELOPER
DOCUMENTATION FOR USAGE INFORMATION:
https://github.com/JupiterOne/sdk/blob/master/docs/integrations/development.md
https://github.com/JupiterOne/sdk/blob/main/docs/integrations/development.md
********************************************************************************
-->

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Object {
],
"name": "role_binding_for_resourceresource",
"projectId": "project-id",
"readonly": true,
"resource": "resource",
"role": "projects/j1-gc-integration-dev-v3/roles/167984947943customroleconditions",
}
Expand Down
24 changes: 12 additions & 12 deletions src/steps/cloud-asset/converters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@ import { getMockRoleBinding } from '../../../test/mocks';
import { buildIamBindingEntityKey, createIamBindingEntity } from './converters';

describe('#createIamBindingEntity', () => {
test('should convert to entity', () => {
const resource = 'resource';
const projectName = 'projects/123456789';
const projectId = 'project-id';
const binding = getMockRoleBinding();

const _key = buildIamBindingEntityKey({
binding,
projectName,
resource,
});
const resource = 'resource';
const projectName = 'projects/123456789';
const projectId = 'project-id';
const binding = getMockRoleBinding();
const isReadOnly = true;

test('should convert to entity', () => {
expect(
createIamBindingEntity({
_key,
_key: buildIamBindingEntityKey({
binding,
projectName,
resource,
}),
binding,
projectId,
resource,
isReadOnly,
}),
).toMatchSnapshot();
});
Expand Down
4 changes: 4 additions & 0 deletions src/steps/cloud-asset/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface BindingEntity extends Entity {
'condition.description': string;
'condition.expression': string;
'condition.location': string;
readonly: boolean;
}

export function buildIamBindingEntityKey({
Expand Down Expand Up @@ -44,11 +45,13 @@ export function createIamBindingEntity({
projectId,
binding,
resource,
isReadOnly,
}: {
_key: string;
projectId?: string;
binding: cloudasset_v1.Schema$Binding;
resource: string | undefined | null;
isReadOnly: boolean;
}): BindingEntity {
const namePrefix = 'Role Binding for Resource: ';

Expand All @@ -75,6 +78,7 @@ export function createIamBindingEntity({
'condition.description': binding.condition?.description,
'condition.expression': binding.condition?.expression,
'condition.location': binding.condition?.location,
readonly: isReadOnly, // Are all the permissions associated with this binding read only permissions
},
},
}) as BindingEntity;
Expand Down
26 changes: 24 additions & 2 deletions src/steps/cloud-asset/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
CLOUD_STORAGE_BUCKET_ENTITY_TYPE,
} from '../storage';
import { CLOUD_FUNCTION_ENTITY_TYPE, fetchCloudFunctions } from '../functions';
import { IAM_MANAGED_ROLES_DATA_JOB_STATE_KEY } from '../../utils/iam';

expect.extend({
toHaveOnlyDirectRelationships(
Expand Down Expand Up @@ -353,6 +354,7 @@ describe('#fetchIamBindings', () => {
expect(google_iam_binding).toMatchGraphObjectSchema({
_class: bindingEntities.BINDINGS._class,
schema: {
additionalProperties: false,
properties: {
_type: { const: bindingEntities.BINDINGS._type },
_rawData: {
Expand All @@ -362,15 +364,17 @@ describe('#fetchIamBindings', () => {
resource: { type: 'string' },
projectId: { type: 'string' },
members: { type: 'array' },
role: { type: 'string' },
'condition.title': { type: 'string' },
'condition.description': { type: 'string' },
'condition.expression': { type: 'string' },
'condition.location': { type: 'string' },
readonly: { type: 'boolean' },
},
},
});

expect(google_iam_role.length).toBeGreaterThan(0);
expect(google_iam_role).toMatchGraphObjectSchema({
const roleSchema = {
_class: ['AccessRole'],
schema: {
additionalProperties: false,
Expand All @@ -389,6 +393,20 @@ describe('#fetchIamBindings', () => {
readonly: { type: 'boolean' },
},
},
};
expect(google_iam_role.length).toBeGreaterThan(0);
expect(google_iam_role).toMatchGraphObjectSchema(roleSchema);
const { targets: roleMappedRelationships } = filterGraphObjects(
google_iam_binding_uses_role,
(r) => !!r._mapping,
) as {
targets: ExplicitRelationship[];
rest: MappedRelationship[];
};
roleMappedRelationships.forEach((relationship) => {
expect(
(relationship as unknown as MappedRelationship)._mapping.targetEntity,
).toMatchGraphObjectSchema(roleSchema);
});

// Ensure there are no mapped relationships to google_iam_roles that we are also ingesting
Expand Down Expand Up @@ -472,6 +490,10 @@ describe('#fetchIamBindings', () => {

const context = createMockContext();

await context.jobState.setData(
IAM_MANAGED_ROLES_DATA_JOB_STATE_KEY,
{},
);
await fetchIamBindings(context);
await createBindingToAnyResourceRelationships(context);

Expand Down
55 changes: 43 additions & 12 deletions src/steps/cloud-asset/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
createMappedRelationship,
generateRelationshipType,
IntegrationStep,
JobState,
Relationship,
RelationshipClass,
RelationshipDirection,
Expand Down Expand Up @@ -43,6 +44,26 @@ import {
} from '../../utils/iamBindings/getTypeAndKeyFromResourceIdentifier';
import { getEnabledServiceNames } from '../enablement';
import { MULTIPLE_J1_TYPES_FOR_RESOURCE_KIND } from '../../utils/iamBindings/resourceKindToTypeMap';
import { isReadOnlyPermission } from '../../utils/iam';
import { createIamRoleEntity } from '../iam/converters';

async function isBindingReadOnly(
jobState: JobState,
roleName: string | undefined | null,
): Promise<boolean> {
let permissions: string[] | null | undefined = undefined;
if (roleName) {
// previously ingested custom role entity
const roleEntity = await jobState.findEntity(roleName);
if (roleEntity) {
permissions = ((roleEntity.permissions as string) || '').split(',');
} else {
// managed role data from jobState
permissions = await getPermissionsForManagedRole(jobState, roleName);
}
}
return permissions?.some(isReadOnlyPermission) ?? true; // default to true if there are no permissions
}

export async function fetchIamBindings(
context: IntegrationStepContext,
Expand Down Expand Up @@ -73,19 +94,23 @@ export async function fetchIamBindings(
}

let projectId: string | undefined;

if (projectName) {
projectId = await getProjectIdFromName(jobState, projectName);

if (!projectId) {
// This should never happen because this step depends on another
// step that collects all of the project data in an organization.
// This would only happen if we have not run fetch-resource-manager-org-project-relationships, which caches project data
logger.warn({ projectName }, 'Missing project ID in local cache');
}
}

const isReadOnly = await isBindingReadOnly(jobState, binding.role);
await jobState.addEntity(
createIamBindingEntity({ _key, projectId, binding, resource }),
createIamBindingEntity({
_key,
projectId,
binding,
resource,
isReadOnly,
}),
);

bindingGraphKeySet.add(_key);
Expand Down Expand Up @@ -145,10 +170,20 @@ export async function createBindingRoleRelationships(
}),
);
} else {
const permissions = await getPermissionsForManagedRole(
const includedPermissions = await getPermissionsForManagedRole(
jobState,
bindingEntity.role,
);
const targetRoleEntitiy = createIamRoleEntity(
{
name: bindingEntity.role,
title: bindingEntity.role,
includedPermissions,
},
{
custom: false,
},
);
await jobState.addRelationship(
createMappedRelationship({
_class: RelationshipClass.USES,
Expand All @@ -163,12 +198,8 @@ export async function createBindingRoleRelationships(
targetFilterKeys: [['_type', '_key']],
skipTargetCreation: false,
targetEntity: {
_type: IAM_ROLE_ENTITY_TYPE,
_key: bindingEntity.role,
name: bindingEntity.role,
displayName: bindingEntity.role,
permissions: permissions?.join(','),
custom: !!permissions, // If there are permissions, this is a managed role
...targetRoleEntitiy,
_rawData: undefined,
},
},
}),
Expand Down
6 changes: 5 additions & 1 deletion src/steps/dataproc/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import {
import { integrationConfig } from '../../../test/config';
import { setupGoogleCloudRecording } from '../../../test/recording';
import { IntegrationConfig } from '../../types';
import { createClusterStorageRelationships, createClusterImageRelationships, fetchDataprocClusters } from '.';
import {
createClusterStorageRelationships,
createClusterImageRelationships,
fetchDataprocClusters,
} from '.';
import {
ENTITY_TYPE_DATAPROC_CLUSTER,
RELATIONSHIP_TYPE_DATAPROC_CLUSTER_USES_COMPUTE_IMAGE,
Expand Down
3 changes: 2 additions & 1 deletion src/utils/iam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,9 @@ export function parseIamMember(member: string): ParsedIamMember {
* - https://cloud.google.com/iam/docs/overview#allusers
*/
export function isMemberPublic(member: string) {
return member === 'allUsers' || member === 'allAuthenticatedUsers';
return PUBLIC_MEMBERS.includes(member);
}
export const PUBLIC_MEMBERS = ['allUsers', 'allAuthenticatedUsers'];

export async function getIamManagedRoleData(
jobState: JobState,
Expand Down
6 changes: 5 additions & 1 deletion src/utils/iamBindings/getTypeAndKeyFromResourceIdentifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,15 @@ export interface TypeAndKey {
* returns - j1-gc-integration-dev-v3:natality
*/
export async function getTypeAndKeyFromResourceIdentifier(
googleResourceIdentifier: string,
googleResourceIdentifier: string | undefined,
context: StepExecutionContext,
): Promise<TypeAndKey> {
const response: TypeAndKey = { metadata: {} };

if (!googleResourceIdentifier) {
return response;
}

const googleResourceKind = findResourceKindFromCloudResourceIdentifier(
googleResourceIdentifier,
);
Expand Down

0 comments on commit a61aacc

Please sign in to comment.