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

Cache trigger secrets for the duration of request #585

Closed
Closed
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
42 changes: 40 additions & 2 deletions pkg/interceptors/interceptors.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ limitations under the License.
package interceptors

import (
"context"
"net/http"
"path"

triggersv1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -29,7 +31,40 @@ type Interceptor interface {
ExecuteTrigger(req *http.Request) (*http.Response, error)
}

func GetSecretToken(cs kubernetes.Interface, sr *triggersv1.SecretRef, eventListenerNamespace string) ([]byte, error) {
const requestCacheKey = "interceptors.RequestCache"

// WithCache clones the given request and sets the request context to include a cache.
// This allows us to cache results from expensive operations and perform them just once
// per each trigger.
//
// Each request should have its own cache, and those caches should expire once the request
// is processed. For this reason, it's appropriate to store the cache on the request
// context.
func WithCache(req *http.Request) *http.Request {
return req.WithContext(context.WithValue(req.Context(), requestCacheKey, make(map[string]interface{})))
}

func getCache(req *http.Request) map[string]interface{} {
if cache, ok := req.Context().Value(requestCacheKey).(map[string]interface{}); ok {
return cache
}

return make(map[string]interface{})
}

// GetSecretToken queries Kubernetes for the given secret reference. We use this function
// to resolve secret material like Github webhook secrets, and call it once for every
// trigger that references it.
//
// As we may have many triggers that all use the same secret, we cache the secret values
// in the request cache.
func GetSecretToken(req *http.Request, cs kubernetes.Interface, sr *triggersv1.SecretRef, eventListenerNamespace string) ([]byte, error) {
cacheKey := path.Join("secret", sr.Namespace, sr.SecretName, sr.SecretKey)
cache := getCache(req)
if secretValue, ok := cache[cacheKey]; ok {
return secretValue.([]byte), nil
}

ns := sr.Namespace
if ns == "" {
ns = eventListenerNamespace
Expand All @@ -39,5 +74,8 @@ func GetSecretToken(cs kubernetes.Interface, sr *triggersv1.SecretRef, eventList
return nil, err
}

return secret.Data[sr.SecretKey], nil
secretValue := secret.Data[sr.SecretKey]
cache[cacheKey] = secret.Data[sr.SecretKey]

return secretValue, nil
}
5 changes: 5 additions & 0 deletions pkg/sink/sink.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,11 @@ func (r Sink) executeInterceptors(t *triggersv1.EventListenerTrigger, in *http.R
Header: in.Header,
Body: ioutil.NopCloser(bytes.NewBuffer(event)),
}

// We create a cache against each request, so whenever we make network calls like
// fetching kubernetes secrets, we can do so only once per request.
request = interceptors.WithCache(request)

var resp *http.Response
for _, i := range t.Interceptors {
var interceptor interceptors.Interceptor
Expand Down