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
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,24 @@ Ratsd currently supports the Trusted Secure Module `tsm` attester. You can speci
```bash
curl -X POST http://localhost:8895/ratsd/chares -H "Content-type: application/vnd.veraison.chares+json" -d '{"nonce": "TUlEQk5IMjhpaW9pc2pQeXh4eHh4eHh4eHh4eHh4eHhNSURCTkgyOGlpb2lzalB5eHh4eHh4eHh4eHh4eHh4eA", tsm-report:{"privilege_level": "$level"}}' # Replace $level with a number from 0 to 3
```

## Get evidence from the selected attester only

If more than one leaf attesters present, ratsd adds the evidence generated by all attesters to the response of `/ratsd/chares`. To limit the output to the selected attester, add `list-options: selected` to config.yaml,
then specify the name of each attester along with the associated options in `attester-selection`. If the user does not wish to specify the attester-specific option, "$attester_name": "null" should be specified. The following is an example of the request:
```
"nonce": "base64urlencoded",

"attester-selection": {
"attester-id-1": {
"param11name": "param11value",
"param12name": "param12value"
},
"attester-id-2": {
"param21name": "param21value"
},
"attester-id-3": null
}
```

If `list-options` is not set, or if it's set to `all` in config.yaml, ratsd populates the EAT with CMW from all available attesters as the default behavior.
40 changes: 33 additions & 7 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ const (
type Server struct {
logger *zap.SugaredLogger
manager plugin.IManager
options string
}

func NewServer(logger *zap.SugaredLogger, manager plugin.IManager) *Server {
func NewServer(logger *zap.SugaredLogger, manager plugin.IManager, options string) *Server {
return &Server{
logger: logger,
manager: manager,
options: options,
}
}

Expand Down Expand Up @@ -124,19 +126,28 @@ func (s *Server) RatsdChares(w http.ResponseWriter, r *http.Request, param Ratsd
Status: http.StatusBadRequest,
}
s.reportProblem(w, p)
fmt.Println(errMsg)
return
}
} else if s.options == "selected" {
errMsg := "attester-selection must contain at least one attester"
p := &problems.DefaultProblem{
Type: string(TagGithubCom2024VeraisonratsdErrorInvalidrequest),
Title: string(InvalidRequest),
Detail: errMsg,
Status: http.StatusBadRequest,
}
s.reportProblem(w, p)
return
}

for _, pn := range pl {
getCMW := func (pn string) bool {
attester, err := s.manager.LookupByName(pn)
if err != nil {
errMsg := fmt.Sprintf(
"failed to get handle from %s: %s", pn, err.Error())
p := problems.NewDetailedProblem(http.StatusInternalServerError, errMsg)
s.reportProblem(w, p)
return
return false
}

formatOut := attester.GetSupportedFormats()
Expand All @@ -145,11 +156,11 @@ func (s *Server) RatsdChares(w http.ResponseWriter, r *http.Request, param Ratsd
pn, formatOut.Status.Error)
p := problems.NewDetailedProblem(http.StatusInternalServerError, errMsg)
s.reportProblem(w, p)
return
return false
}

params, hasOption := options[pn]
if !hasOption {
if !hasOption || string(params) == "null" {
params = json.RawMessage{}
}

Expand All @@ -166,11 +177,26 @@ func (s *Server) RatsdChares(w http.ResponseWriter, r *http.Request, param Ratsd
"failed to get attestation report from %s: %s ", pn, out.Status.Error)
p := problems.NewDetailedProblem(http.StatusInternalServerError, errMsg)
s.reportProblem(w, p)
return
return false
}

c := cmw.NewMonad(in.ContentType, out.Evidence)
collection.AddCollectionItem(pn, c)
return true
}

if s.options == "all" {
for _, pn := range pl {
if !getCMW(pn) {
return
}
}
} else {
for pn, _ := range options {
if !getCMW(pn) {
return
}
}
}

