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
33 changes: 33 additions & 0 deletions .github/workflows/integ.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# GitHub Actions - CI for Go to build & test. See ci-go-cover.yml and linters.yml for code coverage and linters.
# Taken from: https://github.com/fxamacker/cbor/workflows/ci.yml (thanks!)
name: integration-tests
on: [push, pull_request]
jobs:

# Test on various OS with specified Go version.
tests:
name: ubuntu-latest
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v5
with:
go-version: "1.25.1"
- name: Install jq
run:
sudo apt-get install --yes jq
- name: Install Docker
run: |
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
sudo systemctl restart docker
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Checkout code
uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Build project
run: make
- name: Run tests
run: |
make integ-test
9 changes: 9 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[submodule "test/bats"]
path = test/bats
url = https://github.com/bats-core/bats-core.git
[submodule "test/test_helper/bats-support"]
path = test/test_helper/bats-support
url = https://github.com/bats-core/bats-support.git
[submodule "test/test_helper/bats-assert"]
path = test/test_helper/bats-assert
url = https://github.com/bats-core/bats-assert.git
18 changes: 13 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ build:
test:
$(GO) test -tags=test ./... $(TEST_ARGS)

.PHONY: integ-test
integ-test:
@scripts/integration-tests.sh setup
@scripts/integration-tests.sh run
@scripts/integration-tests.sh teardown

