...

Source file src/edge-infra.dev/pkg/sds/emergencyaccess/msgdata/request.go

Documentation: edge-infra.dev/pkg/sds/emergencyaccess/msgdata

     1  package msgdata
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/google/shlex"
     9  
    10  	"edge-infra.dev/pkg/sds/emergencyaccess/eaconst"
    11  )
    12  
    13  // Represents all supported operator intervention request messages.
    14  // A request message contains request data to be interpreted by
    15  // a target remote agent, and a bag of attributes.
    16  type Request interface {
    17  	// Add an attribute to the request message. If the key is already set to any
    18  	// value the new value will have no affect and the old value will be used
    19  	AddAttribute(key, val string)
    20  	// Returns a deep copy of the request message attributes.
    21  	Attributes() map[string]string
    22  	// Returns the "command" value in the request that requires authorization.
    23  	// For a command this would be the first word of the string.
    24  	// For an executable this would be the name of the executable file.
    25  	CommandToBeAuthorized() string
    26  	// Returns a JSON representation of the request data. The underlying structure
    27  	// will depend on the message version and request type.
    28  	Data() ([]byte, error)
    29  	// Returns the request message type ["command", "executable"]
    30  	RequestType() eaconst.RequestType
    31  }
    32  
    33  // Represents a request that is expected to contain artifact contents.
    34  type Artifactor interface {
    35  	// Update the "contents" field of the request data. This value is expected
    36  	// to be a base64-encoded string. If the field is already populated, it
    37  	// will be overwritten.
    38  	WriteContents(contents string)
    39  }
    40  
    41  // Takes a request data (in JSON form) and attributes and returns a structured
    42  // request in the form of the Request interface. Attributes must contain the
    43  // request message version and type in order to accurately parse the data.
    44  func NewRequest(data []byte, attributes map[string]string) (Request, error) {
    45  	version, ok := attributes[eaconst.VersionKey]
    46  	if !ok {
    47  		return nil, errors.New("failed to find version attribute")
    48  	}
    49  	requestType, ok := attributes[eaconst.RequestTypeKey]
    50  	if !ok {
    51  		return nil, errors.New("failed to find requestType attribute")
    52  	}
    53  	switch version {
    54  	case string(eaconst.MessageVersion1_0):
    55  		return assembleV1_0Request(data, attributes)
    56  	case string(eaconst.MessageVersion2_0):
    57  		if requestType == string(eaconst.Command) {
    58  			return assembleV2_0CommandRequest(data, attributes)
    59  		}
    60  		if requestType == string(eaconst.Executable) {
    61  			return assembleV2_0ExecutableRequest(data, attributes)
    62  		}
    63  		return nil, fmt.Errorf("received version 2.0 message with unsupported request type %q", requestType)
    64  	default:
    65  		return nil, fmt.Errorf("received unsupported request message version %q", version)
    66  	}
    67  }
    68  
    69  func determineRequestType(payload string) eaconst.RequestType {
    70  	switch {
    71  	case strings.HasPrefix(payload, eaconst.ExecutableIdentifier):
    72  		return eaconst.Executable
    73  	default:
    74  		return eaconst.Command
    75  	}
    76  }
    77  
    78  func deepCopyMap(m map[string]string) map[string]string {
    79  	newMap := make(map[string]string)
    80  	for k, v := range m {
    81  		newMap[k] = v
    82  	}
    83  	return newMap
    84  }
    85  
    86  func stripExecutablePrefix(s string) string {
    87  	after, _ := strings.CutPrefix(s, eaconst.ExecutableIdentifier)
    88  	return after
    89  }
    90  
    91  func validateAttributes(attributes map[string]string, expectedVersion, expectedType string) error {
    92  	var err error
    93  	if actualVersion := attributes[eaconst.VersionKey]; actualVersion != expectedVersion {
    94  		err = errors.Join(err, fmt.Errorf("version attribute %q should be %q", actualVersion, expectedVersion))
    95  	}
    96  	if actualType := attributes[eaconst.RequestTypeKey]; actualType != expectedType {
    97  		err = errors.Join(err, fmt.Errorf("type attribute %q should be %q", actualType, expectedType))
    98  	}
    99  	return err
   100  }
   101  
   102  func parsePayload(payload string) (name string, args []string, err error) {
   103  	if payload == "" {
   104  		return "", nil, errors.New("payload cannot be empty")
   105  	}
   106  	cmdAndArgs, err := shlex.Split(payload)
   107  	if err != nil {
   108  		return "", nil, fmt.Errorf("failed to parse payload: %w", err)
   109  	}
   110  	return cmdAndArgs[0], cmdAndArgs[1:], nil
   111  }
   112  

View as plain text