Skip to content

Commit

Permalink
Consistent AppraisalPolicyID and scheme-specific policies
Browse files Browse the repository at this point in the history
"Policy" as defined by the RATS architecture[1], and, therefore, the
ear.appraisal-policy-id entry n EAR[2], maps onto the combination of
attestation scheme and policy in Veraison.

This means that, when a policy is not used, the ear.appraisal-policy-id
field should be set to reflect the attestation scheme. If a policy is
used, the field should be set to reflect both, the scheme and the
policy.

Additionally, up to this point, the policy manager allowed only one
active policy per tenant. Differentiation between schemes, if necessary,
could be performed within the policy rules. This commit changes this so
that the polices are now managed based on both, the tenant and the
scheme. This means that policies for different schemes can be updated
independently by the tenant.

[1]: https://datatracker.ietf.org/doc/html/draft-ietf-rats-architecture-05
[2]: https://www.rfc-editor.org/rfc/internet-drafts/draft-fv-rats-ear-00.html

Signed-off-by: Sergei Trofimov <[email protected]>
  • Loading branch information
setrofim committed Jul 19, 2023
1 parent 2a2acce commit 1cedbf1
Show file tree
Hide file tree
Showing 26 changed files with 592 additions and 186 deletions.
1 change: 1 addition & 0 deletions integration-tests/data/results/cca.good.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"sourced-data": 0,
"storage-opaque": 2
},
"ear.appraisal-policy-id": "policy:CCA_SSD_PLATFORM",
"ear.veraison.annotated-evidence": {
"cca-platform-challenge": "Bea1iETGoM0ZOCBpuv2w5JRmKjrc+P3hFHjpM5Ua8XkP9d5ceOPbESPaCiB6i2ZVbgoi8Z7mS9wviZU7azJVXw==",
"cca-platform-config": "AQID",
Expand Down
1 change: 1 addition & 0 deletions integration-tests/data/results/enacttrust.good.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"sourced-data": 0,
"storage-opaque": 0
},
"ear.appraisal-policy-id": "policy:TPM_ENACTTRUST",
"ear.veraison.annotated-evidence": {
"node-id": "7df7714e-aa04-4638-bcbf-434b1dd720f1",
"firmware-version": 7,
Expand Down
1 change: 1 addition & 0 deletions integration-tests/data/results/psa.badcrypto.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"sourced-data": 99,
"storage-opaque": 99
},
"ear.appraisal-policy-id": "policy:PSA_IOT",
"ear.veraison.policy-claims": {
"problem": "signature validation failed"
}
Expand Down
1 change: 1 addition & 0 deletions integration-tests/data/results/psa.badinstance.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"storage-opaque": 99,
"sourced-data": 99
},
"ear.appraisal-policy-id": "policy:PSA_IOT",
"ear.veraison.policy-claims": {
"problem": "no trust anchor for evidence"
}
Expand Down
2 changes: 1 addition & 1 deletion integration-tests/data/results/psa.badswcomp.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"storage-opaque": 2,
"sourced-data": 0
},
"ear.appraisal-policy-id": "policy://PSA_OIT",
"ear.appraisal-policy-id": "policy:PSA_IOT",
"ear.veraison.annotated-evidence": {
"eat-profile": "http://arm.com/psa/2.0.0",
"psa-client-id": 1,
Expand Down
2 changes: 1 addition & 1 deletion integration-tests/data/results/psa.good.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"storage-opaque": 2,
"sourced-data": 0
},
"ear.appraisal-policy-id": "policy://PSA_OIT",
"ear.appraisal-policy-id": "policy:PSA_IOT",
"ear.veraison.annotated-evidence": {
"eat-profile": "http://arm.com/psa/2.0.0",
"psa-client-id": 1,
Expand Down
1 change: 1 addition & 0 deletions integration-tests/data/results/psa.noident.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"sourced-data": 99,
"storage-opaque": 99
},
"ear.appraisal-policy-id": "policy:PSA_IOT",
"ear.veraison.policy-claims": {
"problem": "could not establish identity from evidence"
}
Expand Down
4 changes: 4 additions & 0 deletions integration-tests/utils/checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ def compare_to_expected_result(response, expected, verifier_key):

