Skip to content

Commit

Permalink
Align trust vector with draft-ietf-rats-ar4si
Browse files Browse the repository at this point in the history
Bring the TrustVector within Attestation result into closer alignment
with https://datatracker.ietf.org/doc/draft-ietf-rats-ar4si/

- Align the claims inside the TrustVector with those specified by the
  ar4si spec.
- Change their values to be integers with values in the int8 range
  (note: due to protobuf limitations, actual representation is int32).
- Add a TrustTier type that corresponds to the "Trustworthiness Tier"
  concept from ar4si. ARStatus has methods to covert its value into a
  tier.
- The overall Test status is now a TrustTier. This is now set
  automatically by the core verifier from the TrustVector, rather than
  relying on Scheme plugins to update it (note: plugins can still
  override it, if necessary).
- Add "veraison-verifier-added-claims" extension to the attestation
  result and allow it to be populated by policy.

Signed-off-by: Sergei Trofimov <[email protected]>
  • Loading branch information
setrofim committed Sep 14, 2022
1 parent d228bb7 commit 3fd604b
Show file tree
Hide file tree
Showing 27 changed files with 1,011 additions and 389 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/denisbrodbeck/machineid v1.0.1
github.com/gin-gonic/gin v1.8.1
github.com/go-playground/assert/v2 v2.0.1
github.com/golang/mock v1.6.0
github.com/google/go-tpm v0.3.3
github.com/google/uuid v1.3.0
Expand All @@ -21,6 +22,7 @@ require (
github.com/veraison/psatoken v0.0.2-0.20220729120948-5bec1d03670c
google.golang.org/grpc v1.48.0
google.golang.org/protobuf v1.28.0
gopkg.in/yaml.v3 v3.0.1
)

