Hello, World!
With the WA2 intent binary installed, we can check our first system!
As is traditional in learning a new language, we open with the Hello, world! example,
of course we actually want something closer to:Success: target satisfies intent
but “Hello, Success: target satisfies intent” is not as catchy as “Hello, World!”
A target
We need a target system to check.
Our target will be this simple AWS CloudFormation template.
It creates a single S3 bucket.
AWSTemplateFormatVersion: "2010-09-09"
Resources:
DataBucket:
Type: AWS::S3::Bucket
We might assume that this bucket stores data because it is named DataBucket,
but that is just a guess at this point.
Write a test
We want to ask a question of the target, did you classify your data?
Let’s write a test to do that:
use aws:cfn
use data
// select which policies are active in this profile
profile example {
policy require_classification
}
// we require everything is given a classification
policy require_classification {
must all_cfn_rx_must_be_classified
}
// we need to know which cfn rx are critical
rule all_cfn_rx_must_be_classified {
// everything that is a AWS CloudFormation Resource
for resource in query(aws:cfn:Resource) {
// resource must have a data:Criticality fact attached
must query(resource/data:Criticality) {
subject: resource,
message: "Resource must be classified"
}
}
}
The code above is written in the intent language:
- we
usesome supporting namespaces for AWS CloudFormation and data classification - we create a
profileto group policies. - we define a
policydescribing whatmustbe satisfied. - finally the
rulethat is evaluated
When WA2 is asked to look at the target,
it automatically converts the CloudFormation into the WA2 graph.
So our rule can query(aws:cfn:Resource) to find all
CloudFormation resources in the graph.
Our rule also uses query(resource/data:Criticality) to
check if the resource has data Criticality evidence.
The must keyword is a modal verb (RFC 21191) that
tells WA2 how this rule is satisfied. In this case
we used must so what follows must be truthy (not empty, false, or 0).
Run the test
We can now use the CLI to check whether our target satisfies our intent:
intent check --profile example --target naive.yaml --entry naive.wa2
PREPARE
-------
✓ Read target naive.yaml
• Schedule CloudFormation validation
Validation will run concurrently and report after results.
✓ Initialise kernel
✓ Parse intent entry naive.wa2
✓ Select profile example
✓ Run analysis
RESULTS
-------
✗ Profile: example [0/1]
└─ ✗ Policy: naive:require_classification [0/1]
└─ ✗ must naive:all_cfn_rx_must_be_classified (1 finding)
└─ ✗ DataBucket
Location: naive.yaml: line 3
Message: Resource must be classified
VALIDATION
----------
✓ Validate CloudFormation against specification
We were looking for evidence of classification.
Right now, no such evidence exists.
We have not yet told WA2 how that evidence should be produced.
So the policy fails, correctly.
Note
There are three sections in the output
- PREPARE: to analyse by loading and parsing files
- RESULTS: show success or issues
- VALIDATION: of target Validation is done in parallel, and uses the CloudFormation Specification from AWS. Since validation against the specification takes time, we optimise the dev experience by running it in the background, hence why it appears last.
Add the
--novalidationparameter to disable validation for even faster execution.
You can view this as a Test-Driven Development (TDD2) approach:
- write a test
- see the test fail
- write the simplest code that helps it pass
- refactor as needed
We’ve done the first two steps already, so let’s write that simple code to help it pass.
Help it pass
Our rule looks for evidence of data classification.
We need to say how data classification is expressed in our CloudFormation implementation.
In CloudFormation, we normally do this with “AWS Tags”.
In our CloudFormation we plan to use a DataCriticality tag, so lets query for that.
We want this code to run every time WA2 tries to satisfy our intent.
We use the derive keyword to say it is going to add to the graph:
// a derive creates derived information
derive evidence_of_criticality_from_cfn_rx_tagging {
for resource in query(aws:cfn:Resource) {
let tag = query(resource/aws:Tags/*[aws:Key = "DataCriticality"])
// tagged? create a data:Criticality fact, and attach it to the resource
if tag {
let fact = add(_, wa2:type, data:Criticality)
add(resource, wa2:contains, fact)
}
}
}
Our intent code queries for all CloudFormation resources.
If a resource has an AWS Tag, and it has a DataCriticality key -
then we add data:Criticality evidence to that resource in the graph.
Fix the target
Update the target CloudFormation to include the classification tag:
AWSTemplateFormatVersion: "2010-09-09"
Resources:
DataBucket:
Type: AWS::S3::Bucket
Properties:
Tags:
- Key: DataCriticality
Value: Important
Run the test (again)
Let’s check the target again:
intent check --profile example --target tagged.yaml --entry tagged.wa2
PREPARE
-------
✓ Read target tagged.yaml
• Schedule CloudFormation validation
Validation will run concurrently and report after results.
✓ Initialise kernel
✓ Parse intent entry tagged.wa2
✓ Select profile example
✓ Run analysis
RESULTS
-------
✓ Profile: example [1/1]
VALIDATION
----------
✓ Validate CloudFormation against specification
The policy is satisfied because the required architectural fact now exists.
What just happened?
When WA2 evaluates a system, it builds a graph representation of the architecture and reasons about it.
Your CloudFormation becomes nodes and relationships in the WA2 graph.
Rules and derives operate on that graph.
CloudFormation
↓
WA2 Graph
↓ ↑
derive → evidence
↓
rule
↓
policy
↓
profile
↓
evaluation result
derivestatements addevidenceto thegraphrulesevaluate that evidencepoliciesgrouprulesinto architectural requirements
Vendor-specific logic derives facts about the system.
Architectural policies evaluate those facts without depending on implementation details.
Peering at the graph
Sometimes its useful to look at the graph, which is displayed
as a containment tree with → to indicate non-containing edges:
intent check --profile example --target tagged.yaml --entry tagged.wa2 --graph
PREPARE
-------
✓ Read target tagged.yaml
• Schedule CloudFormation validation
Validation will run concurrently and report after results.
✓ Initialise kernel
✓ Parse intent entry tagged.wa2
✓ Select profile example
✓ Run analysis
RESULTS
-------
✓ Profile: example [1/1]
GRAPH
-----
core:workload : core:Workload
├─ -core:source-
│ └─ _:59 : aws:cfn:Template
│ ├─ -aws:cfn:pseudoParameters-
│ │ └─ _:60
│ │ ├─ AWS::AccountId : aws:cfn:PseudoParameter
│ │ ├─ AWS::NotificationARNs : aws:cfn:PseudoParameter
│ │ ├─ AWS::NoValue : aws:cfn:PseudoParameter
│ │ ├─ AWS::Partition : aws:cfn:PseudoParameter
│ │ ├─ AWS::Region : aws:cfn:PseudoParameter
│ │ ├─ AWS::StackId : aws:cfn:PseudoParameter
│ │ ├─ AWS::StackName : aws:cfn:PseudoParameter
│ │ └─ AWS::URLSuffix : aws:cfn:PseudoParameter
│ └─ -aws:cfn:resources-
│ └─ _:70
│ └─ DataBucket : aws:cfn:Resource
│ aws:type="AWS::S3::Bucket"
│ aws:logicalId="DataBucket"
│ ├─ -aws:Tags-
│ │ └─ _:72
│ │ └─ _:74
│ │ aws:Key="DataCriticality"
│ │ aws:Value="Important"
│ └─ _:78 : data:Criticality
└─ _:77 : core:Store
└─ -core:source- DataBucket : aws:cfn:Resource (→)
VALIDATION
----------
✓ Validate CloudFormation against specification
Refactor as needed
Our tests are green, but they carry technical debt:
- Our policy is tightly coupled to CloudFormation
- The evidence is weak; we are not validating the tag value
- It asks a compliance question: “did you do it?”, not “did you need to?”
Let’s address that in the next chapter.