Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TEP-0022: Triggers - Immutable Input Events #218

Merged
merged 3 commits into from
Nov 9, 2020

Conversation

wlynch
Copy link
Member

@wlynch wlynch commented Sep 29, 2020

This TEP proposes a change to Trigger interceptor handling to ensure
that incoming input events are not mutated through the triggering
process.

TEP Design for tektoncd/triggers#778

@tekton-robot tekton-robot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Sep 29, 2020
@wlynch
Copy link
Member Author

wlynch commented Sep 29, 2020

/cc @dibyom @bigkevmcd

@wlynch wlynch changed the title Triggers TEP: Immutable Input Events TEP-0022: Triggers - Immutable Input Events Sep 29, 2020

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
This TEP proposes a change to Trigger interceptor handling to ensure
that incoming input events are not mutated through the triggering
process.

TEP Design for tektoncd/triggers#778
@wlynch wlynch force-pushed the trigger-immutable-tep branch from 602d714 to bca6b70 Compare September 29, 2020 21:16
@bigkevmcd
Copy link
Member

What does the new Interceptor Interface look like?

// Interceptor is the interface that all interceptors implement.
//
// Implementations can return extensions, which will be made available to subsequent calls in `inputs.<interceptor name>`.
type Interceptor interface {
    ExecuteTrigger(input map[string]interface{}) (extensions map[string]interface{}, err error)
}

Webhook interceptors may no longer modify headers as part of the interceptor
chain.

If no response body is returned by the interceptor, no `interceptors.<name>`
Copy link
Member

Choose a reason for hiding this comment

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

This might complicate bindings, if you have several interceptors, and the values go in as...

{
  "interceptors": {
    "github-push-repo-a-interceptor": {
      "short_sha": "bca6b70"
    },
    "github-push-repo-b-interceptor": {
      "short_sha": "3e29f82b"
    }
  }
}

Getting a binding that can build and tag with the short_sha in a generic form will be tricky.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, what if this is a flat list i.e we do not key by interceptor name (we could call this field extensions or something?)

The main drawback I see is if two interceptors both produce the same field. In that case, maybe we follow a simple rule -- the last interceptor in the chain can overwrite the previous one? (Another alternative might be to just throw an error in this case)

Copy link
Member

Choose a reason for hiding this comment

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

The scope for interceptors to do this is fairly small, there's no "global" interceptor, so if it's happening, it's happening within a fairly small stanza in your YAML.

To me, this suggests a low likelihood of having to debug a mysterious value, and with "last-one-wins", it should be fairly easy to debug (it's not like we're subject to the vagaries of hash ordering).

Copy link
Member Author

Choose a reason for hiding this comment

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

SGTM.

field) we will include the entire input event in a reserved body field named
`input`. This field will not be mutated throughout trigger processing, so that
inteceptors and trigger bindings can rely on assumptions on the incoming event
type. Incoming event data will be stored within `input` as `body` and `headers`
Copy link
Member

Choose a reason for hiding this comment

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

Will users have to now use input.body or input.header for bindings/interceptors? If so, can we consider just keeping the body and headers as is today but make them immutable while adding a new modifiable field (interceptors) as described below?

Copy link
Member

Choose a reason for hiding this comment

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

Looking at it again, maybe the interface becomes:

interface Interceptor {
    ExecuteTrigger(headers http.Header, body map[string]interface{}) (extensions map[string]interface{}, err error)
}

The Webhook interceptor would just do a POST, transferring the headers, encoding the body, and being expected to return an optional JSON body (which would become extensions) ?

Copy link
Member Author

@wlynch wlynch Sep 30, 2020

Choose a reason for hiding this comment

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

I think we'll want to encapsulate this into Request/Response types, following what is proposed in the pluggable interceptor proposal by @dibyom. This will make it easier to add additional fields into the request/response objects as needed (i.e. the additional trigger fields proposed by Dibyo):

interface Interceptor {
    ExecuteTrigger(req InterceptorRequest) (InterceptorResponse, error)
}

type InterceptorRequest struct {
  Headers http.Headers
  Body json.RawMessage
  Interceptors map[string]interface{}
}

type InterceptorResponse struct {
  Body json.RawMessage
}

Webhook interceptors may no longer modify headers as part of the interceptor
chain.

If no response body is returned by the interceptor, no `interceptors.<name>`
Copy link
Member

Choose a reason for hiding this comment

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

Yeah, what if this is a flat list i.e we do not key by interceptor name (we could call this field extensions or something?)

