Examples¶
The install, in two parts¶
Part 1 creates the parameters that hold the ExternalIds and the registration token; Part 2 is the module call, with their ARNs. Both go in one root:
resource "aws_ssm_parameter" "loopproof_external_ids" {
name = "/loopproof/production/external-ids"
description = "ExternalIds that LoopProof presents to the connector role, as a JSON list."
type = "SecureString"
value_wo = "unset"
value_wo_version = 1
}
resource "aws_ssm_parameter" "loopproof_registration_token" {
name = "/loopproof/production/registration-token"
description = "Registration token from LoopProof's onboarding."
type = "SecureString"
value_wo = "unset"
value_wo_version = 1
}
module "loopproof_connector" {
source = "registry.infrahouse.com/infrahouse/loopproof-connector/aws"
version = "1.3.0"
environment = "production"
control_plane_account_id = "123456789012"
external_ids_parameter_arn = aws_ssm_parameter.loopproof_external_ids.arn
registration_token_parameter_arn = aws_ssm_parameter.loopproof_registration_token.arn
event_bus_arn = "arn:aws:events:us-west-1:123456789012:event-bus/loopproof-production-findings"
registration_url = "https://api.loopproof.ai/connector/registrations"
}
Apply, fill both parameters, apply again: see Getting Started. A runnable version is in examples/basic.
Parameters you manage another way¶
Skip Part 1 and pass the ARNs of your own SecureString parameters, in the account and Region of the AWS provider:
module "loopproof_connector" {
# ...
external_ids_parameter_arn = "arn:aws:ssm:us-east-1:111122223333:parameter/security/external-ids"
registration_token_parameter_arn = "arn:aws:ssm:us-east-1:111122223333:parameter/security/loopproof-token"
}
The ExternalIds' parameter must hold a JSON list, such as ["<ExternalId>"].
A customer-managed key¶
Whoever applies the root then needs kms:Decrypt on the key, and the fill commands need --key-id with it.
Forward Medium findings too¶
This adds Inspector's, CSPM's and exposure findings of Medium severity. GuardDuty's are forwarded at every severity already, whatever severities lists.
Exclude accounts¶
Keep the findings of an account outside your audit scope, or of a decommissioned one, in your account:
The next apply updates the forwarding rule and the parameter /loopproof/<environment>/excluded-accounts, which LoopProof reads. Set excluded_accounts = [], or remove it, to include the account again.
Accept a risk¶
Accept port probes on your VPN endpoints, which are public by design, once its register entry, SEC-42, is in your Linear's "LoopProof risk register" project:
module "loopproof_connector" {
# ...
threat_accept_rules = [
{
id = "vpn-port-probes"
finding_types = ["Recon:EC2/PortProbeUnprotectedPort"]
where = {
tags = { role = "vpn" }
accounts = ["111122223333"]
}
reason = "VPN endpoints are public by design"
review_by = "2027-03-31"
decision_ref = "SEC-42"
},
]
}
A vulnerability you accept, in vulnerability_accept_rules, names its CVEs, or a source package and its version, and where it applies:
vulnerability_accept_rules = [
{
id = "legacy-glibc"
package = { name = "glibc", version = "2.26-64.amzn2.0.1" }
where = { resource_ids = ["i-0123456789abcdef0"] }
reason = "The instance is retired in January"
review_by = "2027-01-31"
decision_ref = "OPS-7"
},
]
The next apply publishes each rule in /loopproof/<environment>/accept-rules/<id>, and LoopProof reads them within 15 minutes, or at once from its Settings page. To renew a rule, change its review_by; to stop it, remove it.
Rotating the ExternalId¶
The Terraform doesn't change; the parameter does.
-
Add the new value to the parameter, and apply. LoopProof's calls keep working with the old one. The command reads the current list from the parameter and adds the value you type; it prints neither.
printf 'New ExternalId: ' >&2; stty -echo; read -r new; stty echo; printf '\n' >&2 current=$(aws ssm get-parameter --with-decryption --name /loopproof/production/external-ids \ --query Parameter.Value --output text) printf '%s' "${current%]},\"$new\"]" | aws ssm put-parameter --overwrite --type SecureString \ --name /loopproof/production/external-ids --value file:///dev/stdin; unset current new terraform apply -
Wait at least 10 minutes, then tell LoopProof to switch to the new value.
-
Keep the new value only, and apply. The command keeps the last value of step 1's list, the new one, and prints nothing; it expects the two values step 1 left.
current=$(aws ssm get-parameter --with-decryption --name /loopproof/production/external-ids \ --query Parameter.Value --output text) printf '[%s' "${current##*,}" | aws ssm put-parameter --overwrite --type SecureString \ --name /loopproof/production/external-ids --value file:///dev/stdin; unset current terraform apply
Neither apply replaces the role: Terraform updates its trust policy in place, and the role doesn't register again. The wait in step 2 is for IAM: for a few minutes after the first apply, STS still refuses the new value now and then.
Registering again¶
terraform apply -replace=module.loopproof_connector.terraform_data.registration makes the registration call once more, with the token in the parameter. LoopProof answers 200 for a role it already has.