diff --git a/README.md b/README.md index 895b8b9..b9b7f1e 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/api/server.go b/api/server.go index 89a3532..2caf9e0 100644 --- a/api/server.go +++ b/api/server.go @@ -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, } } @@ -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() @@ -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{} } @@ -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() diff --git a/api/server_test.go b/api/server_test.go index 16909dc..4f56d01 100644 --- a/api/server_test.go +++ b/api/server_test.go @@ -89,12 +89,12 @@ func TestRatsdChares_invalid_body(t *testing.T) { params.Accept = ¶m 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"}, @@ -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 { @@ -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) @@ -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 { @@ -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", diff --git a/cmd/main.go b/cmd/main.go index 0084b21..fb0edfe 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -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 { @@ -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, diff --git a/config.yaml b/config.yaml index c83fb84..faec0f1 100644 --- a/config.yaml +++ b/config.yaml @@ -4,3 +4,4 @@ ratsd: listen-addr: 0.0.0.0:8895 protocol: http plugin-dir: attesters/bin + list-options: all