assert decoded["ear.status"] == expected_claims["ear.status"]

if "ear.appraisal-policy-id" in expected_claims:
assert decoded["ear.appraisal-policy-id"] ==\
expected_claims["ear.appraisal-policy-id"]

for trust_claim, tc_value in decoded["ear.trustworthiness-vector"].items():
expected_value = expected_claims["ear.trustworthiness-vector"][trust_claim]
assert expected_value == tc_value, f'mismatch for claim "{trust_claim}"'
Expand Down
84 changes: 84 additions & 0 deletions policy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,87 @@ The following policy agent configuration directives are currently supported:
### `opa` backend configuration

Currently, `opa` backend does not support any configuration.

## Policy Identification

There are three different ways of identifying a policy:

### appraisal policy ID

This is an identifier used in the attestation result as defined by [EAR
Internet draft](https://www.ietf.org/archive/id/draft-fv-rats-ear-01.html#name-ear-appraisal-claims).
Note that in this case, "policy" is used in the sense of "Appraisal Policy for
Evidence" as per [RATS architecture](https://www.rfc-editor.org/rfc/rfc9334).
In Veraison, this encompasses both, the scheme, and the policy applied from the
policy store by a policy engine.

An appraisal policy ID is a [URI](https://www.rfc-editor.org/rfc/rfc3986) with
the scheme `policy` followed by a rootless path indicating the (RATS) policy
using which the appraisal has been generated. The first segment of the path is
the name of the scheme used to create the appraisal. The second segment, if
present, is the individual policy ID (see below) of the policy that has been
applied to the appraisal created by the scheme.

For example:

- `policy:TPM_ENACTTRUST`: the appraisal has been created using "TPM_ENACTTRUST" scheme, with
no additional policy applied.
- `policy:PSA_IOT/340d22f7-9eda-499f-9aa2-5af295d6d812`: the appraisal has been
created using "PSA_IOT" scheme and has subsequently been updated by the
policy with unique policy ID "ae19cc27-a449-1fb8-6c10-00f47ad1c55c".

#### Potential future extensions

These indicate potential future enhancements, and are **not** supported by the
current implementation.

##### Cascading policies

In the future we may support applying multiple individual policies to a single
appraisal. In that case, each path segment after the first (the scheme) is the
individual policy ID of a policy that has been applied. The ordering of the
segments matches the order in which the policies were applied.

For example:

- `policy:PSA_IOT/340d22f7-9eda-499f-9aa2-5af295d6d812/ae19cc27-a449-1fb8-6c10-00f47ad1c55c`:
the appraisal has been created using "PSA_IOT" scheme, it was then updated by
a policy with the individual policy id
`340d22f7-9eda-499f-9aa2-5af295d6d812`, followed by a policy with the
individual policy ID `ae19cc27-a449-1fb8-6c10-00f47ad1c55c`.

### policy store key

Policies are stored, retrieved from, and updated in the policy store using a key.
The key is a string consisting of the tenant id, scheme, and policy name
delimited by colons.

For example:

- `0:PSA_IOT:opa`: the key for tenant "0"'s policy for scheme "PSA_IOT" with
name "opa".

#### policy name

The name exists to support cascading policies in the future (see above). At the
moment, as there is only one policy allowed per appraisal, the name is not
necessary and is always set to the name of the policy engine ("opa"). While
this unnecessarily increases the key size and is somewhat wasteful, given that
the number of the policies a typical deployment is expected to be, at most, in
the hundreds, and the relatively negligible overhead compared to the size of the
polices themselves, this is not deemed to be a major concern.

### individual policy ID

The individual policy ID identifies the specific policy that was applied to an
appraisal. It forms a component of the appraisal policy ID (which also includes
the scheme, and possibly, in the future, individual IDs from multiple
policies). It differs from the policy store key in that it also incorporates
versioning information.

The individual policy id is the UUID of the specific policy instance.

For example:

- `340d22f7-9eda-499f-9aa2-5af295d6d812`: policy for tenant with id "0", named
"opa", at version 1.
5 changes: 3 additions & 2 deletions policy/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func (o *Agent) Evaluate(
return nil, fmt.Errorf("could not evaluate policy: %w", err)
}

o.logger.Debugw("policy evaluated", "policy-id", policy.ID, "updated", updatedByPolicy)
o.logger.Debugw("policy evaluated", "policy-id", policy.StoreKey, "updated", updatedByPolicy)

updatedStatus, ok := updatedByPolicy["ear.status"]
if !ok {
Expand Down Expand Up @@ -126,7 +126,8 @@ func (o *Agent) Evaluate(
if err != nil {
return nil, fmt.Errorf("bad appraisal data from policy: %w", err)
}
evaluatedAppraisal.AppraisalPolicyID = &policy.ID
evaluatedAppraisal.AppraisalPolicyID = appraisal.AppraisalPolicyID

return evaluatedAppraisal, nil
} else {
// policy did not update anything, so return the original appraisal
Expand Down
12 changes: 7 additions & 5 deletions policy/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,15 +142,17 @@ func Test_Agent_Evaluate(t *testing.T) {

ctx := context.Background()
policy := &Policy{
ID: "test-policy",
Rules: "",
StoreKey: PolicyKey{"test-tenant", "test-scheme", "test-policy"},
Rules: "",
}

var endorsements []string
contraStatus := ear.TrustTierContraindicated
polID := "policy:test-scheme"
appraisal := &ear.Appraisal{
Status: &contraStatus,
TrustVector: &ear.TrustVector{},
Status: &contraStatus,
TrustVector: &ear.TrustVector{},
AppraisalPolicyID: &polID,
}
evidence := &proto.EvidenceContext{}

Expand Down Expand Up @@ -184,7 +186,7 @@ func Test_Agent_Evaluate(t *testing.T) {
if v.ExpectedAppraisal == nil {
assert.Nil(t, res)
} else {
assert.Equal(t, policy.ID, *res.AppraisalPolicyID)
assert.Equal(t, *appraisal.AppraisalPolicyID, *res.AppraisalPolicyID)
assert.Equal(t, *v.ExpectedAppraisal.Status, *res.Status)
assert.Equal(t, v.ExpectedAppraisal.TrustVector.InstanceIdentity,
res.TrustVector.InstanceIdentity)
Expand Down
14 changes: 10 additions & 4 deletions policy/cmd/polcli/commands/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func init() {
func validateAddArgs(cmd *cobra.Command, args []string) error {
// note: assumes ExactArgs(2) matched.

if err := policy.ValidateID(args[0]); err != nil {
if _, err := policy.PolicyKeyFromString(args[0]); err != nil {
return fmt.Errorf("invalid policy ID: %w", err)
}

Expand All @@ -46,7 +46,11 @@ func validateAddArgs(cmd *cobra.Command, args []string) error {
}

func doAddCommand(cmd *cobra.Command, args []string) error {
policyID := args[0]
policyID, err := policy.PolicyKeyFromString(args[0])
if err != nil {
return err
}

policyFile := args[1]

rulesBytes, err := os.ReadFile(policyFile)
Expand All @@ -59,11 +63,13 @@ func doAddCommand(cmd *cobra.Command, args []string) error {
addFunc = store.Update
}

if err := addFunc(policyID, string(rulesBytes)); err != nil {
policy, err := addFunc(policyID, "default", "opa", string(rulesBytes))
if err != nil {
return fmt.Errorf("could not add policy: %w", err)
}

log.Printf("Policy %q stored under ID %q.\n", policyFile, policyID)
log.Printf("Policy %q stored under key %q with UUID %q .\n",
policyFile, policyID, policy.UUID)

return nil
}
7 changes: 5 additions & 2 deletions policy/cmd/polcli/commands/del.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,18 @@ var (
func validateDelArgs(cmd *cobra.Command, args []string) error {
// note: assumes ExactArgs(1) matched.

if err := policy.ValidateID(args[0]); err != nil {
if _, err := policy.PolicyKeyFromString(args[0]); err != nil {
return fmt.Errorf("invalid policy ID: %w", err)
}

return nil
}

func doDelCommand(cmd *cobra.Command, args []string) error {
policyID := args[0]
policyID, err := policy.PolicyKeyFromString(args[0])
if err != nil {
return err
}

if err := store.Del(policyID); err != nil {
return fmt.Errorf("could not delete policy: %w", err)
Expand Down
35 changes: 20 additions & 15 deletions policy/cmd/polcli/commands/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ var (
PostRunE: finiPolicyStore,
}

getVersion int32
getUUID string
getOutputFilePath string
)

func init() {
getCmd.PersistentFlags().Int32VarP(&getVersion, "version", "v", 0,
getCmd.PersistentFlags().StringVarP(&getUUID, "version", "v", "",
"get the specified, rather than latest, version")
getCmd.PersistentFlags().StringVarP(&getOutputFilePath, "output", "o", "",
"write the policy to the specified file, rather than STDOUT")
Expand All @@ -35,41 +35,46 @@ func init() {
func validateGetArgs(cmd *cobra.Command, args []string) error {
// note: assumes ExactArgs(1) matched.

if err := policy.ValidateID(args[0]); err != nil {
if _, err := policy.PolicyKeyFromString(args[0]); err != nil {
return fmt.Errorf("invalid policy ID: %w", err)
}

return nil
}

func doGetCommand(cmd *cobra.Command, args []string) error {
var policies []policy.Policy
var policy policy.Policy
var policies []*policy.Policy
var pol *policy.Policy
var err error

policyID := args[0]
policyKey, err := policy.PolicyKeyFromString(args[0])
if err != nil {
return err
}

if getVersion == 0 {
policy, err = store.GetLatest(policyID)
if getUUID == "" {
pol, err = store.GetActive(policyKey)
if err != nil {
return err
}
} else {
policies, err = store.Get(policyID)
policies, err = store.Get(policyKey)
if err != nil {
return err
}

found := false
for _, candidate := range policies {
if candidate.Version == getVersion {
policy = candidate
if candidate.UUID.String() == getUUID {
pol = candidate
found = true
break
}
}

if policy.Version == 0 {
return fmt.Errorf("version %d for policy %q not found",
getVersion, policyID)
if !found {
return fmt.Errorf("UUID %q for policy %q not found",
getUUID, policyKey)
}
}

Expand All @@ -85,7 +90,7 @@ func doGetCommand(cmd *cobra.Command, args []string) error {
writer = os.Stdout
}

if _, err := writer.Write([]byte(policy.Rules)); err != nil {
if _, err := writer.Write([]byte(pol.Rules)); err != nil {
return err
}

Expand Down
4 changes: 2 additions & 2 deletions policy/cmd/polcli/commands/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ func doListCommand(cmd *cobra.Command, args []string) error {
table.SetHeader([]string{"id", "version", "md5sum"})

for _, p := range policies {
version := fmt.Sprint(p.Version)
uuid := p.UUID.String()
md5sum := fmt.Sprintf("%x", md5.Sum([]byte(p.Rules)))
table.Append([]string{p.ID, version, md5sum})
table.Append([]string{p.StoreKey.String(), uuid, md5sum})
}

table.Render()
Expand Down
Loading

0 comments on commit 1cedbf1

Please sign in to comment.