github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/protocol/v1/constructors.go (about)

     1  // Copyright (c) 2017-2022, R.I. Pienaar and the Choria Project contributors
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package v1
     6  
     7  import (
     8  	"context"
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"time"
    13  
    14  	"github.com/choria-io/go-choria/inter"
    15  	"github.com/choria-io/go-choria/protocol"
    16  )
    17  
    18  // NewRequest creates a choria:request:1
    19  func NewRequest(agent string, senderid string, callerid string, ttl int, requestid string, collective string) (req protocol.Request, err error) {
    20  	req = &Request{
    21  		Protocol: protocol.RequestV1,
    22  		Envelope: &RequestEnvelope{
    23  			SenderID:  senderid,
    24  			TTL:       ttl,
    25  			RequestID: requestid,
    26  			Time:      time.Now().Unix(),
    27  		},
    28  	}
    29  
    30  	req.SetCollective(collective)
    31  	req.SetAgent(agent)
    32  	req.SetCallerID(callerid)
    33  	req.SetFilter(protocol.NewFilter())
    34  
    35  	return req, nil
    36  }
    37  
    38  // NewReply creates a choria:reply:1 based on a previous Request
    39  func NewReply(request protocol.Request, certname string) (rep protocol.Reply, err error) {
    40  	if request.Version() != protocol.RequestV1 {
    41  		return nil, fmt.Errorf("cannot create a version 1 Reply from a %s request", request.Version())
    42  	}
    43  
    44  	rep = &Reply{
    45  		Protocol: protocol.ReplyV1,
    46  		Envelope: &ReplyEnvelope{
    47  			RequestID: request.RequestID(),
    48  			SenderID:  certname,
    49  			Agent:     request.Agent(),
    50  			Time:      time.Now().Unix(),
    51  		},
    52  	}
    53  
    54  	protocol.CopyFederationData(request, rep)
    55  
    56  	j, err := request.JSON()
    57  	if err != nil {
    58  		return nil, fmt.Errorf("could not turn Request %s into a JSON document: %s", request.RequestID(), err)
    59  	}
    60  
    61  	rep.SetMessage(j)
    62  
    63  	return rep, nil
    64  }
    65  
    66  // NewReplyFromSecureReply create a choria:reply:1 based on the data contained in a SecureReply
    67  func NewReplyFromSecureReply(sr protocol.SecureReply) (rep protocol.Reply, err error) {
    68  	if sr.Version() != protocol.SecureReplyV1 {
    69  		return nil, fmt.Errorf("cannot create a version 1 SecureReply from a %s SecureReply", sr.Version())
    70  	}
    71  
    72  	rep = &Reply{
    73  		Protocol: protocol.ReplyV1,
    74  		Envelope: &ReplyEnvelope{},
    75  	}
    76  
    77  	err = rep.IsValidJSON(sr.Message())
    78  	if err != nil {
    79  		return nil, fmt.Errorf("the JSON body from the SecureReply is not a valid Reply message: %s", err)
    80  	}
    81  
    82  	err = json.Unmarshal(sr.Message(), rep)
    83  	if err != nil {
    84  		return nil, fmt.Errorf("could not parse JSON data from Secure Reply: %s", err)
    85  	}
    86  
    87  	return rep, nil
    88  }
    89  
    90  // NewRequestFromSecureRequest creates a choria::request:1 based on the data contained in a SecureRequest
    91  func NewRequestFromSecureRequest(sr protocol.SecureRequest) (protocol.Request, error) {
    92  	if sr.Version() != protocol.SecureRequestV1 {
    93  		return nil, fmt.Errorf("cannot create a version 1 SecureRequest from a %s SecureRequest", sr.Version())
    94  	}
    95  
    96  	req := &Request{
    97  		Protocol: protocol.RequestV1,
    98  		Envelope: &RequestEnvelope{},
    99  	}
   100  
   101  	err := req.IsValidJSON(sr.Message())
   102  	if err != nil {
   103  		return nil, fmt.Errorf("the JSON body from the SecureRequest is not a valid Request message: %s", err)
   104  	}
   105  
   106  	err = json.Unmarshal(sr.Message(), req)
   107  	if err != nil {
   108  		return nil, fmt.Errorf("could not parse JSON data from Secure Request: %s", err)
   109  	}
   110  
   111  	return req, nil
   112  }
   113  
   114  // NewSecureReply creates a choria:secure:reply:1
   115  func NewSecureReply(reply protocol.Reply, security inter.SecurityProvider) (secure protocol.SecureReply, err error) {
   116  	if security.BackingTechnology() != inter.SecurityTechnologyX509 {
   117  		return nil, fmt.Errorf("version 1 protocol requires a x509 based security system")
   118  	}
   119  
   120  	secure = &SecureReply{
   121  		Protocol: protocol.SecureReplyV1,
   122  		security: security,
   123  	}
   124  
   125  	err = secure.SetMessage(reply)
   126  	if err != nil {
   127  		return nil, fmt.Errorf("could not set message on SecureReply structure: %s", err)
   128  	}
   129  
   130  	return secure, nil
   131  }
   132  
   133  // NewSecureReplyFromTransport creates a new choria:secure:reply:1 from the data contained in a Transport message
   134  func NewSecureReplyFromTransport(message protocol.TransportMessage, security inter.SecurityProvider, skipvalidate bool) (secure protocol.SecureReply, err error) {
   135  	if security.BackingTechnology() != inter.SecurityTechnologyX509 {
   136  		return nil, fmt.Errorf("version 1 protocol requires a x509 based security system")
   137  	}
   138  
   139  	secure = &SecureReply{
   140  		Protocol: protocol.SecureReplyV1,
   141  		security: security,
   142  	}
   143  
   144  	data, err := message.Message()
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	err = secure.IsValidJSON(data)
   150  	if err != nil {
   151  		return nil, fmt.Errorf("the JSON body from the TransportMessage is not a valid SecureReply message: %s", err)
   152  	}
   153  
   154  	err = json.Unmarshal(data, &secure)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  
   159  	if !skipvalidate {
   160  		if !secure.Valid() {
   161  			return nil, errors.New("SecureReply message created from the Transport Message is not valid")
   162  		}
   163  	}
   164  
   165  	return secure, nil
   166  }
   167  
   168  // NewSecureRequest creates a choria:secure:request:1
   169  func NewSecureRequest(request protocol.Request, security inter.SecurityProvider) (secure protocol.SecureRequest, err error) {
   170  	if security.BackingTechnology() != inter.SecurityTechnologyX509 {
   171  		return nil, fmt.Errorf("version 1 protocol requires a x509 based security system")
   172  	}
   173  
   174  	pub := []byte("insecure")
   175  
   176  	if protocol.IsSecure() && !protocol.IsRemoteSignerAgent(request.Agent()) {
   177  		pub, err = security.PublicCertBytes()
   178  		if err != nil {
   179  			// registration when doing anon tls might not have a certificate - so we allow that to go unsigned
   180  			if protocol.IsRegistrationAgent(request.Agent()) {
   181  				pub = []byte("insecure registration")
   182  			} else {
   183  				return nil, fmt.Errorf("could not retrieve Public Certificate from the security subsystem: %s", err)
   184  			}
   185  		}
   186  	}
   187  
   188  	secure = &SecureRequest{
   189  		Protocol:          protocol.SecureRequestV1,
   190  		PublicCertificate: string(pub),
   191  		security:          security,
   192  	}
   193  
   194  	err = secure.SetMessage(request)
   195  	if err != nil {
   196  		return nil, fmt.Errorf("could not set message SecureRequest structure: %s", err)
   197  	}
   198  
   199  	return secure, nil
   200  }
   201  
   202  // NewRemoteSignedSecureRequest is a NewSecureRequest that delegates the signing to a remote signer like aaasvc
   203  func NewRemoteSignedSecureRequest(ctx context.Context, request protocol.Request, security inter.SecurityProvider) (secure protocol.SecureRequest, err error) {
   204  	if security.BackingTechnology() != inter.SecurityTechnologyX509 {
   205  		return nil, fmt.Errorf("version 1 protocol requires a x509 based security system")
   206  	}
   207  
   208  	// no need for remote stuff, we don't do any signing or certs,
   209  	// additionally the service hosting the remote signing service isnt
   210  	// secured by choria protocol since at calling time the client does
   211  	// not have a cert etc, but the request expects a signed JWT so that
   212  	// provides the security of that request
   213  	if !protocol.IsSecure() || protocol.IsRemoteSignerAgent(request.Agent()) {
   214  		return NewSecureRequest(request, security)
   215  	}
   216  
   217  	reqj, err := request.JSON()
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  
   222  	secj, err := security.RemoteSignRequest(ctx, reqj)
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  
   227  	secure = &SecureRequest{
   228  		Protocol: protocol.SecureRequestV1,
   229  		security: security,
   230  	}
   231  
   232  	err = json.Unmarshal(secj, &secure)
   233  	if err != nil {
   234  		return nil, fmt.Errorf("could not parse signed request: %s", err)
   235  	}
   236  
   237  	return secure, nil
   238  }
   239  
   240  // NewSecureRequestFromTransport creates a new choria:secure:request:1 from the data contained in a Transport message
   241  func NewSecureRequestFromTransport(message protocol.TransportMessage, security inter.SecurityProvider, skipvalidate bool) (secure protocol.SecureRequest, err error) {
   242  	if security.BackingTechnology() != inter.SecurityTechnologyX509 {
   243  		return nil, fmt.Errorf("version 1 protocol requires a x509 based security system")
   244  	}
   245  
   246  	secure = &SecureRequest{
   247  		security: security,
   248  	}
   249  
   250  	data, err := message.Message()
   251  	if err != nil {
   252  		return
   253  	}
   254  
   255  	err = secure.IsValidJSON(data)
   256  	if err != nil {
   257  		return nil, fmt.Errorf("the JSON body from the TransportMessage is not a valid SecureRequest message: %s", err)
   258  	}
   259  
   260  	err = json.Unmarshal(data, &secure)
   261  	if err != nil {
   262  		return nil, err
   263  	}
   264  
   265  	if !skipvalidate {
   266  		if !secure.Valid() {
   267  			return nil, fmt.Errorf("SecureRequest message created from the Transport Message did not pass security validation")
   268  		}
   269  	}
   270  
   271  	return secure, nil
   272  }
   273  
   274  // NewTransportMessage creates a choria:transport:1
   275  func NewTransportMessage(certname string) (message protocol.TransportMessage, err error) {
   276  	message = &TransportMessage{
   277  		Protocol: protocol.TransportV1,
   278  		Headers:  &TransportHeaders{},
   279  	}
   280  
   281  	message.SetSender(certname)
   282  
   283  	return message, nil
   284  }
   285  
   286  // NewTransportFromJSON creates a new TransportMessage from JSON
   287  func NewTransportFromJSON(data []byte) (message protocol.TransportMessage, err error) {
   288  	message = &TransportMessage{
   289  		Headers: &TransportHeaders{},
   290  	}
   291  
   292  	err = message.IsValidJSON(data)
   293  	if err != nil {
   294  		return nil, err
   295  	}
   296  
   297  	err = json.Unmarshal(data, &message)
   298  	if err != nil {
   299  		return nil, err
   300  	}
   301  
   302  	return message, nil
   303  }