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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@
build/
**/coverage.txt
.DS_Store
CLAUDE.md
54 changes: 37 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ ReceptorSDK is an open source Trustero project and contributions are welcome.

ReceptorSDK is periodically refreshed to reflect the newest additions to the Trustero API. Users of the SDK are advised to track the latest releases rather closely to ensure proper function in the unlikely event of an incompatible change to a Trustero API.


## Usage

The developer needs to implement the [Receptor interface](https://pkg.go.dev/github.com/trustero/api/go/receptor_sdk#Receptor) in their project to create a Receptor.
Expand All @@ -27,59 +26,79 @@ The developer needs to implement the [Receptor interface](https://pkg.go.dev/git
package main

import (
"github.com/trustero/api/go/receptor_sdk"
"github.com/trustero/api/go/receptor_sdk/cmd"
"github.com/trustero/api/go/receptor_v1"
"github.com/trustero/api/go/receptor_sdk"
"github.com/trustero/api/go/receptor_sdk/cmd"
"github.com/trustero/api/go/receptor_v1"
)

const (
receptorName = "trr-custom"
serviceName = "Custom Service"
receptorName = "trr-custom"
serviceName = "Custom Service"
)

type Receptor struct {
// YOUR CODE HERE
}

func (r *Receptor) GetReceptorType() string {
return receptorName
return receptorName
}

func (r *Receptor) GetKnownServices() []string {
return []string{serviceName}
return []string{serviceName}
}

func (r *Receptor) GetCredentialObj() (credentialObj interface{}) {
return r
return r
}

func (r *Receptor) GetInstructions() (string, error) {
return
return
}

func (r *Receptor) GetLogo() (string, error) {
return
return
}

func (r *Receptor) GetConfigObj(credentials interface{}) (err error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

To stay consistent with the interface definition, should we update this signature to GetConfigObj(credentials interface{}) (configObj interface{})?

// YOUR CODE HERE
return
}

func (r *Receptor) GetEvidenceInfo(credentials interface{}) (evidences []*receptor_sdk.Evidence) {
// YOUR CODE HERE
}

func (r *Receptor) Configure(credentials interface{}) (config *receptor_v1.ReceptorConfiguration, err error) {
// YOUR CODE HERE
}

func (r *Receptor) Verify(credentials interface{}) (ok bool, err error) {
// YOUR CODE HERE
return
return
}

func (r *Receptor) Discover(credentials interface{}) (svcs []*receptor_v1.ServiceEntity, err error) {
// YOUR CODE HERE
return
return
}

func (r *Receptor) Report(credentials interface{}) (evidences []*receptor_sdk.Evidence, err error) {
// YOUR CODE HERE
return
return
}

func (r *Receptor) ReportBatch(credentials interface{}, evidenceChan chan []*receptor_sdk.Evidence) {

defer func() {
close(evidenceChan)
}()
// YOUR CODE HERE
return
}

func main() {
cmd.Execute(&Receptor{})
cmd.Execute(&Receptor{})
}
```

Expand All @@ -98,7 +117,7 @@ If a client needs to be generated for debugging or other use cases, it may be do
rc = client.ServerConn.GetReceptorClient()

if receptorInfo, err = getReceptorConfig(rc); err != nil {
log.Error().Err(err)
log.Error().Err(err)
}

credentialString = receptorInfo.GetCredential()
Expand All @@ -113,7 +132,8 @@ You can compile the Receptor code into a binary or run the main file directly.
If you run the main file directly, your command should look something like this:

```
go run main.go scan dryrun --find-evidence
go run main.go scan dryrun --find-evidence --credentials <base64 encoded string of credential object>
```

This command will run the Verify, Discover, and Report functions that you wrote and print their output to the console. You should be able to see the final Evidences that are generated by the receptor.

2 changes: 1 addition & 1 deletion go/examples/gitlab_receptor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func (r *Receptor) Configure(credentials interface{}) (config *receptor_v1.Recep

// This will return Config json when the receptor is asked for
// config
func (r *Receptor) GetConfigObj() (config interface{}) {
func (r *Receptor) GetConfigObj(credentials interface{}) (config interface{}) {
return nil
}

Expand Down
38 changes: 34 additions & 4 deletions go/receptor_sdk/client/apiclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"strconv"
"time"

Expand All @@ -18,6 +19,7 @@ import (
"github.com/rs/zerolog/log"
"golang.org/x/oauth2"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials"
)

Expand Down Expand Up @@ -55,22 +57,50 @@ func InitGRPCClient(cert, override string) {
// Dial makes a GRPC connection to Trustero GRPC service. A Trustero JWT bearer token must be provided.
func (sc *ServerConnection) Dial(token, host string, port int) (err error) {
// Dial options
grpcCred := oauth.NewOauthAccess(&oauth2.Token{AccessToken: token})
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
grpcCred := oauth.TokenSource{TokenSource: ts}
opts := []grpc.DialOption{
grpc.WithUnaryInterceptor(logUnaryCall),
grpc.WithBlock(),
grpc.WithPerRPCCredentials(grpcCred),
sc.TlsDialOption,
grpc.WithStreamInterceptor(logStreamCall),
grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(500 * 1024 * 1024)),
grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(2048 * 1024 * 1024)),
}

// Connect to local server
addr := host + ":" + strconv.Itoa(port)
sc.Connection, err = grpc.Dial(addr, opts...)
sc.Connection, err = grpc.NewClient(addr, opts...)
return
}

// DialAndWait dials and waits for the connection to reach Ready state or timeout.
// This preserves old blocking behavior safely, avoiding deprecated WithBlock.
func (sc *ServerConnection) DialAndWait(token, host string, port int, timeout time.Duration) error {
if err := sc.Dial(token, host, port); err != nil {
return err
}
if sc.Connection == nil {
return errors.New("grpc connection is nil after dial")
}

ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

for {
state := sc.Connection.GetState()
if state == connectivity.Ready {
return nil
}
sc.Connection.Connect()
if ok := sc.Connection.WaitForStateChange(ctx, state); !ok {
if ctx.Err() != nil {
return ctx.Err()
}
return errors.New("grpc wait for state change failed")
}
}
}

// CloseClient closes a previously dialed Trustero GRPC connection.
func (sc *ServerConnection) CloseClient() error {
if sc.Connection != nil {
Expand Down
70 changes: 63 additions & 7 deletions go/receptor_sdk/cmd/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,75 @@ package cmd

import (
"context"
"encoding/json"

"github.com/spf13/cobra"
"github.com/trustero/api/go/receptor_sdk"
"github.com/trustero/api/go/receptor_v1"
)

func configure(rc receptor_v1.ReceptorClient, credentials interface{}) (err error) {
const (
configureUse = "configure <trustero_access_token>|dryrun"
configureShort = "Configure service provider account information"
configureLong = `
Configure service provider account information. Configure command
decodes the base64 URL encoded credentials from the '--credentials' command
line flag, gets the configuration for the service provider account and sends to Trustero. If 'dryrun' is specified instead of a
Trustero access token, the configure command will not report the results to
Trustero and instead print the configuration results to console.`
)

type confi struct {
cmd *cobra.Command
}

func (v *confi) getCommand() *cobra.Command {
return v.cmd
}

// Receptor configuration setup
var config *receptor_v1.ReceptorConfiguration
if config, err = receptorImpl.Configure(credentials); err != nil || config == nil {
return
func (v *confi) setup() {
v.cmd = &cobra.Command{
Use: configureUse,
Short: configureShort,
Long: configureLong,
Args: cobra.MinimumNArgs(1),
PreRun: grpcPreRun,
RunE: configure,
PostRun: grpcPostRun,
SilenceUsage: true,
}
v.cmd.FParseErrWhitelist.UnknownFlags = true

addGrpcFlags(v.cmd)
}

// Cobra executes this function on verify command.
func configure(_ *cobra.Command, args []string) (err error) {
// Run receptor's Verify function and report results to Trustero
err = invokeWithContext(args[0],
func(rc receptor_v1.ReceptorClient, credentials interface{}, config interface{}) (err error) {
// Send the config back to Trustero if there is additional config
if config != nil {
jsonBytes, err := json.Marshal(receptorImpl.GetConfigObj(credentials))
if err != nil {
return err
}
if receptor_sdk.NoSave {
println(string(jsonBytes))

// print to console instead of sending to Trustero when 'dryrun' is specified

return nil

// Send receptor configuration to Trustero
_, err = rc.SetConfiguration(context.Background(), config)
} else {
Comment thread
sen-trustero marked this conversation as resolved.
}
_, err = rc.SetConfiguration(context.Background(), &receptor_v1.ReceptorConfiguration{
ReceptorObjectId: receptor_sdk.ReceptorId,
Config: string(jsonBytes),
ModelId: receptorImpl.GetReceptorType(),
})
}
return
})
return
}
1 change: 0 additions & 1 deletion go/receptor_sdk/cmd/mock_receptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ func toYaml(v interface{}) (yamld string, err error) {
return
}

// Verified implements a mock [receptor_v1.Receptor.GetConfiguration] method for testing.
func (rc *mockReceptorClient) SetConfiguration(ctx context.Context, c *receptor.ReceptorConfiguration, opts ...grpc.CallOption) (e *emptypb.Empty, err error) {
println(header + "SetConfiguration(...)")
var yamld string
Expand Down
16 changes: 13 additions & 3 deletions go/receptor_sdk/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"encoding/base64"
"encoding/json"
"strings"
"time"

"github.com/rs/zerolog/log"
"golang.org/x/net/context"
Expand Down Expand Up @@ -46,6 +47,7 @@ var cmds = map[string]command{
"evidenceinfo": &evi{},
"logo": &logor{},
"instructions": &instruct{},
"configure": &confi{},
}

// Execute is the entry point into the CLI framework. Receptor author implements the [receptor_sdk.Receptor]
Expand Down Expand Up @@ -106,6 +108,7 @@ func addGrpcFlags(cmd *cobra.Command) {
addStrFlag(cmd, &receptor_sdk.CredentialsBase64URL, "credentials", "", "", "Base64 URL encoded service provider credential")
addStrFlag(cmd, &receptor_sdk.ConfigBase64URL, "config", "", "", "Base64 URL encoded receptor configuration")
addStrFlag(cmd, &receptor_sdk.DiscoveryId, "discovery-id", "", "", "Trustero discovery identifier")
addIntFlag(cmd, &receptor_sdk.ConnectTimeout, "connect-timeout", "", 10, "Timeout in seconds to wait for GRPC connection readiness")

}

Expand Down Expand Up @@ -240,9 +243,9 @@ func invokeWithContext(token string, run commandInContext) (err error) {

// Unmarshal json string config
if len(configStr) > 0 && configStr != "{}" {
configObj, err = unmarshalConfig(configStr, receptorImpl.GetConfigObj())
configObj, err = unmarshalConfig(configStr, receptorImpl.GetConfigObj(credentialObj))
} else {
configObj = receptorImpl.GetConfigObj()
configObj = receptorImpl.GetConfigObj(credentialObj)
}

// Invoke receptor's method
Expand All @@ -264,7 +267,14 @@ func getReceptorClient(token string) (rc receptor.ReceptorClient, err error) {
rc = &mockReceptorClient{}
} else {
// Connect to Trustero grpc server
if err = client.ServerConn.Dial(token, receptor_sdk.Host, receptor_sdk.Port); err != nil {
// timeout range check: minimum 10 second, maximum 60 seconds, default 10 seconds
timeout := time.Duration(receptor_sdk.ConnectTimeout) * time.Second
if timeout < 10 {
timeout = 10 * time.Second
} else if timeout > 60*time.Second {
timeout = 60 * time.Second
}
if err = client.ServerConn.DialAndWait(token, receptor_sdk.Host, receptor_sdk.Port, timeout); err != nil {
return
}
// Get grpc client
Expand Down
2 changes: 1 addition & 1 deletion go/receptor_sdk/cmd/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func scan(_ *cobra.Command, args []string) (err error) {
}
//Send the config back to Trustero if there is additional config
if config != nil {
jsonBytes, err := json.Marshal(receptorImpl.GetConfigObj())
jsonBytes, err := json.Marshal(receptorImpl.GetConfigObj(credentials))
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion go/receptor_sdk/cmd/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func verify(_ *cobra.Command, args []string) (err error) {

// Send the config back to Trustero if there is additional config
if config != nil {
jsonBytes, err := json.Marshal(receptorImpl.GetConfigObj())
jsonBytes, err := json.Marshal(receptorImpl.GetConfigObj(credentials))
if err != nil {
return err
}
Expand Down
5 changes: 4 additions & 1 deletion go/receptor_sdk/receptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var (
ReceptorId string // Trustero's persistent record ID of a record holding a receptor's service provider credentials.
ConfigBase64URL string // Receptor configuration as a base64 URL encoded json string.
DiscoveryId string // Trustero discovery identifier
ConnectTimeout int // Timeout in seconds to wait for GRPC connection readiness
)

// Receptor is the main interface for the Receptor implementor-facing API.
Expand Down Expand Up @@ -60,7 +61,9 @@ type Receptor interface {
GetCredentialObj() (credentialObj interface{})

// GetConfigObj returns an instance of a Config object
GetConfigObj() (configObj interface{})
// used for additional configuration from users when the default credentials are not sufficient for the receptor
// to collect evidence and needs service provider account specific configuration.
GetConfigObj(credentials interface{}) (configObj interface{})

// GetConfigObjDesc returns an instance of struct that represents a json for the config object to be rendered
// in the receptor config modal
Expand Down
2 changes: 1 addition & 1 deletion go/receptor_v1/receptor_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading