Skip to content
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
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ go build -o om ./cmd/om
# Or scan cloud / cluster inventory
export AWS_REGION=us-east-1
./om scan aws
# On an EC2 instance in that account, as root, after the API scan:
# sudo -E ./om scan workload aws
# sudo -E ./om scan workload image aws
# On an Azure VM or a GCE instance in that region, as root:
# On the scanner worker, as root, after the API scan.
# Production grants are in deploy/connect.
# sudo -E ./om scan workload aws --snapshot-role "$ROLE_ARN" --external-id "$EXTERNAL_ID" --kms-key "$SCANNER_KEY"
# sudo -E ./om scan workload image aws --snapshot-role "$ROLE_ARN" --external-id "$EXTERNAL_ID" --kms-key "$SCANNER_KEY"
# sudo -E ./om scan workload azure
# sudo -E ./om scan workload image azure
# sudo -E ./om scan workload gcp
Expand Down Expand Up @@ -146,14 +146,14 @@ An API token is created only when an admin asks for one, from the console or wit
```bash
./om scan demo # sample environment, no credentials
./om scan aws
./om scan workload aws # root on an EC2 worker; Linux root-volume packages
./om scan workload image aws # root on an EC2 worker; AMI packages, applied to instances
./om scan workload aws --snapshot-role "$ROLE_ARN" --external-id "$EXTERNAL_ID" --kms-key "$SCANNER_KEY"
./om scan workload image aws --snapshot-role "$ROLE_ARN" --external-id "$EXTERNAL_ID" --kms-key "$SCANNER_KEY"
./om scan azure # requires AZURE_SUBSCRIPTION_ID + az login
./om scan workload azure # root on an Azure VM; Linux OS-disk packages
./om scan workload image azure # root on an Azure VM; boot-image packages, applied to VMs
./om scan workload azure # scanner VM; snapshots the production subscription
./om scan workload image azure # scanner VM; boot-image packages, applied to VMs
./om scan gcp # requires GCP_PROJECT_ID + ADC
./om scan workload gcp # root on a GCE instance; Linux boot-disk packages
./om scan workload image gcp # root on a GCE instance; image packages, applied to instances
./om scan workload gcp # scanner VM; snapshots the production project
./om scan workload image gcp # scanner VM; image packages, applied to instances
./om scan k8s # requires kubeconfig or in-cluster credentials
./om scan plugin -- ./my-collector
```
Expand Down
2 changes: 1 addition & 1 deletion collectors/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Cloud and platform ingestion plugins. Each collector normalizes provider APIs in

The **demo** collector loads a fixed environment (internet-exposed web tier, production database, public/private buckets, admin vs app identities, public Kubernetes service). Use it to exercise CSPM packs without cloud credentials.

The **AWS** collector emits properties the CIS pack matches on: `open_ingress`, `imdsv2`, `public_ip`, S3 `encryption` / `versioning` / `public_access_block`, and IAM user `mfa` / `unused_access_keys`. It also records RDS and Aurora instances as datastores. S3 bucket tags and the RDS `TagList` copy `sensitivity` or `data-class` onto the datastore. `sensitivity` wins when both are set. The same scan reads CloudTrail management events from the last 24 hours and stores the ones that name an identity and a resource on an exposed path. A failed lookup omits those events. `om scan aws` does not read instance disks. `om scan workload aws` inventories packages on Linux root volumes. `om scan workload image aws` reads each boot image once and applies those packages to instances launched from it.
The **AWS** collector emits properties the CIS pack matches on: `open_ingress`, `imdsv2`, `public_ip`, S3 `encryption` / `versioning` / `public_access_block`, and IAM user `mfa` / `unused_access_keys`. It also records RDS and Aurora instances as datastores. S3 bucket tags and the RDS `TagList` copy `sensitivity` or `data-class` onto the datastore. `sensitivity` wins when both are set. The same scan reads CloudTrail management events from the last 24 hours and stores the ones that name an identity and a resource on an exposed path. A failed lookup omits those events. `om scan aws` does not read instance disks. `om scan workload aws` inventories packages on Linux root volumes from a worker in a scanner account. `om scan workload image aws` reads each boot image once and applies those packages to instances launched from it. The production role is in `deploy/connect`.

