Skip to content

Commit

Permalink
introduce authentication/authorization at the EventListenerTrigger le…
Browse files Browse the repository at this point in the history
…vel (with default back to existing EventListener level)
  • Loading branch information
gabemontero committed Mar 30, 2020
1 parent 9e4655d commit 1f769bc
Show file tree
Hide file tree
Showing 16 changed files with 678 additions and 61 deletions.
1 change: 1 addition & 0 deletions cmd/eventlistenersink/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ func main() {
EventListenerName: sinkArgs.ElName,
EventListenerNamespace: sinkArgs.ElNamespace,
Logger: logger,
Auth: sink.DefaultAuthOverride{},
}

// Listen and serve
Expand Down
4 changes: 2 additions & 2 deletions docs/clustertriggerbindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ designed to encourage reusability clusterwide. You can reference a
ClusterTriggerBinding in any EventListener in any namespace.

<!-- FILE: examples/clustertriggerbindings/clustertriggerbinding.yaml -->

```YAML
apiVersion: triggers.tekton.dev/v1alpha1
kind: ClusterTriggerBinding
Expand All @@ -28,14 +27,14 @@ spec:
value: $(header.Content-Type)
```
You can specify multiple ClusterTriggerBindings in a Trigger. You can use a
ClusterTriggerBinding in multiple Triggers.
In case of using a ClusterTriggerBinding, the `Binding` kind should be added.
The default kind is TriggerBinding which represents a namespaced TriggerBinding.

<!-- FILE: examples/eventlisteners/eventlistener-clustertriggerbinding.yaml -->

```YAML
---
apiVersion: triggers.tekton.dev/v1alpha1
Expand All @@ -54,3 +53,4 @@ spec:
template:
name: pipeline-template
```

