Skip to content

Commit

Permalink
Support for a few originRequest properties
Browse files Browse the repository at this point in the history
Integration tests

WIP adding config map/secret mounting

Properly pass CaPool values

Refactoring and cleanup

Revert imports moving around

Not sure why that happened

Refactor to just forward controller volume mounts

I gave this some more thought and I think just letting consumers mount volumes to the controller and then forward those to the cloudflared pod would be better. Curious what you think!

Rebase conflict
  • Loading branch information
UnstoppableMango committed Jun 1, 2024
1 parent 160ee74 commit 4f2a173
Show file tree
Hide file tree
Showing 10 changed files with 429 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: CLOUDFLARED_IMAGE
value: "{{ .Values.cloudflared.image.repository }}:{{ .Values.cloudflared.image.tag }}"
- name: CLOUDFLARED_IMAGE_PULL_POLICY
Expand All @@ -82,6 +86,10 @@ spec:
value: {{ .Values.cloudflared.replicaCount | quote }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- if gt (len .Values.extraVolumeMounts) 0 }}
volumeMounts:
{{- toYaml .Values.extraVolumeMounts | nindent 12 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
Expand All @@ -94,3 +102,7 @@ spec:
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if gt (len .Values.extraVolumes) 0 }}
volumes:
{{- toYaml .Values.extraVolumes | nindent 8 }}
{{- end }}
8 changes: 8 additions & 0 deletions helm/cloudflare-tunnel-ingress-controller/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ tolerations: []

affinity: {}

# Any additional volumes to include in the controller deployment.
# Note: These will be forwarded to the cloudflared deployment as well.
extraVolumes: []

# Any additional volumes to mount in the controller pod.
# Note: These will be forwarded to the cloudflared pod as well.
extraVolumeMounts: []

cloudflared:
image:
repository: cloudflare/cloudflared
Expand Down
50 changes: 41 additions & 9 deletions pkg/cloudflare-controller/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package cloudflarecontroller
import (
"context"
"strings"

"github.com/STRRL/cloudflare-tunnel-ingress-controller/pkg/exposure"
"github.com/cloudflare/cloudflare-go"
"github.com/pkg/errors"
Expand All @@ -14,20 +14,52 @@ func fromExposureToCloudflareIngress(ctx context.Context, exposure exposure.Expo
return nil, errors.Errorf("exposure %s is deleted, should not generate cloudflare ingress for it", exposure.Hostname)
}

result := cloudflare.UnvalidatedIngressRule{
Hostname: exposure.Hostname,
Path: exposure.PathPrefix,
Service: exposure.ServiceTarget,
var originRequest *cloudflare.OriginRequestConfig = nil

if exposure.OriginServerName != nil {
if originRequest == nil {
originRequest = &cloudflare.OriginRequestConfig{}
}

originRequest.OriginServerName = exposure.OriginServerName
}

if exposure.CAPool != nil {
if originRequest == nil {
originRequest = &cloudflare.OriginRequestConfig{}
}

originRequest.CAPool = exposure.CAPool
}

if exposure.TLSTimeout != nil {
if originRequest == nil {
originRequest = &cloudflare.OriginRequestConfig{}
}

originRequest.TLSTimeout = &cloudflare.TunnelDuration{
Duration: *exposure.TLSTimeout,
}
}

if strings.HasPrefix(exposure.ServiceTarget, "https://") {
result.OriginRequest = &cloudflare.OriginRequestConfig{}
if exposure.ProxySSLVerifyEnabled == nil {
result.OriginRequest.NoTLSVerify = boolPointer(true)
if originRequest == nil {
originRequest = &cloudflare.OriginRequestConfig{}
}

if exposure.NoTLSVerify == nil {
originRequest.NoTLSVerify = boolPointer(true)
} else {
result.OriginRequest.NoTLSVerify = boolPointer(!*exposure.ProxySSLVerifyEnabled)
originRequest.NoTLSVerify = exposure.NoTLSVerify
}
}

result := cloudflare.UnvalidatedIngressRule{
Hostname: exposure.Hostname,
Path: exposure.PathPrefix,
Service: exposure.ServiceTarget,
OriginRequest: originRequest,
}

return &result, nil
}
97 changes: 87 additions & 10 deletions pkg/cloudflare-controller/transform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"reflect"
"testing"
"time"

"github.com/STRRL/cloudflare-tunnel-ingress-controller/pkg/exposure"
"github.com/cloudflare/cloudflare-go"
Expand Down Expand Up @@ -92,11 +93,11 @@ func Test_fromExposureToCloudflareIngress(t *testing.T) {
args: args{
ctx: context.Background(),
exposure: exposure.Exposure{
Hostname: "ingress.example.com",
ServiceTarget: "https://10.0.0.1:443",
PathPrefix: "/",
IsDeleted: false,
ProxySSLVerifyEnabled: boolPointer(false),
Hostname: "ingress.example.com",
ServiceTarget: "https://10.0.0.1:443",
PathPrefix: "/",
IsDeleted: false,
NoTLSVerify: boolPointer(true),
},
},
want: &cloudflare.UnvalidatedIngressRule{
Expand All @@ -112,11 +113,11 @@ func Test_fromExposureToCloudflareIngress(t *testing.T) {
args: args{
ctx: context.Background(),
exposure: exposure.Exposure{
Hostname: "ingress.example.com",
ServiceTarget: "https://10.0.0.1:443",
PathPrefix: "/",
IsDeleted: false,
ProxySSLVerifyEnabled: boolPointer(true),
Hostname: "ingress.example.com",
ServiceTarget: "https://10.0.0.1:443",
PathPrefix: "/",
IsDeleted: false,
NoTLSVerify: boolPointer(false),
},
},
want: &cloudflare.UnvalidatedIngressRule{
Expand All @@ -127,8 +128,72 @@ func Test_fromExposureToCloudflareIngress(t *testing.T) {
NoTLSVerify: boolPointer(false),
},
},
}, {
name: "origin server name",
args: args{
ctx: context.Background(),
exposure: exposure.Exposure{
Hostname: "ingress.example.com",
ServiceTarget: "https://10.0.0.1:443",
PathPrefix: "/",
IsDeleted: false,
OriginServerName: stringPointer("example.com"),
},
},
want: &cloudflare.UnvalidatedIngressRule{
Hostname: "ingress.example.com",
Path: "/",
Service: "https://10.0.0.1:443",
OriginRequest: &cloudflare.OriginRequestConfig{
OriginServerName: stringPointer("example.com"),
NoTLSVerify: boolPointer(true),
},
},
}, {
name: "origin CA pool",
args: args{
ctx: context.Background(),
exposure: exposure.Exposure{
Hostname: "ingress.example.com",
ServiceTarget: "http://10.0.0.1:443",
PathPrefix: "/",
IsDeleted: false,
CAPool: stringPointer("/path/to/my/certs.crt"),
},
},
want: &cloudflare.UnvalidatedIngressRule{
Hostname: "ingress.example.com",
Path: "/",
Service: "http://10.0.0.1:443",
OriginRequest: &cloudflare.OriginRequestConfig{
CAPool: stringPointer("/path/to/my/certs.crt"),
},
},
}, {
name: "valid origin TLS timeout",
args: args{
ctx: context.Background(),
exposure: exposure.Exposure{
Hostname: "ingress.example.com",
ServiceTarget: "http://10.0.0.1:443",
PathPrefix: "/",
IsDeleted: false,
TLSTimeout: parseDuration("30s"),
},
},
want: &cloudflare.UnvalidatedIngressRule{
Hostname: "ingress.example.com",
Path: "/",
Service: "http://10.0.0.1:443",
OriginRequest: &cloudflare.OriginRequestConfig{
TLSTimeout: &cloudflare.TunnelDuration{
Duration: *parseDuration("30s"),
},
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := fromExposureToCloudflareIngress(tt.args.ctx, tt.args.exposure)
Expand All @@ -142,3 +207,15 @@ func Test_fromExposureToCloudflareIngress(t *testing.T) {
})
}
}

func parseDuration(value string) *time.Duration {
duration, err := time.ParseDuration(value)
if err != nil {
panic(err)
}
return &duration
}

func stringPointer(s string) *string {
return &s
}
36 changes: 34 additions & 2 deletions pkg/controller/controlled-cloudflared-connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

Expand Down Expand Up @@ -40,20 +41,49 @@ func CreateControlledCloudflaredIfNotExist(
return errors.Wrap(err, "fetch tunnel token")
}

controllerPod := v1.Pod{}
err = kubeClient.Get(ctx, types.NamespacedName{
Namespace: namespace,
Name: os.Getenv("POD_NAME"),
}, &controllerPod)
if err != nil {
return errors.Wrap(err, "get controller pod")
}

var controllerVolumeMounts []v1.VolumeMount
for _, container := range controllerPod.Spec.Containers {
if container.Name == "cloudflare-tunnel-ingress-controller" {
controllerVolumeMounts = container.VolumeMounts
}
}

replicas, err := strconv.ParseInt(os.Getenv("CLOUDFLARED_REPLICA_COUNT"), 10, 32)
if err != nil {
return errors.Wrap(err, "invalid replica count")
}

deployment := cloudflaredConnectDeploymentTemplating(token, namespace, int32(replicas))
deployment := cloudflaredConnectDeploymentTemplating(
token,
namespace,
int32(replicas),
controllerPod.Spec.Volumes,
controllerVolumeMounts,
)

err = kubeClient.Create(ctx, deployment)
if err != nil {
return errors.Wrap(err, "create controlled-cloudflared-connector deployment")
}
return nil
}

func cloudflaredConnectDeploymentTemplating(token string, namespace string, replicas int32) *appsv1.Deployment {
func cloudflaredConnectDeploymentTemplating(
token string,
namespace string,
replicas int32,
extraVolumes []v1.Volume,
extraVolumeMounts []v1.VolumeMount,
) *appsv1.Deployment {
appName := "controlled-cloudflared-connector"
image := os.Getenv("CLOUDFLARED_IMAGE")
pullPolicy := os.Getenv("CLOUDFLARED_IMAGE_PULL_POLICY")
Expand Down Expand Up @@ -97,9 +127,11 @@ func cloudflaredConnectDeploymentTemplating(token string, namespace string, repl
"--token",
token,
},
VolumeMounts: extraVolumeMounts,
},
},
RestartPolicy: v1.RestartPolicyAlways,
Volumes: extraVolumes,
},
},
},
Expand Down
Loading

0 comments on commit 4f2a173

Please sign in to comment.