The **Azure** collector records logical SQL servers. `om scan azure` and `om scan gcp` do not read OS disks. `om scan workload azure` and `om scan workload gcp` inventory packages on those disks. `om scan workload image azure` and `om scan workload image gcp` read each boot image once. The same scan reads administrative Activity Log events from the last 24 hours and stores the ones that name an identity and a resource on an exposed path. A failed lookup omits those events. Entra ID sign-in logs are not part of this scan. The **GCP** collector records Cloud SQL instances. The same scan reads Admin Activity audit logs from the last 24 hours and stores the ones that name an identity and a resource on an exposed path. A failed lookup omits those events. Data Access logs are not part of this scan. A workload gets `CAN_ACCESS` only when a security group, firewall, or VPC path allows it. Azure copies the crown-jewel mark from storage-account and SQL-server tags. GCP copies it from a bucket label or a Cloud SQL user label. The keys are `sensitivity` and `data-class`. A plugin may set `sensitivity` on a datastore directly.

Expand Down
23 changes: 23 additions & 0 deletions deploy/connect/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Connect a production account

The workload scan mounts disks only in a scanner account, subscription, or project that you run. Each production account grants a role that can snapshot and share. It does not grant permission to attach a disk or start an instance.

Apply the scanner stack once, then apply the production stack in every account you want to read. Terraform and OpenTofu use the same files. AWS also has CloudFormation and a shell script. Azure has an `az` script. GCP has a `gcloud` script.

| Cloud | Scanner | Production |
| --- | --- | --- |
| AWS | [`aws/scanner`](aws/scanner) | [`aws/production`](aws/production) |
| Azure | [`azure/scanner`](azure/scanner) | [`azure/production`](azure/production) |
| GCP | [`gcp/scanner`](gcp/scanner) | [`gcp/production`](gcp/production) |

Create the scanner role first. Pass its ARN, principal id, or service account into the production stack, with an external id you choose and store. The production key policy has to allow that principal to decrypt root-volume keys. The snippet is [`aws/kms-key-policy.json`](aws/kms-key-policy.json). Add the same principal to each Azure disk-encryption key vault and each GCP key that encrypts a boot disk.

Then, on the scanner worker, as root:

```bash
sudo -E ./om scan workload aws --snapshot-role "$ROLE_ARN" --external-id "$EXTERNAL_ID" --kms-key "$SCANNER_KEY"
sudo -E ./om scan workload azure
sudo -E ./om scan workload gcp
```

`AZURE_SUBSCRIPTION_ID` and `GCP_PROJECT_ID` are the production subscription and project. The worker's own subscription and project come from the instance metadata. AWS snapshots are copied into the scanner account with `--kms-key` when the source volume is encrypted.
20 changes: 20 additions & 0 deletions deploy/connect/aws/kms-key-policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"Sid": "AllowOpenSourceOMScanner",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::SCANNER_ACCOUNT_ID:role/opensourceom-worker"
},
"Action": [
"kms:Decrypt",
"kms:DescribeKey",
"kms:CreateGrant",
"kms:ReEncryptFrom",
"kms:ReEncryptTo"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"kms:ViaService": "ec2.REGION.amazonaws.com"
}
}
}
33 changes: 33 additions & 0 deletions deploy/connect/aws/production/apply.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/sh
# Apply the production snapshot role with the AWS CLI.
# Terraform and OpenTofu use main.tf. CloudFormation uses cloudformation.yaml.
set -eu

: "${SCANNER_ROLE_ARN:?set SCANNER_ROLE_ARN}"
: "${SCANNER_ACCOUNT_ID:?set SCANNER_ACCOUNT_ID}"
: "${EXTERNAL_ID:?set EXTERNAL_ID}"

