Skip to content

Commit

Permalink
Add Namespaced Scope Interceptor
Browse files Browse the repository at this point in the history
Added Namespaced Scope Interceptor. A namespaced admin can create and
manage its interceptor without requiring elevated permissions. Fixes #1364
  • Loading branch information
khrm committed Oct 12, 2022
1 parent 811e571 commit 0e26478
Show file tree
Hide file tree
Showing 34 changed files with 2,781 additions and 9 deletions.
2 changes: 2 additions & 0 deletions cmd/controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/tektoncd/triggers/pkg/reconciler/clusterinterceptor"
elresources "github.com/tektoncd/triggers/pkg/reconciler/eventlistener/resources"
"github.com/tektoncd/triggers/pkg/reconciler/interceptor"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
Expand Down Expand Up @@ -95,5 +96,6 @@ func main() {
cfg,
eventlistener.NewController(c),
clusterinterceptor.NewController(),
interceptor.NewController(),
)
}
54 changes: 54 additions & 0 deletions config/300-interceptor.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Copyright 2022 The Tekton Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: interceptors.triggers.tekton.dev
labels:
app.kubernetes.io/instance: default
app.kubernetes.io/part-of: tekton-triggers
triggers.tekton.dev/release: "devel"
version: "devel"
spec:
group: triggers.tekton.dev
scope: Cluster
names:
kind: Interceptor
plural: interceptors
singular: interceptor
shortNames:
- ni
categories:
- tekton
- tekton-triggers
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
# One can use x-kubernetes-preserve-unknown-fields: true
# at the root of the schema (and inside any properties, additionalProperties)
# to get the traditional CRD behaviour that nothing is pruned, despite
# setting spec.preserveUnknownProperties: false.
#
# See https://kubernetes.io/blog/2019/06/20/crd-structural-schema/
# See issue: https://github.com/knative/serving/issues/912
x-kubernetes-preserve-unknown-fields: true
# Opt into the status subresource so metadata.generation
# starts to increment
subresources:
status: {}
157 changes: 156 additions & 1 deletion docs/triggers-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ string
<h3 id="triggers.tekton.dev/v1alpha1.ClientConfig">ClientConfig
</h3>
<p>
(<em>Appears on:</em><a href="#triggers.tekton.dev/v1alpha1.ClusterInterceptorSpec">ClusterInterceptorSpec</a>)
(<em>Appears on:</em><a href="#triggers.tekton.dev/v1alpha1.ClusterInterceptorSpec">ClusterInterceptorSpec</a>, <a href="#triggers.tekton.dev/v1alpha1.InterceptorSpec">InterceptorSpec</a>)
</p>
<div>
<p>ClientConfig describes how a client can communicate with the Interceptor</p>
Expand Down Expand Up @@ -1182,6 +1182,79 @@ SecretRef
</tr>
</tbody>
</table>
<h3 id="triggers.tekton.dev/v1alpha1.Interceptor">Interceptor
</h3>
<div>
<p>Interceptor describes a pluggable interceptor including configuration
such as the fields it accepts and its deployment address. The type is based on
the Validating/MutatingWebhookConfiguration types for configuring AdmissionWebhooks</p>
</div>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>metadata</code><br/>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#objectmeta-v1-meta">
Kubernetes meta/v1.ObjectMeta
</a>
</em>
</td>
<td>
<em>(Optional)</em>
Refer to the Kubernetes API documentation for the fields of the
<code>metadata</code> field.
</td>
</tr>
<tr>
<td>
<code>spec</code><br/>
<em>
<a href="#triggers.tekton.dev/v1alpha1.InterceptorSpec">
InterceptorSpec
</a>
</em>
</td>
<td>
<br/>
<br/>
<table>
<tr>
<td>
<code>clientConfig</code><br/>
<em>
<a href="#triggers.tekton.dev/v1alpha1.ClientConfig">
ClientConfig
</a>
</em>
</td>
<td>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<code>status</code><br/>
<em>
<a href="#triggers.tekton.dev/v1alpha1.InterceptorStatus">
InterceptorStatus
</a>
</em>
</td>
<td>
<em>(Optional)</em>
</td>
</tr>
</tbody>
</table>
<h3 id="triggers.tekton.dev/v1alpha1.InterceptorInterface">InterceptorInterface
</h3>
<div>
Expand All @@ -1204,6 +1277,9 @@ SecretRef
<tbody><tr><td><p>&#34;ClusterInterceptor&#34;</p></td>
<td><p>ClusterInterceptorKind indicates that Interceptor type has a cluster scope.</p>
</td>
</tr><tr><td><p>&#34;NamespacedInterceptor&#34;</p></td>
<td><p>NamespacedInterceptorKind indicated that interceptor has a namespaced scope</p>
</td>
</tr></tbody>
</table>
<h3 id="triggers.tekton.dev/v1alpha1.InterceptorParams">InterceptorParams
Expand Down Expand Up @@ -1427,6 +1503,85 @@ Status
</tr>
</tbody>
</table>
<h3 id="triggers.tekton.dev/v1alpha1.InterceptorSpec">InterceptorSpec
</h3>
<p>
(<em>Appears on:</em><a href="#triggers.tekton.dev/v1alpha1.Interceptor">Interceptor</a>)
</p>
<div>
<p>InterceptorSpec describes the Spec for an Interceptor</p>
</div>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>clientConfig</code><br/>
<em>
<a href="#triggers.tekton.dev/v1alpha1.ClientConfig">
ClientConfig
</a>
</em>
</td>
<td>
</td>
</tr>
</tbody>
</table>
<h3 id="triggers.tekton.dev/v1alpha1.InterceptorStatus">InterceptorStatus
</h3>
<p>
(<em>Appears on:</em><a href="#triggers.tekton.dev/v1alpha1.Interceptor">Interceptor</a>)
</p>
<div>
<p>InterceptorStatus holds the status of the Interceptor</p>
</div>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>Status</code><br/>
<em>
<a href="https://pkg.go.dev/knative.dev/pkg/apis/duck/v1#Status">
knative.dev/pkg/apis/duck/v1.Status
</a>
</em>
</td>
<td>
<p>
(Members of <code>Status</code> are embedded into this type.)
</p>
</td>
</tr>
<tr>
<td>
<code>AddressStatus</code><br/>
<em>
<a href="https://pkg.go.dev/knative.dev/pkg/apis/duck/v1#AddressStatus">
knative.dev/pkg/apis/duck/v1.AddressStatus
</a>
</em>
</td>
<td>
<p>
(Members of <code>AddressStatus</code> are embedded into this type.)
</p>
<p>Interceptor is Addressable and exposes the URL where the Interceptor is running</p>
</td>
</tr>
</tbody>
</table>
<h3 id="triggers.tekton.dev/v1alpha1.KubernetesResource">KubernetesResource
</h3>
<p>
Expand Down
36 changes: 31 additions & 5 deletions pkg/adapter/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"time"

clusterinterceptorsinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1alpha1/clusterinterceptor"
interceptorsinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1alpha1/interceptor"
clustertriggerbindingsinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1beta1/clustertriggerbinding"
eventlistenerinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1beta1/eventlistener"
triggersinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1beta1/trigger"
Expand Down Expand Up @@ -119,7 +120,7 @@ func (s *sinker) getHTTPClient() (*http.Client, error) {
go func() {
for {
<-ticker.C
if err := s.getCertFromClusterInterceptor(certPool); err != nil {
if err := s.getCertFromInterceptor(certPool); err != nil {
s.Logger.Fatalf("Timed out waiting on CaBundle to available for clusterInterceptor: %v", err)
}
}
Expand All @@ -141,7 +142,7 @@ func (s *sinker) getHTTPClient() (*http.Client, error) {
ExpectContinueTimeout: s.Args.ElHTTPClientExpectContinueTimeout * time.Second}}, nil
}

func (s *sinker) getCertFromClusterInterceptor(certPool *x509.CertPool) error {
func (s *sinker) getCertFromInterceptor(certPool *x509.CertPool) error {
var (
caCert []byte
count int
Expand All @@ -166,10 +167,35 @@ func (s *sinker) getCertFromClusterInterceptor(certPool *x509.CertPool) error {
}
}
}
if httpsCILen != 0 && httpsCILen == count {
return true, nil
if httpsCILen == 0 || httpsCILen != count {
return false, fmt.Errorf("empty caBundle in clusterInterceptor spec")
}
return false, fmt.Errorf("empty caBundle in clusterInterceptor spec")

httpsCILen = 0
count = 0

interceptorList, err := interceptorsinformer.Get(s.injCtx).Lister().Interceptors(s.Namespace).List(labels.Everything())
if err != nil {
return false, err
}

for i := range interceptorList {
if v, k := clusterInterceptorList[i].Labels["server/type"]; k && v == "https" {
httpsCILen++
if !bytes.Equal(interceptorList[i].Spec.ClientConfig.CaBundle, []byte{}) {
caCert = interceptorList[i].Spec.ClientConfig.CaBundle
if ok := certPool.AppendCertsFromPEM(caCert); !ok {
return false, fmt.Errorf("unable to parse cert from %s", caCert)
}
count++
}
}
}
if httpsCILen != count {
return false, fmt.Errorf("empty caBundle in interceptor spec")
}

return true, nil
}); err != nil {
return fmt.Errorf("Timed out waiting on CaBundle to available for clusterInterceptor: %v", err)
}
Expand Down
21 changes: 21 additions & 0 deletions pkg/apis/triggers/v1alpha1/interceptor_defaults.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
Copyright 2022 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"context"
)

// SetDefaults sets the defaults on the object.
func (it *Interceptor) SetDefaults(ctx context.Context) {}
43 changes: 43 additions & 0 deletions pkg/apis/triggers/v1alpha1/interceptor_validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
Copyright 2022 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"context"

"knative.dev/pkg/apis"
)

// Validate Interceptor
func (it *Interceptor) Validate(ctx context.Context) *apis.FieldError {
if apis.IsInDelete(ctx) {
return nil
}
return it.Spec.validate(ctx)
}

func (s *InterceptorSpec) validate(ctx context.Context) (errs *apis.FieldError) {
if s.ClientConfig.URL != nil && s.ClientConfig.Service != nil {
errs = errs.Also(apis.ErrMultipleOneOf("spec.clientConfig.url", "spec.clientConfig.service"))
}
if svc := s.ClientConfig.Service; svc != nil {
if svc.Namespace == "" {
errs = errs.Also(apis.ErrMissingField("spec.clientConfig.service.namespace"))
}
if svc.Name == "" {
errs = errs.Also(apis.ErrMissingField("spec.clientConfig.service.name"))
}
}
return errs
}
Loading

0 comments on commit 0e26478

Please sign in to comment.