require (
Expand Down Expand Up @@ -74,5 +76,4 @@ require (
golang.org/x/text v0.3.7 // indirect
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
96 changes: 56 additions & 40 deletions policy/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,24 @@ func Test_Agent_Evaluate(t *testing.T) {
{
Name: "success",
ReturnResult: map[string]interface{}{
"status": "SUCCESS",
"status": 2, // AFFIRMING
"trust-vector": map[string]interface{}{
"certification-status": "",
"config-integrity": "",
"hw-authenticity": "",
"runtime-integrity": "",
"sw-integrity": "SUCCESS",
"sw-up-to-dateness": "",
"instance-identity": 0,
"configuration": 0,
"executables": 2, // AFFIRMING
"file-system": 0,
"hardware": 0,
"runtime-opaque": 0,
"storage-opaque": 0,
"sourced-data": 0,
},
},
ReturnError: nil,
ExpectedError: "",
ExpectedResult: &proto.AttestationResult{
Status: proto.AR_Status_SUCCESS,
Status: proto.TrustTier_AFFIRMING,
TrustVector: &proto.TrustVector{
SoftwareIntegrity: proto.AR_Status_SUCCESS,
Executables: 2, // AFFIRMING
},
},
},
Expand All @@ -78,32 +80,36 @@ func Test_Agent_Evaluate(t *testing.T) {
ReturnResult: map[string]interface{}{
"status": "MEH",
"trust-vector": map[string]interface{}{
"certification-status": "",
"config-integrity": "",
"hw-authenticity": "",
"runtime-integrity": "",
"sw-integrity": "SUCCESS",
"sw-up-to-dateness": "",
"instance-identity": 0,
"configuration": 0,
"executables": 2, // AFFIRMING
"file-system": 0,
"hardware": 0,
"runtime-opaque": 0,
"storage-opaque": 0,
"sourced-data": 0,
},
},
ReturnError: nil,
ExpectedError: "invalid value for enum type: \"MEH\" from JSON {\"status\":\"MEH\",\"trust-vector\":{\"sw-integrity\":\"SUCCESS\"}}",
ExpectedError: "invalid value for enum type: \"MEH\"",
ExpectedResult: nil,
},
{
Name: "bad result, no status",
ReturnResult: map[string]interface{}{
"trust-vector": map[string]interface{}{
"certification-status": "",
"config-integrity": "",
"hw-authenticity": "",
"runtime-integrity": "",
"sw-integrity": "SUCCESS",
"sw-up-to-dateness": "",
"instance-identity": 0,
"configuration": 0,
"executables": 2, // AFFIRMING
"file-system": 0,
"hardware": 0,
"runtime-opaque": 0,
"storage-opaque": 0,
"sourced-data": 0,
},
},
ReturnError: nil,
ExpectedError: "backend returned outcome with no status field: map[trust-vector:map[certification-status: config-integrity: hw-authenticity: runtime-integrity: sw-integrity:SUCCESS sw-up-to-dateness:]]",
ExpectedError: "backend returned outcome with no status field",
ExpectedResult: nil,
},
{
Expand All @@ -118,13 +124,17 @@ func Test_Agent_Evaluate(t *testing.T) {
{
Name: "bad result, bad trust vector",
ReturnResult: map[string]interface{}{
"status": "SUCCESS",
"status": 2, // AFFIRMING
"trust-vector": map[string]interface{}{
"certification-status": "",
"config-integrity": "",
"hw-authenticity": "",
"wrong-field": 7,
"sw-integrity": "SUCCESS",
"instance-identity": 0,
"configuration": 0,
"executables": 2, // AFFIRMING
"file-system": 0,
"hardware": 0,
"runtime-opaque": 0,
"storage-opaque": 0,
"sourced-data": 0,
"wrong-field": 0,
},
},
ReturnError: nil,
Expand All @@ -143,7 +153,7 @@ func Test_Agent_Evaluate(t *testing.T) {
}
var endorsements []string
result := &proto.AttestationResult{
Status: proto.AR_Status_FAILURE,
Status: 96, // CONTRAINDICATED
TrustVector: &proto.TrustVector{},
}
evidence := &proto.EvidenceContext{}
Expand Down Expand Up @@ -175,16 +185,22 @@ func Test_Agent_Evaluate(t *testing.T) {
} else {
assert.Equal(t, policy.ID, res.AppraisalPolicyID)
assert.Equal(t, v.ExpectedResult.Status, res.Status)
assert.Equal(t, v.ExpectedResult.TrustVector.SoftwareIntegrity,
res.TrustVector.SoftwareIntegrity)
assert.Equal(t, v.ExpectedResult.TrustVector.SoftwareUpToDateness,
res.TrustVector.SoftwareUpToDateness)
assert.Equal(t, v.ExpectedResult.TrustVector.HardwareAuthenticity,
res.TrustVector.HardwareAuthenticity)
assert.Equal(t, v.ExpectedResult.TrustVector.CertificationStatus,
res.TrustVector.CertificationStatus)
assert.Equal(t, v.ExpectedResult.TrustVector.ConfigIntegrity,
res.TrustVector.ConfigIntegrity)
assert.Equal(t, v.ExpectedResult.TrustVector.InstanceIdentity,
res.TrustVector.InstanceIdentity)
assert.Equal(t, v.ExpectedResult.TrustVector.Configuration,
res.TrustVector.Configuration)
assert.Equal(t, v.ExpectedResult.TrustVector.Executables,
res.TrustVector.Executables)
assert.Equal(t, v.ExpectedResult.TrustVector.FileSystem,
res.TrustVector.FileSystem)
assert.Equal(t, v.ExpectedResult.TrustVector.Hardware,
res.TrustVector.Hardware)
assert.Equal(t, v.ExpectedResult.TrustVector.RuntimeOpaque,
res.TrustVector.RuntimeOpaque)
assert.Equal(t, v.ExpectedResult.TrustVector.StorageOpaque,
res.TrustVector.StorageOpaque)
assert.Equal(t, v.ExpectedResult.TrustVector.SourcedData,
res.TrustVector.SourcedData)
}
}
}
102 changes: 67 additions & 35 deletions policy/opa.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"context"
_ "embed"
"encoding/json"
"errors"
"fmt"
"log"

Expand All @@ -14,8 +15,7 @@ import (
"github.com/veraison/services/proto"
)

var ErrBadInput = "could not construct policy input: %w"
var ErrBadOPAResult = "wanted map[string]interface{}, but OPA returned: %v"
var ErrBadOPAResult = errors.New("bad result update from policy")

//go:embed opa.rego
var preambleText string
Expand Down Expand Up @@ -49,7 +49,7 @@ func (o *OPA) Evaluate(

input, err := constructInput(result, evidence, endorsements)
if err != nil {
return nil, fmt.Errorf(ErrBadInput, err)
return nil, fmt.Errorf("could not construct policy input: %w", err)
}

rego := rego.New(
Expand All @@ -67,12 +67,10 @@ func (o *OPA) Evaluate(
}

value := resultSet[0].Expressions[0].Value
resultUpdate, ok := value.(map[string]interface{})
if !ok {
return nil, fmt.Errorf(ErrBadOPAResult, value)
}

if err = validateUpdateValues(resultUpdate); err != nil {
resultUpdate, err := processUpdateValue(value)

if err != nil {
return nil, fmt.Errorf("policy returned bad update: %w", err)
}

Expand Down Expand Up @@ -106,49 +104,83 @@ func constructInput(
}, nil
}

func validateUpdateValues(update map[string]interface{}) error {
if err := checkStatusValue(update["status"]); err != nil {
return fmt.Errorf("bad \"status\" value: %w", err)
func getInt32Status(v interface{}) (int32, error) {
number, ok := v.(json.Number)
if !ok {
err := fmt.Errorf("expected json.Number, but got %T", v)
return 0, err
}

tv, ok := update["trust-vector"].(map[string]interface{})
if !ok {
return fmt.Errorf(
"bad trust-vector: expected map[string]interface{}, but got %T",
update["trust-vector"],
)
i64, err := number.Int64()
if err != nil {
return 0, err
}

for k, v := range tv {
if err := checkStatusValue(v); err != nil {
return fmt.Errorf("bad value for %q: %w", k, err)
}
if _, err := proto.Int64ToStatus(i64); err != nil {
return 0, err
}

return nil
return int32(i64), nil
}

func checkStatusValue(v interface{}) error {
s, ok := v.(string)
func processUpdateValue(value interface{}) (map[string]interface{}, error) {
rawUpdate, ok := value.(map[string]interface{})
if !ok {
return fmt.Errorf("must be a string, but got %T", v)
err := fmt.Errorf(
"%w: expected map[string]interface{}, but got %T",
ErrBadOPAResult, value)
return nil, err
}

updateTv := map[string]interface{}{
"instance-identity": 0,
"configuration": 0,
"executables": 0,
"file-system": 0,
"hardware": 0,
"runtime-opaque": 0,
"storage-opaque": 0,
"sourced-data": 0,
}

// empty string means there was no update to correpsonding key
if s == "" {
return nil
updatedStatus, err := getInt32Status(rawUpdate["status"])
if err != nil {
return nil, err
}

if _, ok = proto.TrustTier_name[updatedStatus]; !ok {
return nil, fmt.Errorf("not a valid TrustTier value: %d", updatedStatus)
}

_, ok = proto.AR_Status_value[s]
rawTv, ok := rawUpdate["trust-vector"].(map[string]interface{})
if !ok {
var valid []string
for i := 0; i < len(proto.AR_Status_name); i++ {
name := proto.AR_Status_name[int32(i)]
valid = append(valid, fmt.Sprintf("%q", name))
err := fmt.Errorf(
"%w: \"trust-vector\" value should be map[string]interface{}, but got %T",
ErrBadOPAResult, value)
return nil, err
}

for claim, rawValue := range rawTv {
if _, ok := updateTv[claim]; !ok {
err := fmt.Errorf("%w: unexpected claim %q ", ErrBadOPAResult, claim)
return nil, err
}

value, err := getInt32Status(rawValue)
if err != nil {
err := fmt.Errorf("%w: bad value %q for %q: %v",
ErrBadOPAResult, rawValue, claim, err)
return nil, err
}

return fmt.Errorf("%q is a not a valid status; must be in %v", s, valid)
updateTv[claim] = value
}

return nil
update := map[string]interface{}{
"status": updatedStatus,
"trust-vector": updateTv,
"veraison-verifier-added-claims": rawUpdate["veraison-verifier-added-claims"],
}

return update, nil
}
Loading

0 comments on commit 3fd604b

Please sign in to comment.