trust=$(cat <<EOF
{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":"${SCANNER_ROLE_ARN}"},"Action":"sts:AssumeRole","Condition":{"StringEquals":{"sts:ExternalId":"${EXTERNAL_ID}"}}}]}
EOF
)

policy=$(cat <<EOF
{"Version":"2012-10-17","Statement":[
{"Sid":"Read","Effect":"Allow","Action":["ec2:DescribeInstances","ec2:DescribeImages","ec2:DescribeSnapshots","ec2:DescribeVolumes"],"Resource":"*"},
{"Sid":"CreateTaggedSnapshot","Effect":"Allow","Action":["ec2:CreateSnapshot","ec2:CreateTags"],"Resource":["arn:aws:ec2:*:*:volume/*","arn:aws:ec2:*::snapshot/*"],"Condition":{"StringEquals":{"aws:RequestTag/opensourceom:scan":"workload"}}},
{"Sid":"ShareWithScanner","Effect":"Allow","Action":"ec2:ModifySnapshotAttribute","Resource":"arn:aws:ec2:*::snapshot/*","Condition":{"StringEquals":{"ec2:Add/userId":"${SCANNER_ACCOUNT_ID}"}}},
{"Sid":"Unshare","Effect":"Allow","Action":"ec2:ModifySnapshotAttribute","Resource":"arn:aws:ec2:*::snapshot/*","Condition":{"StringEquals":{"ec2:Remove/userId":"${SCANNER_ACCOUNT_ID}"}}},
{"Sid":"DeleteOurSnapshots","Effect":"Allow","Action":"ec2:DeleteSnapshot","Resource":"arn:aws:ec2:*::snapshot/*","Condition":{"StringEquals":{"ec2:ResourceTag/opensourceom:scan":"workload"}}},
{"Sid":"EncryptedVolumes","Effect":"Allow","Action":["kms:CreateGrant","kms:Decrypt","kms:DescribeKey","kms:ReEncryptFrom","kms:ReEncryptTo"],"Resource":"*"}
]}
EOF
)

if aws iam get-role --role-name opensourceom-snapshot >/dev/null 2>&1; then
aws iam update-assume-role-policy --role-name opensourceom-snapshot --policy-document "$trust"
else
aws iam create-role --role-name opensourceom-snapshot --assume-role-policy-document "$trust"
fi
aws iam put-role-policy --role-name opensourceom-snapshot --policy-name snapshot --policy-document "$policy"
aws iam get-role --role-name opensourceom-snapshot --query Role.Arn --output text
87 changes: 87 additions & 0 deletions deploy/connect/aws/production/cloudformation.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
AWSTemplateFormatVersion: "2010-09-09"
Description: Grant the OpenSourceOM scanner account permission to snapshot and share. This stack does not attach a disk.

Parameters:
ScannerRoleArn:
Type: String
Description: ARN of opensourceom-worker in the scanner account.
ScannerAccountId:
Type: String
AllowedPattern: "[0-9]{12}"
ExternalId:
Type: String
NoEcho: true

Resources:
SnapshotRole:
Type: AWS::IAM::Role
Properties:
RoleName: opensourceom-snapshot
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS: !Ref ScannerRoleArn
Action: sts:AssumeRole
Condition:
StringEquals:
sts:ExternalId: !Ref ExternalId
Policies:
- PolicyName: snapshot
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: Read
Effect: Allow
Action:
- ec2:DescribeInstances
- ec2:DescribeImages
- ec2:DescribeSnapshots
- ec2:DescribeVolumes
Resource: "*"
- Sid: CreateTaggedSnapshot
Effect: Allow
Action:
- ec2:CreateSnapshot
- ec2:CreateTags
Resource:
- arn:aws:ec2:*:*:volume/*
- arn:aws:ec2:*::snapshot/*
Condition:
StringEquals:
aws:RequestTag/opensourceom:scan: workload
- Sid: ShareWithScanner
Effect: Allow
Action: ec2:ModifySnapshotAttribute
Resource: arn:aws:ec2:*::snapshot/*
Condition:
StringEquals:
ec2:Add/userId: !Ref ScannerAccountId
- Sid: Unshare
Effect: Allow
Action: ec2:ModifySnapshotAttribute
Resource: arn:aws:ec2:*::snapshot/*
Condition:
StringEquals:
ec2:Remove/userId: !Ref ScannerAccountId
- Sid: DeleteOurSnapshots
Effect: Allow
Action: ec2:DeleteSnapshot
Resource: arn:aws:ec2:*::snapshot/*
Condition:
StringEquals:
ec2:ResourceTag/opensourceom:scan: workload
- Sid: EncryptedVolumes
Effect: Allow
Action:
- kms:CreateGrant
- kms:Decrypt
- kms:DescribeKey
- kms:ReEncryptFrom
- kms:ReEncryptTo
Resource: "*"

Outputs:
RoleArn:
Value: !GetAtt SnapshotRole.Arn
119 changes: 119 additions & 0 deletions deploy/connect/aws/production/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
variable "scanner_role_arn" {
type = string
description = "ARN of opensourceom-worker in the scanner account."
}

variable "scanner_account_id" {
type = string
description = "Account id that snapshots are shared with."
}

variable "external_id" {
type = string
description = "Value the worker passes to sts:AssumeRole."
}

data "aws_iam_policy_document" "trust" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "AWS"
identifiers = [var.scanner_role_arn]
}
condition {
test = "StringEquals"
variable = "sts:ExternalId"
values = [var.external_id]
}
}
}

data "aws_iam_policy_document" "snapshot" {
statement {
sid = "Read"
actions = [
"ec2:DescribeInstances",
"ec2:DescribeImages",
"ec2:DescribeSnapshots",
"ec2:DescribeVolumes",
]
resources = ["*"]
}

statement {
sid = "CreateTaggedSnapshot"
actions = [
"ec2:CreateSnapshot",
"ec2:CreateTags",
]
resources = [
"arn:aws:ec2:*:*:volume/*",
"arn:aws:ec2:*::snapshot/*",
]
condition {
test = "StringEquals"
variable = "aws:RequestTag/opensourceom:scan"
values = ["workload"]
}
}

statement {
sid = "ShareWithScanner"
actions = ["ec2:ModifySnapshotAttribute"]
resources = ["arn:aws:ec2:*::snapshot/*"]
condition {
test = "StringEquals"
variable = "ec2:Add/userId"
values = [var.scanner_account_id]
}
}

statement {
sid = "Unshare"
actions = ["ec2:ModifySnapshotAttribute"]
resources = ["arn:aws:ec2:*::snapshot/*"]
condition {
test = "StringEquals"
variable = "ec2:Remove/userId"
values = [var.scanner_account_id]
}
}

statement {
sid = "DeleteOurSnapshots"
actions = ["ec2:DeleteSnapshot"]
resources = ["arn:aws:ec2:*::snapshot/*"]
condition {
test = "StringEquals"
variable = "ec2:ResourceTag/opensourceom:scan"
values = ["workload"]
}
}

statement {
sid = "EncryptedVolumes"
actions = [
"kms:CreateGrant",
"kms:Decrypt",
"kms:DescribeKey",
"kms:ReEncryptFrom",
"kms:ReEncryptTo",
]
resources = ["*"]
}
}

resource "aws_iam_role" "snapshot" {
name = "opensourceom-snapshot"
assume_role_policy = data.aws_iam_policy_document.trust.json
}

resource "aws_iam_role_policy" "snapshot" {
name = "snapshot"
role = aws_iam_role.snapshot.id
policy = data.aws_iam_policy_document.snapshot.json
}

output "role_arn" {
value = aws_iam_role.snapshot.arn
}
26 changes: 26 additions & 0 deletions deploy/connect/aws/scanner/apply.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/sh
# Apply the scanner instance profile with the AWS CLI.
set -eu

: "${EXTERNAL_ID:?set EXTERNAL_ID}"

trust='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole"}]}'

policy=$(cat <<EOF
{"Version":"2012-10-17","Statement":[
{"Sid":"CopyAndAttach","Effect":"Allow","Action":["ec2:DescribeInstances","ec2:DescribeSnapshots","ec2:DescribeVolumes","ec2:CopySnapshot","ec2:CreateVolume","ec2:CreateTags","ec2:AttachVolume","ec2:DetachVolume","ec2:DeleteVolume","ec2:DeleteSnapshot"],"Resource":"*"},
{"Sid":"AssumeProduction","Effect":"Allow","Action":"sts:AssumeRole","Resource":"arn:aws:iam::*:role/opensourceom-snapshot","Condition":{"StringEquals":{"sts:ExternalId":"${EXTERNAL_ID}"}}},
{"Sid":"Keys","Effect":"Allow","Action":["kms:CreateGrant","kms:Decrypt","kms:Encrypt","kms:DescribeKey","kms:GenerateDataKeyWithoutPlaintext","kms:ReEncryptFrom","kms:ReEncryptTo"],"Resource":"*"}
]}
EOF
)

if aws iam get-role --role-name opensourceom-worker >/dev/null 2>&1; then
aws iam update-assume-role-policy --role-name opensourceom-worker --policy-document "$trust"
else
aws iam create-role --role-name opensourceom-worker --assume-role-policy-document "$trust"
fi
aws iam put-role-policy --role-name opensourceom-worker --policy-name worker --policy-document "$policy"
aws iam create-instance-profile --instance-profile-name opensourceom-worker 2>/dev/null || true
aws iam add-role-to-instance-profile --instance-profile-name opensourceom-worker --role-name opensourceom-worker 2>/dev/null || true
aws iam get-role --role-name opensourceom-worker --query Role.Arn --output text
Loading
Loading