serialized, err := collection.MarshalJSON()
Expand Down
19 changes: 15 additions & 4 deletions api/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,12 @@ func TestRatsdChares_invalid_body(t *testing.T) {
params.Accept = &param
logger := log.Named("test")

pluginList := []string{"mock-tsm"}
pluginList := []string{"mock-tsm", "tsm-report"}
dm := mock_deps.NewMockIManager(ctrl)
dm.EXPECT().GetPluginList().Return(pluginList).AnyTimes()
dm.EXPECT().LookupByName("mock-tsm").Return(mocktsm.GetPlugin(), nil).AnyTimes()

s := NewServer(logger, dm)
s := NewServer(logger, dm, "selected")
tests := []struct{ name, body, msg string }{
{"missing nonce", `{"noncee": "MIDBNH28iioisjPy"}`,
"fail to retrieve nonce from the request"},
Expand All @@ -103,6 +103,8 @@ func TestRatsdChares_invalid_body(t *testing.T) {
"attester-selection": "attester-slection"}`, validNonce),
"failed to parse attester selection: json: cannot unmarshal string into" +
` Go value of type map[string]json.RawMessage`},
{"no attester specified in selected mode", fmt.Sprintf(`{"nonce": "%s"}`, validNonce),
"attester-selection must contain at least one attester"},
}

for _, tt := range tests {
Expand Down Expand Up @@ -146,7 +148,7 @@ func TestRatsdChares_valid_request_no_available_attester(t *testing.T) {
dm := mock_deps.NewMockIManager(ctrl)
dm.EXPECT().GetPluginList().Return(pluginList)

s := NewServer(logger, dm)
s := NewServer(logger, dm, "all")
w := httptest.NewRecorder()
rs := fmt.Sprintf(`{"nonce": "%s"}`, validNonce)
rb := strings.NewReader(rs)
Expand Down Expand Up @@ -182,7 +184,7 @@ func TestRatsdChares_valid_request(t *testing.T) {
dm.EXPECT().GetPluginList().Return(pluginList).AnyTimes()
dm.EXPECT().LookupByName("mock-tsm").Return(mocktsm.GetPlugin(), nil).AnyTimes()

s := NewServer(logger, dm)
s := NewServer(logger, dm, "all")
realNonce, _ := base64.RawURLEncoding.DecodeString(validNonce)

tests := []struct {
Expand All @@ -194,6 +196,15 @@ func TestRatsdChares_valid_request(t *testing.T) {
fmt.Sprintf(`{"nonce": "%s"}`, validNonce),
0,
},
{
"with null as params",
fmt.Sprintf(`{"nonce": "%s",
"attester-selection":{
"mock-tsm": null
}
}`, validNonce),
0,
},
{
"with params",
fmt.Sprintf(`{"nonce": "%s",
Expand Down
13 changes: 7 additions & 6 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ var (
)

type cfg struct {
ListenAddr string `mapstructure:"listen-addr" valid:"dialstring"`
Protocol string `mapstructure:"protocol" valid:"in(http|https)"`
Cert string `mapstructure:"cert" config:"zerodefault"`
CertKey string `mapstructure:"cert-key" config:"zerodefault"`
PluginDir string `mapstructure:"plugin-dir" config:"zerodefault"`
ListenAddr string `mapstructure:"listen-addr" valid:"dialstring"`
Protocol string `mapstructure:"protocol" valid:"in(http|https)"`
Cert string `mapstructure:"cert" config:"zerodefault"`
CertKey string `mapstructure:"cert-key" config:"zerodefault"`
PluginDir string `mapstructure:"plugin-dir" config:"zerodefault"`
ListOptions string `mapstructure:"list-options" valid:"in(all|selected)"`
}

func (o cfg) Validate() error {
Expand Down Expand Up @@ -84,7 +85,7 @@ func main() {

log.Info("Loaded sub-attesters:", pluginManager.GetPluginList())

svr := api.NewServer(log.Named("api"), pluginManager)
svr := api.NewServer(log.Named("api"), pluginManager, cfg.ListOptions)
r := http.NewServeMux()
options := api.StdHTTPServerOptions{
BaseRouter: r,
Expand Down
1 change: 1 addition & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ ratsd:
listen-addr: 0.0.0.0:8895
protocol: http
plugin-dir: attesters/bin
list-options: all
Copy link
Contributor

Choose a reason for hiding this comment

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

I am probably missing something, but I don’t fully grasp why this should be configuration-defined behaviour, rather than being completely driven by the API user.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was trying to implement attester discovery with such config option. In your proposal, you mentioned the user have to specify at least "attester-id": null to include the evidence from attester-id. The option was added to make ratsd behave the same way as the current implementation without this PR. But I'd agree the user may want to perform the discovery first before the query and without restarting ratsd.

Copy link
Contributor

Choose a reason for hiding this comment

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

I was trying to implement attester discovery with such config option. In your proposal, you mentioned the user have to specify at least "attester-id": null to include the evidence from attester-id.

Oh, thanks. I forgot I had written that thing 😓

The option was added to make ratsd behave the same way as the current implementation without this PR. But I'd agree the user may want to perform the discovery first before the query and without restarting ratsd.

Perhaps we could leave it in as a temp backstop and remove it when we have implemented proper discovery.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@thomas-fossati I've updated the README to include attester selection. The decision to remove such option and refine the default behavior should be determined in the Attester Discovery PR IMO.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@thomas-fossati API discovery is added in #51