The main drawback I see is if two interceptors both produce the same field. In that case, maybe we follow a simple rule -- the last interceptor in the chain can overwrite the previous one? (Another alternative might be to just throw an error in this case)

Webhook interceptors may no longer modify headers as part of the interceptor
chain.

If no response body is returned by the interceptor, no `interceptors.<name>`
Copy link
Member Author

Choose a reason for hiding this comment

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

SGTM.

payloads.

Any response received from interceptors will be included in following paylods
under a new `interceptors` field, keyed by interceptor name for uniqueness.
Copy link
Member Author

Choose a reason for hiding this comment

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

Any thoughts on naming?

  • interceptors
  • extensions
  • plugins
  • ???

Copy link
Member

Choose a reason for hiding this comment

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

very slight preference towards extensions but open to all of them!

field) we will include the entire input event in a reserved body field named
`input`. This field will not be mutated throughout trigger processing, so that
inteceptors and trigger bindings can rely on assumptions on the incoming event
type. Incoming event data will be stored within `input` as `body` and `headers`
Copy link
Member Author

@wlynch wlynch Sep 30, 2020

Choose a reason for hiding this comment

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

I think we'll want to encapsulate this into Request/Response types, following what is proposed in the pluggable interceptor proposal by @dibyom. This will make it easier to add additional fields into the request/response objects as needed (i.e. the additional trigger fields proposed by Dibyo):

interface Interceptor {
    ExecuteTrigger(req InterceptorRequest) (InterceptorResponse, error)
}

type InterceptorRequest struct {
  Headers http.Headers
  Body json.RawMessage
  Interceptors map[string]interface{}
}

type InterceptorResponse struct {
  Body json.RawMessage
}

This primarily removes the proposed `input` field in favor of direct `headers`
and `body` field to reduce the impact this would have on existing users.

Other minor changes:
- Make feature flag an annotation.
- Flesh out details around interface changes (highly influenced by
  Pluggable Interceptors proposal).
- Add more details around CEL Interceptor changes.
@dibyom
Copy link
Member

dibyom commented Oct 5, 2020

/approve

@tekton-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: dibyom

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@tekton-robot tekton-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Oct 5, 2020
@wlynch
Copy link
Member Author

wlynch commented Oct 5, 2020

/kind tep

@tekton-robot tekton-robot added the kind/tep Categorizes issue or PR as related to a TEP (or needs a TEP). label Oct 5, 2020
<<[UNRESOLVED wlynch ]>>
TODO based on feedback
<<[/UNRESOLVED]>>
```
Copy link
Contributor

Choose a reason for hiding this comment

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

i think it's worth generating some of these as part of the proposal, even if the alternatives you come up with aren't very appealing; if that's the case then it increase confidence in the proposal at least :D

some ideas: one alternative is always to NOT do the thing, i.e. what does it look like to try to meet the same goals but without the explicit feature? or we could make this something you opt into (kinda already the case with the feature flag, but maybe at a different level, e.g. configuring mutability in the binding?)? or maybe some kind of interceptor you add into your interceptors list that ensures the event hasn't been mutated?

(none of these are great but again i think it's worth generating and recording)

Copy link
Contributor

Choose a reason for hiding this comment

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

oh and you do have one below! "TODO based on feedback" made me think there wouldn't be any

anyway here are a few more :D

Copy link
Member Author

Choose a reason for hiding this comment

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

Done!

<<[UNRESOLVED wlynch ]>>
TODO based on feedback
<<[/UNRESOLVED]>>
```
Copy link
Contributor

Choose a reason for hiding this comment

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

one potential drawback that comes to mind: can't as easily use interceptors to filter out sensitive data? e.g. if you could imagine wanting to pass the entire body of an event to a resulting Run

i suppose you can still do this, but you need to do it by duplicating all the data into a new field?

Copy link
Member Author

Choose a reason for hiding this comment

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

ooh, great point! Added (though I don't think we want to support this).

alternatives.

Adds drawbacks / alternatives raised in review:
- Payload redaction
- What if we keep immutable behavior?
@bobcatfish
Copy link
Contributor

Discussed merging at proposed in the API working group today - seems like it's likely we can go to "implementable" quickly after that, don't seem to be any outstanding issues atm

/lgtm

@tekton-robot tekton-robot added the lgtm Indicates that a PR is ready to be merged. label Nov 9, 2020
@tekton-robot tekton-robot merged commit d938803 into tektoncd:master Nov 9, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. kind/tep Categorizes issue or PR as related to a TEP (or needs a TEP). lgtm Indicates that a PR is ready to be merged. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants