Skip to content

Commit

Permalink
Add changes to run clusterinterceptor as HTTPS
Browse files Browse the repository at this point in the history
  • Loading branch information
savitaashture authored and tekton-robot committed May 2, 2022
1 parent 7146397 commit 28d7b73
Show file tree
Hide file tree
Showing 19 changed files with 441 additions and 135 deletions.
137 changes: 130 additions & 7 deletions cmd/interceptors/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,41 @@ limitations under the License.
package main

import (
"bytes"
"context"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"time"

triggersclientset "github.com/tektoncd/triggers/pkg/client/clientset/versioned"
"github.com/tektoncd/triggers/pkg/interceptors/server"
"go.uber.org/zap"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubeclientset "k8s.io/client-go/kubernetes"
v1 "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/rest"
secretInformer "knative.dev/pkg/client/injection/kube/informers/core/v1/secret"
"knative.dev/pkg/injection"
"knative.dev/pkg/logging"
"knative.dev/pkg/signals"
certresources "knative.dev/pkg/webhook/certificates/resources"
)

const (
// Port is the port that the port that interceptor service listens on
Port = 8082
// HTTPSPort is the port where interceptor service listens on
HTTPSPort = 8443
readTimeout = 5 * time.Second
writeTimeout = 20 * time.Second
idleTimeout = 60 * time.Second
decade = 100 * 365 * 24 * time.Hour

keyFile = "/tmp/server-key.pem"
certFile = "/tmp/server-cert.pem"
)

func main() {
Expand All @@ -60,10 +74,16 @@ func main() {
}
}()

kubeClient, err := kubeclientset.NewForConfig(cfg)
if err != nil {
logger.Errorf("failed to create new Clientset for the given config: %v", err)
return
}

secretLister := secretInformer.Get(ctx).Lister()
service, err := server.NewWithCoreInterceptors(secretLister, logger)
if err != nil {
log.Printf("failed to initialize core interceptors: %s", err)
logger.Errorf("failed to initialize core interceptors: %s", err)
return
}
startInformer()
Expand All @@ -72,8 +92,17 @@ func main() {
mux.Handle("/", service)
mux.HandleFunc("/ready", handler)

keyFile, certFile, caCert, err := getCerts(ctx, secretLister, kubeClient, logger)
if err != nil {
return
}

if err := updateCRDWithCaCert(ctx, cfg, caCert); err != nil {
return
}

srv := &http.Server{
Addr: fmt.Sprintf(":%d", Port),
Addr: fmt.Sprintf(":%d", HTTPSPort),
BaseContext: func(listener net.Listener) context.Context {
return ctx
},
Expand All @@ -82,13 +111,107 @@ func main() {
IdleTimeout: idleTimeout,
Handler: mux,
}

logger.Infof("Listen and serve on port %d", Port)
if err := srv.ListenAndServe(); err != nil {
logger.Infof("Listen and serve on port %d", HTTPSPort)
if err := srv.ListenAndServeTLS(certFile, keyFile); err != nil {
logger.Fatalf("failed to start interceptors service: %v", err)
}

}

// updateCRDWithCaCert updates clusterinterceptor crd caBundle with caCert
func updateCRDWithCaCert(ctx context.Context, cfg *rest.Config, caCert []byte) error {
tc, err := triggersclientset.NewForConfig(cfg)
if err != nil {
return err
}
clusterInterceptorList, err := tc.TriggersV1alpha1().ClusterInterceptors().List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
for i := range clusterInterceptorList.Items {
if bytes.Equal(clusterInterceptorList.Items[i].Spec.ClientConfig.CaBundle, []byte{}) {
clusterInterceptorList.Items[i].Spec.ClientConfig.CaBundle = caCert
if _, err := tc.TriggersV1alpha1().ClusterInterceptors().Update(ctx, &clusterInterceptorList.Items[i], metav1.UpdateOptions{}); err != nil {
return err
}
}
}
return nil
}

func handler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}

// getCerts uses Knative pkg to generate certs for clusterinterceptor to run as https
func getCerts(ctx context.Context, secretLister v1.SecretLister, kubeClient *kubeclientset.Clientset, logger *zap.SugaredLogger) (string, string, []byte, error) {
interceptorSvcName := os.Getenv("INTERCEPTOR_TLS_SVC_NAME")
interceptorSecretName := os.Getenv("INTERCEPTOR_TLS_SECRET_NAME")
namespace := os.Getenv("SYSTEM_NAMESPACE")

secret, err := secretLister.Secrets(namespace).Get(interceptorSecretName)
if err != nil {
if apierrors.IsNotFound(err) {
// The secret should be created explicitly by a higher-level system
// that's responsible for install/updates. We simply populate the
// secret information.
logger.Infof("secret %s is missing", interceptorSecretName)
return "", "", []byte{}, err
}
logger.Infof("error accessing certificate secret %q: %v", interceptorSecretName, err)
return "", "", []byte{}, err
}

var (
serverKey, serverCert, caCert []byte
createCerts bool
)
serverKey, haskey := secret.Data[certresources.ServerKey]
if !haskey {
logger.Infof("secret %q is missing key %q", secret.Name, certresources.ServerKey)
createCerts = true
}
serverCert, haskey = secret.Data[certresources.ServerCert]
if !haskey {
logger.Infof("secret %q is missing key %q", secret.Name, certresources.ServerCert)
createCerts = true
}
caCert, haskey = secret.Data[certresources.CACert]
if !haskey {
logger.Infof("secret %q is missing key %q", secret.Name, certresources.CACert)
createCerts = true
}

// TODO: Certification validation and rotation is pending

if createCerts {
serverKey, serverCert, caCert, err = certresources.CreateCerts(ctx, interceptorSvcName, namespace, time.Now().Add(decade))
if err != nil {
logger.Errorf("failed to create certs : %v", err)
return "", "", []byte{}, err
}

secret.Data = map[string][]byte{
certresources.ServerKey: serverKey,
certresources.ServerCert: serverCert,
certresources.CACert: caCert,
}
if _, err = kubeClient.CoreV1().Secrets(namespace).Update(ctx, secret, metav1.UpdateOptions{}); err != nil {
logger.Errorf("failed to update secret : %v", err)
return "", "", []byte{}, err
}
}

// write serverKey to file so that it can be passed while running https server.
if err = ioutil.WriteFile(keyFile, serverKey, 0600); err != nil {
logger.Errorf("failed to write serverKey file %v", err)
return "", "", []byte{}, err
}

// write serverCert to file so that it can be passed while running https server.
if err = ioutil.WriteFile(certFile, serverCert, 0600); err != nil {
logger.Errorf("failed to write serverCert file %v", err)
return "", "", []byte{}, err
}
return keyFile, certFile, caCert, nil
}
19 changes: 19 additions & 0 deletions config/200-clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ rules:
resources: ["secrets"]
verbs: ["get", "list", "watch"]
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: tekton-triggers-core-interceptors-secrets
labels:
app.kubernetes.io/instance: default
app.kubernetes.io/part-of: tekton-triggers
rules:
- apiGroups: ["triggers.tekton.dev"]
resources: ["clusterinterceptors"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch", "update"]
resourceNames: ["tekton-triggers-core-interceptors-certs"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
Expand Down Expand Up @@ -106,3 +122,6 @@ rules:
- apiGroups: ["triggers.tekton.dev"]
resources: ["clustertriggerbindings", "clusterinterceptors"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch"]
17 changes: 17 additions & 0 deletions config/201-clusterrolebinding.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,20 @@ roleRef:
kind: ClusterRole
name: tekton-triggers-core-interceptors
apiGroup: rbac.authorization.k8s.io
---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: tekton-triggers-core-interceptors-secrets
labels:
app.kubernetes.io/instance: default
app.kubernetes.io/part-of: tekton-triggers
subjects:
- kind: ServiceAccount
name: tekton-triggers-core-interceptors
namespace: tekton-pipelines
roleRef:
kind: ClusterRole
name: tekton-triggers-core-interceptors-secrets
apiGroup: rbac.authorization.k8s.io
16 changes: 11 additions & 5 deletions config/interceptors/core-interceptors-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ spec:
containers:
- name: tekton-triggers-core-interceptors
image: "ko://github.com/tektoncd/triggers/cmd/interceptors"
ports:
- containerPort: 8443
args: [
"-logtostderr",
"-stderrthreshold", "INFO",
Expand All @@ -65,11 +67,16 @@ spec:
value: config-observability-triggers
- name: METRICS_DOMAIN
value: tekton.dev/triggers
# assuming service and deployment names are same always for consistency
- name: INTERCEPTOR_TLS_SVC_NAME
value: tekton-triggers-core-interceptors
- name: INTERCEPTOR_TLS_SECRET_NAME
value: tekton-triggers-core-interceptors-certs
readinessProbe:
httpGet:
path: /ready
port: 8082
scheme: HTTP
port: 8443
scheme: HTTPS
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
Expand Down Expand Up @@ -98,9 +105,8 @@ metadata:
namespace: tekton-pipelines
spec:
ports:
- name: "http"
port: 80
targetPort: 8082
- name: "https"
port: 8443
selector:
app.kubernetes.io/name: core-interceptors
app.kubernetes.io/component: interceptors
Expand Down
26 changes: 26 additions & 0 deletions config/interceptors/interceptor-secrets.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# 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: v1
kind: Secret
metadata:
name: tekton-triggers-core-interceptors-certs
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: core-interceptors
app.kubernetes.io/component: interceptors
app.kubernetes.io/instance: default
app.kubernetes.io/part-of: tekton-triggers
triggers.tekton.dev/release: "devel"
# The data is populated at install time.
26 changes: 26 additions & 0 deletions docs/clusterinterceptors.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,29 @@ The Kubernetes object running the custom business logic for your `ClusterInterce
- Returns an HTTP 200 OK response that contains an [`InterceptorResponse`](https://pkg.go.dev/github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1#InterceptorResponse)
as a JSON body. If the trigger processing should continue, the interceptor should set the `continue` field in the response to `true`. If the processing should be stopped, the interceptor should set the `continue` field to `false` and also provide additional information detailing the error in the `status` field.
- Returns a response other than HTTP 200 OK only if payload processing halts due to a catastrophic failure.

### Running ClusterInterceptor as HTTPS

Triggers now run clusterinterceptor as `https` server in order to support end to end secure connection and here is a [TEP](https://github.com/tektoncd/community/blob/main/teps/0102-https-connection-to-triggers-interceptor.md) which gives more detail about this support.

By default Triggers run all core interceptor (GitHub, GitLab, BitBucket, CEL) as `HTTPS`.

Triggers expose a new optional field `caBundle` as part of clusterinterceptor spec.

Example:
```yaml
spec:
clientConfig:
caBundle: <cert data>
service:
name: "my-interceptor-svc"
namespace: "default"
path: "/optional-path" # optional
port: 8443
```

Triggers uses knative pkg to generate key, cert, cacert and fill caBundle for core interceptors (GitHub, GitLab, BitBucket, CEL).

Triggers now support writing custom interceptor for both `http` and `https`. Support of `http` for custom interceptor will be there for 1-2 releases, later it will be removed and only `https` will be supported.

End user who write `https` custom interceptor need to pass `caBundle` in order to make secure connection with eventlistener
11 changes: 11 additions & 0 deletions docs/triggers-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,17 @@ string
<tbody>
<tr>
<td>
<code>caBundle</code><br/>
<em>
[]byte
</em>
</td>
<td>
<p>CaBundle is a PEM encoded CA bundle which will be used to validate the clusterinterceptor server certificate</p>
</td>
</tr>
<tr>
<td>
<code>url</code><br/>
<em>
<a href="https://pkg.go.dev/knative.dev/pkg/apis#URL">
Expand Down
Loading

0 comments on commit 28d7b73

Please sign in to comment.