.PHONY: fmt format gofmt
fmt format gofmt: */*/*.go */*/*/*.go
$(GOFMT) -w */*/*.go */*/*/*.go
Expand All @@ -43,8 +49,7 @@ cover-report coverage-report report: coverage.out

.PHONY: presubmit
presubmit:
# TODO: increase coverage
@$(MAKE) -s test && $(MAKE) -s lint && COVERAGE_THRESHOLD=30% $(MAKE) -s coverage && $(MAKE) -s format
@$(MAKE) -s test && $(MAKE) -s lint && $(MAKE) -s coverage && $(MAKE) -s format
@if ! $(GIT) diff-index --quiet HEAD --; then \
echo -e "\033[1;31mUNCOMMITED CHANGES!\033[0m"; \
exit 2; \
Expand All @@ -60,15 +65,18 @@ Targets:
Run unit tests. Use VERBOSE=1 for SQL traces. Set TEST_DB_FILE to
specify the path for the sqlite DB file to be used for tests (by default
in-memory DB is used).
integ-test:
Run integration tests. These rely on a Docker container running database
servers.
lint:
Run golangci-lint.
fmt, format, gofmt:
Run gofmt -w on Go sourcefiles, fixing any formatting issues.
cover, coverage:
Report overall test coverage percentage and generate coverage.out. You
can specify a space-separated list of packages for which coverage will
not be checked by setting IGNORE_COVERAGE. You can specify alternative
threshold by setting COVERAGE_THRESHOLD.
can specify a space-separated list of packages for which coverage will
not be checked by setting IGNORE_COVERAGE. You can specify alternative
threshold by setting COVERAGE_THRESHOLD.
cover-report, coverage-report, report:
Open a detailed HTML coverage report in default browser.
presubmit:
Expand Down
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,42 @@ variables. To get then environment variable name, change the setting name to be
upper case, replace `-` with `_`, and prefix it with `CORIM_STORE_`. E.g. to
set `no-color` via an environment variable, you would set
`CORIM_STORE_NO_COLOR=1`.

## Store Design Overview

The store is intended for endorsements, reference values, and trust anchors
that will be used for attestation verification. These are stored as value and

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
that will be used for attestation verification. These are stored as value and
that will be used for attestation verification. These are stored as value triples and

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be clear from confusion of key and value in a store!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"stored as value and key triples" is correct, and sounds more natural than "stored as value triples and key triples".

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I understand your point of view, but just a disjoint reader may mistake it to a key value semantics so I suggested a bit explicit, statement. It is fine, if you think, this is not the case!

key triples, which associate an attesting environment description with
corresponding measurements and cryptographic keys.

Sets of triples are grouped under "module tags" (corresponding to CoMIDs),
which are, in turn, associated with "manifests" (corresponding to CoRIMs).
Module tags group claims (contained in triples) relating to a logical system
module (hardware, firmware, etc). A manifest acts as an "envelope" containing
additional metadata about the module tags it contains (such as the validity
period).

![E-R diagram](misc/corim-store-er-simplified.drawio.png)

The above diagram is a simplified entity-relation diagram for the major
entities used by the store. It omits some "meta" fields (e.g. foreign keys),
and only shows the main logical entities within the store. For a complete
overview of the SQL tables created by the store, please see the [schema
diagram](misc/corim-store-schema.drawio.png).

### Triple Life Cycle

By default, when triples are added, they are in an inactive state. They must be
activated in order to become available for use in verification. This decouples
provisioning values into the store from making them available for verification.
Triples may also be deactivated (e.g. when the associated attesters have been
decommissioned), but remain "archived" in the store.

```mermaid
stateDiagram-v2
[*] --> Inactive: add
Inactive --> Active: activate
Active --> Inactive: deactivate
Inactive --> [*]: delete
Active --> [*]: delete
```
160 changes: 160 additions & 0 deletions cmd/corim-store/cmd/common.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
package cmd

import (
"encoding/base64"
"encoding/hex"
"fmt"
"os"
"strings"

"github.com/google/uuid"
"github.com/spf13/cobra"
"github.com/veraison/corim-store/pkg/model"
"github.com/veraison/corim/comid"
)

func CheckErr(msg interface{}) {
Expand Down Expand Up @@ -35,3 +43,155 @@ func Green(msg string) string {

return fmt.Sprintf("\033[1;32m%s\033[0m", msg)
}

func AddEnviromentFlags(cmd *cobra.Command) {
cmd.Flags().StringP("class-id", "C", "", "Environment class ID.")
cmd.Flags().StringP("vendor", "V", "", "Environment vendor.")
cmd.Flags().StringP("model", "M", "", "Environment model.")
cmd.Flags().Int64P("layer", "L", -1, "Environment layer.")
cmd.Flags().Int64P("index", "I", -1, "Environment index.")
cmd.Flags().StringP("instance-id", "i", "", "Environment instance ID")
cmd.Flags().StringP("group-id", "g", "", "Environment group ID")
}

func BuildEnvironment(cmd *cobra.Command) (*model.Environment, error) {
var ret model.Environment

vendor, err := cmd.Flags().GetString("vendor")
if err != nil {
return nil, fmt.Errorf("vendor: %w", err)
}
if vendor != "" {
ret.Vendor = &vendor
}

model, err := cmd.Flags().GetString("model")
if err != nil {
return nil, fmt.Errorf("model: %w", err)
}
if model != "" {
ret.Model = &model
}

layerInt, err := cmd.Flags().GetInt64("layer")
if err != nil {
return nil, fmt.Errorf("layer: %w", err)
}
if layerInt > -1 {
layer := uint64(layerInt)
ret.Layer = &layer
}

indexInt, err := cmd.Flags().GetInt64("index")
if err != nil {
return nil, fmt.Errorf("index: %w", err)
}
if indexInt > -1 {
index := uint64(indexInt)
ret.Index = &index
}

classIDText, err := cmd.Flags().GetString("class-id")
if err != nil {
return nil, fmt.Errorf("class-id: %w", err)
}

if classIDText != "" {
classIDBytes, classIDType, err := parseID(classIDText)
if err != nil {
return nil, fmt.Errorf("class-id: %w", err)
}

ret.ClassBytes = &classIDBytes
if classIDType != "" {
ret.ClassType = &classIDType
}
}

instanceIDText, err := cmd.Flags().GetString("instance-id")
if err != nil {
return nil, fmt.Errorf("instance-id: %w", err)
}

if instanceIDText != "" {
instanceIDBytes, instanceIDType, err := parseID(instanceIDText)
if err != nil {
return nil, fmt.Errorf("instance-id: %w", err)
}

ret.InstanceBytes = &instanceIDBytes
if instanceIDType != "" {
ret.InstanceType = &instanceIDType
}
}

groupIDText, err := cmd.Flags().GetString("group-id")
if err != nil {
return nil, fmt.Errorf("group-id: %w", err)
}

if groupIDText != "" {
groupIDBytes, groupIDType, err := parseID(groupIDText)
if err != nil {
return nil, fmt.Errorf("group-id: %w", err)
}

ret.GroupBytes = &groupIDBytes
if groupIDType != "" {
ret.GroupType = &groupIDType
}
}

return &ret, nil
}

func RenderEnviroment(env *model.Environment) (string, error) {
parts, err := env.RenderParts()
if err != nil {
return "", err
}

ret := ""
for _, part := range parts {
ret += fmt.Sprintf("%s: %s\n", part[0], part[1])
}

return ret, nil
}

func parseID(text string) ([]byte, string, error) {
var typeText string
var valueText string

parts := strings.SplitN(text, ":", 2)
if len(parts) == 2 {
typeText = parts[0]
valueText = parts[1]
} else {
valueText = text
}

switch typeText {
case "uuid":
ret, err := uuid.Parse(valueText)
return ret[:], "uuid", err
case "oid":
var ret comid.OID
if err := ret.FromString(valueText); err != nil {
return nil, "oid", err
}
return []byte(ret), "oid", nil
case "hex":
ret, err := hex.DecodeString(valueText)
return ret, "hex", err
default: // assume base64
// remove padding
valueText = strings.Trim(valueText, "=")
// if URL, convert to standard
valueText = strings.ReplaceAll(valueText, "-", "+")
valueText = strings.ReplaceAll(valueText, "_", "/")

ret, err := base64.RawStdEncoding.DecodeString(valueText)
return ret, typeText, err
}
}
38 changes: 34 additions & 4 deletions cmd/corim-store/cmd/corim.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import (
var corimCmd = &cobra.Command{
Use: "corim",
Short: "CoRIM-related operations.",
Long: `CoRIM-related operations.

Subcommands allow adding and removing CoRIMs from the store. See help for the
individual subcommands.
`,

Run: func(cmd *cobra.Command, args []string) {
CheckErr(runAddCommand(cmd, args))
Expand All @@ -23,7 +28,13 @@ var corimCmd = &cobra.Command{
var addCmd = &cobra.Command{
Use: "add PATH [PATH ...]",
Short: "Add a CoRIM's contents to the store.",
Args: cobra.MinimumNArgs(1),
Long: `Add a CoRIM's contents to the store.

The specified CoRIM(s) will be parsed and added as a "manifest" to the store.
Currently, CoRIMs containing only CoMID tags, and CoMID tags containing only
reference-triple's, endorsed-triple's, and attest-key-triple's, are supported.
`,
Args: cobra.MinimumNArgs(1),

Run: func(cmd *cobra.Command, args []string) {
CheckErr(runAddCommand(cmd, args))
Expand All @@ -33,7 +44,13 @@ var addCmd = &cobra.Command{
var deleteCmd = &cobra.Command{
Use: "delete PATH_OR_MANIFEST_ID",
Short: "Delete data associated with the specified CoRIM or manifest ID.",
Args: cobra.MinimumNArgs(1),
Long: `Delete data associated with the specified CoRIM or manifest ID.

You can specify the manifest ID directly, or you can specify a path to a CoRIM, in which case
the ID will be extracted from it (note: either way, the matching is done based on the ID so
the CoRIM specified does not literally have to be the same file that was previously added).
`,
Args: cobra.MinimumNArgs(1),

Run: func(cmd *cobra.Command, args []string) {
CheckErr(runDeleteCommand(cmd, args))
Expand All @@ -43,7 +60,13 @@ var deleteCmd = &cobra.Command{
var dumpCmd = &cobra.Command{
Use: "dump MANIFEST_ID",
Short: "Write a CoRIM containing data associated with the specified manifest ID.",
Args: cobra.ExactArgs(1),
Long: `Write a CoRIM containing data associated with the specified manifest ID.

This produces an unsigned CoRIM token containing the data associated with the
specified MANIFEST_ID. It is a way to easily "retrieve" a previously added
CoRIM.
`,
Args: cobra.ExactArgs(1),

Run: func(cmd *cobra.Command, args []string) {
CheckErr(runDumpCommand(cmd, args))
Expand All @@ -56,6 +79,11 @@ func runAddCommand(cmd *cobra.Command, args []string) error {
return err
}

activate, err := cmd.Flags().GetBool("activate")
if err != nil {
return err
}

store, err := store.Open(context.Background(), cliConfig.Store())
if err != nil {
return err
Expand All @@ -68,7 +96,7 @@ func runAddCommand(cmd *cobra.Command, args []string) error {
return fmt.Errorf("error reading %s: %w", path, err)
}

if err := store.AddBytes(bytes, label); err != nil {
if err := store.AddBytes(bytes, label, activate); err != nil {
return fmt.Errorf("error adding %s: %w", path, err)
}

Expand Down Expand Up @@ -191,6 +219,8 @@ func init() {
corimCmd.PersistentFlags().StringP("label", "l", "",
"Label that will be applied to the manifest in the store.")

addCmd.Flags().BoolP("activate", "a", false, "Activate added triples.")

deleteCmd.Flags().BoolP("corim", "C", false,
"force interpretation the positional argument as a path to CoRIM")

Expand Down
Loading
Loading