138 changes: 111 additions & 27 deletions docs/eventlisteners.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,74 @@ Tekton resources. In addition, EventListeners allow lightweight event processing
using [Event Interceptors](#Interceptors).

- [Syntax](#syntax)
- [ServiceAccountName](#serviceAccountName)
- [Triggers](#triggers)
- [Interceptors](#Interceptors)
- [ServiceAccountName](#serviceAccountName)
- [Logging](#logging)
- [Labels](#labels)
- [Examples](#examples)

## Multi-Tenant Concerns

The EventListener is effectively an additional form of client into Tekton, versus what
example usage via `kubectl` or `tkn` which you have seen elsewhere. In particular, the HTTP based
events bypass the normal Kubernetes authentication path you get via `kubeconfig` files
and the `kubectl config` family of commands.

As such, there are set of items to consider when deciding how to

- best expose (each) EventListener in your cluster to the outside world.
- best control how (each) EventListener and the underlying API Objects described below access, create,
and update Tekton related API Objects in your cluster.

Minimally, each EventListener has its [ServiceAccountName](#serviceAccountName) as noted below and all
events coming over the "Sink" result in any Tekton resource interactions being done with the permissions
assigned to that ServiceAccount.

However, if you need differing levels of permissions over a set of Tekton resources across the various
[Triggers](#triggers) and [Interceptors](#Interceptors), where not all Triggers or Interceptors can
manipulate certain Tekton Resources in the same way, a simple, single EventListener will not suffice.

Your options at that point are as follows:

### Multiple EventListers (One EventListener Per Namespace)

You can create multiple EventListener objects, where your set of Triggers and Interceptors are spread out across the
EventListeners.

If you create each of those EventListeners in their own namespace, it becomes easy to assign
varying permissions to the ServiceAccount of each one to serve your needs. And often times namespace
creation is coupled with a default set of ServiceAccounts and Secrets that are also defined.
So conceivably some administration steps are taken care of. You just update the permissions
of the automatically created ServiceAccounts.

Possible drawbacks:
- Namespaces with associated Secrets and ServiceAccounts in an aggregate sense prove to be the most expensive
items in Kubernetes underlying `etcd` store. In larger clusters `etcd` storage capacity can become a concern.
- Multiple EventListeners means multiple HTTP ports that must be exposed to the external entities accessing
the "Sink". If you happen to have a HTTP Firewall between your Cluster and external entities, that means more
administrative cost, opening ports in the firewall for each Service, unless you can employ Kubernetes `Ingress` to
serve as a routing abstraction layer for your set of EventListeners.

### Multiple EventListeners (Multiple EventListeners per Namespace)

Multiple EventListeners per namespace will most likely mean more ServiceAccount/Secret/RBAC manipulation for
the administrator, as some of the built in generation of those artifacts as part of namespace creation are not
applicable.

However you will save some on the `etcd` storage costs by reducing the number of namespaces.

Multiple EventListeners and potential Firewall concerns still apply (again unless you employ `Ingress`).

### ServiceAccount per EventListenerTrigger

Being able to set a ServiceAccount on an EventListenerTrigger allows for finer grained permissions as well.

You still have to create the additional ServiceAccounts.

But staying within 1 namespace and minimizing the number of EventListeners with their associated "Sinks" minimizes
concerns around `etcd` storage and port considerations with Firewalls if `Ingress` is not utilized.

## Syntax

To define a configuration file for an `EventListener` resource, you can specify
Expand All @@ -47,30 +108,6 @@ the following fields:
[kubernetes-overview]:
https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/#required-fields

### Triggers

The `triggers` field is required. Each EventListener can consist of one or more
`triggers`. A Trigger consists of:

- `name` - (Optional) a valid
[Kubernetes name](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set)
- [`interceptors`](#interceptors) - (Optional) list of interceptors to use
- `bindings` - A list of names of `TriggerBindings` to use
- `template` - The name of `TriggerTemplate` to use

```yaml
triggers:
- name: trigger-1
interceptors:
- github:
eventTypes: ["pull_request"]
bindings:
- name: pipeline-binding
- name: message-binding
template:
name: pipeline-template
```
### ServiceAccountName

The `serviceAccountName` field is required. The ServiceAccount that the
Expand All @@ -90,7 +127,8 @@ rules:
resources: ["eventlisteners", "triggerbindings", "triggertemplates"]
verbs: ["get"]
- apiGroups: [""]
resources: ["configmaps", "secrets"] # secrets are only needed for Github/Gitlab interceptors
# secrets are only needed for Github/Gitlab interceptors, serviceaccounts only for per trigger authorization
resources: ["configmaps", "secrets", "serviceaccounts"]
verbs: ["get", "list", "watch"]
# Permissions to create resources in associated TriggerTemplates
- apiGroups: ["tekton.dev"]
Expand All @@ -103,6 +141,52 @@ If your EventListener is using
ServiceAccount with a
[ClusterRole instead](../examples/role-resources/clustertriggerbinding-roles/clusterrole.yaml).

### Triggers

The `triggers` field is required. Each EventListener can consist of one or more
`triggers`. A Trigger consists of:

- `name` - (Optional) a valid
[Kubernetes name](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set)
- [`interceptors`](#interceptors) - (Optional) list of interceptors to use
- `bindings` - A list of names of `TriggerBindings` to use
- `template` - The name of `TriggerTemplate` to use

```yaml
triggers:
- name: trigger-1
interceptors:
- github:
eventTypes: ["pull_request"]
bindings:
- name: pipeline-binding
- name: message-binding
template:
name: pipeline-template
```

Also, to support multi-tenant styled scenarios, where an administrator may not want all triggers to have
the same permissions as the `EventListener`, a service account can optionally be set at the trigger level
and used if present in place of the `EventListener` service account when creating resources:

```yaml
triggers:
- name: trigger-1
serviceAccount:
name: trigger-1-sa
namespace: event-listener-namespace
interceptors:
- github:
eventTypes: ["pull_request"]
bindings:
- name: pipeline-binding
- name: message-binding
template:
name: pipeline-template
```

The default ClusterRole for the EventLister allows for reading ServiceAccounts from any namespace.

### ServiceType

The `serviceType` field is optional. EventListener sinks are exposed via
Expand Down Expand Up @@ -414,7 +498,6 @@ processed, and the `overlays` applied.

Optionally, no `filter` expression can be provided, and the `overlays` will be
applied to the incoming body.

<!-- FILE: examples/eventlisteners/cel-eventlistener-no-filter.yaml -->

```YAML
Expand Down Expand Up @@ -542,6 +625,7 @@ spec:
value: $(body.pull_request.head.short_sha)
```


## Examples

For complete examples, see
Expand Down
2 changes: 1 addition & 1 deletion docs/triggerbindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ parameters. The separation of `TriggerBinding`s from `TriggerTemplate`s was
deliberate to encourage reuse between them.

<!-- FILE: examples/triggerbindings/triggerbinding.yaml -->

```YAML
apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerBinding
Expand All @@ -28,6 +27,7 @@ spec:
value: $(header.Content-Type)
```
`TriggerBinding`s are connected to `TriggerTemplate`s within an
[`EventListener`](eventlisteners.md), which is where the pod is actually
instantiated that "listens" for the respective events.
Expand Down
2 changes: 1 addition & 1 deletion docs/triggertemplates.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ A `TriggerTemplate` is a resource that can template resources.
**anywhere** within the resource template.

<!-- FILE: examples/triggertemplates/triggertemplate.yaml -->

```YAML
apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerTemplate
Expand Down Expand Up @@ -53,6 +52,7 @@ spec:
value: $(params.gitrepositoryurl)
```
Similar to
[Pipelines](https://github.com/tektoncd/pipeline/blob/master/docs/pipelines.md),`TriggerTemplate`s
do not do any actual work, but instead act as the blueprint for what resources
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ rules:
resources: ["clustertriggerbindings", "eventlisteners", "triggerbindings", "triggertemplates"]
verbs: ["get"]
- apiGroups: [""]
resources: ["configmaps", "secrets"] # secrets are only needed for Github/Gitlab interceptors
# secrets are only needed for Github/Gitlab interceptors, serviceaccounts only for per trigger authorization
resources: ["configmaps", "secrets", "serviceaccounts"]
verbs: ["get", "list", "watch"]
# Permissions to create resources in associated TriggerTemplates
- apiGroups: ["tekton.dev"]
Expand Down
3 changes: 2 additions & 1 deletion examples/role-resources/triggerbinding-roles/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ rules:
resources: ["eventlisteners", "triggerbindings", "triggertemplates"]
verbs: ["get"]
- apiGroups: [""]
resources: ["configmaps", "secrets"] # secrets are only needed for Github/Gitlab interceptors
# secrets are only needed for Github/Gitlab interceptors, serviceaccounts only for per trigger authorization
resources: ["configmaps", "secrets", "serviceaccounts"]
verbs: ["get", "list", "watch"]
# Permissions to create resources in associated TriggerTemplates
- apiGroups: ["tekton.dev"]
Expand Down
9 changes: 9 additions & 0 deletions pkg/apis/triggers/v1alpha1/event_listener_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ type EventListenerTrigger struct {
// +optional
Name string `json:"name,omitempty"`
Interceptors []*EventInterceptor `json:"interceptors,omitempty"`
// ServiceAccount optionally associates credentials with each trigger;
// more granular authorization for
// who is allowed to utilize the associated pipeline
// vs. defaulting to whatever permissions are associated
// with the entire EventListener and associated sink facilitates
// multi-tenant model based scenarios
// TODO do we want to restrict this to the event listener namespace and just ask for the service account name here?
// +optional
ServiceAccount *corev1.ObjectReference `json:"serviceAccount,omitempty"`
}

// EventInterceptor provides a hook to intercept and pre-process events
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/triggers/v1alpha1/zz_generated.deepcopy.go

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

5 changes: 5 additions & 0 deletions pkg/resources/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"fmt"
"strings"

kerrors "k8s.io/apimachinery/pkg/api/errors"

"k8s.io/client-go/dynamic"

"go.uber.org/zap"
Expand Down Expand Up @@ -99,6 +101,9 @@ func Create(logger *zap.SugaredLogger, rt json.RawMessage, triggerName, eventID,
logger.Infof("For event ID %q creating resource %v", eventID, gvr)

if _, err := dc.Resource(gvr).Namespace(namespace).Create(data, metav1.CreateOptions{}); err != nil {
if kerrors.IsUnauthorized(err) || kerrors.IsForbidden(err) {
return err
}
return fmt.Errorf("couldn't create resource with group version kind %q: %v", gvr, err)
}
return nil
Expand Down
Loading

0 comments on commit 1f769bc

Please sign in to comment.