Skip to content

Commit c315d01

Browse files
author
Paul Cruickshank
committed
Add support for issuing IPSec tunnel certificates using cert-manager.
When cert-manager certificates are enabled, the controller no longer generates the IPSec CA cert or private key stored in the `ovn-ipsec-ca` secret. The secret should be populated with the same CA as configured with cert-manager. It still enables IPSec in OVN NB. When cert-manager certificates are enabled the CNI daemon creates cert-manager CertificateRequest resources instead of CSRs. A cert-manager ClusterIssuer should be configured to approve and sign these CertificateRequests with a matching CA as configured in `ovn-ipsec-ca` secret. The name of the issuer to use is configurable in the CNI. The CNI daemon now watches the `ovn-ipsec-ca` secret for changes allowing for rollout of a new trust bundle. It verifies the currently configured certificate is signed by the new bundle and if not then triggers a new certificate to be issued. The daemon now splits each certificate in the CA bundle into a separate file as strongswan is unable to parse multiple CAs from a single file. The CNI daemon now requests a new certificate when the current certificate is at least half way to expiry based on the times in the certificate. When generating a new certificate the daemon also generates a new key just in case the previous one was leaked somehow. The certificate lifetime is also now configurable rather than lasting for a year. The CNI no longer restarts the ipsec or ovs-ipsec-monitor services when the certificate changes and just requests ipsec to reread the CA certs if they change. To allow for the CNI daemon to keep track of the versions of its key, certificate, and CA cert files it now stores them with locally unique names on disk. Keys and certs are suffixed with the timestamp they were generated. CA files are suffixed with the k8s revision number of the `ovn-ipsec-ca` secret. The cert manager validation webhook (if used) should be run in the host network to prevent the risk of certificate requests deadlocking in the event of a certificate expiry. The CNI pods and cert manager issuer interact with the API server over the host network to create and approve certificates but the API server calls the webhook of the service network which can be broken in the event of an expired certificate. A new kind deployment is created using cert-manager issued certificates and a new e2e test is created that uses it. The e2e test runs through rotating the the CA. Signed-off-by: Paul Cruickshank <pcruickshank@evroc.com>
1 parent 2865d3d commit c315d01

File tree

15 files changed

+1042
-153
lines changed

15 files changed

+1042
-153
lines changed

charts/kube-ovn/templates/ovn-CR.yaml

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,6 @@ rules:
270270
verbs:
271271
- get
272272
- list
273-
274273
---
275274
apiVersion: rbac.authorization.k8s.io/v1
276275
kind: ClusterRole
@@ -349,12 +348,23 @@ rules:
349348
- "list"
350349
- "watch"
351350
- "delete"
352-
- apiGroups:
353-
- ""
354-
resources:
355-
- "secrets"
356-
verbs:
357-
- "get"
351+
---
352+
apiVersion: rbac.authorization.k8s.io/v1
353+
kind: Role
354+
metadata:
355+
name: secret-reader-ovn-ipsec
356+
namespace: kube-system
357+
rules:
358+
- apiGroups:
359+
- ""
360+
resources:
361+
- "secrets"
362+
resourceNames:
363+
- "ovn-ipsec-ca"
364+
verbs:
365+
- "get"
366+
- "list"
367+
- "watch"
358368
---
359369
apiVersion: rbac.authorization.k8s.io/v1
360370
kind: ClusterRole

charts/kube-ovn/templates/ovn-CRB.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,20 @@ subjects:
6767
namespace: {{ .Values.namespace }}
6868
---
6969
apiVersion: rbac.authorization.k8s.io/v1
70+
kind: RoleBinding
71+
metadata:
72+
name: kube-ovn-cni-secret-reader
73+
namespace: kube-system
74+
subjects:
75+
- kind: ServiceAccount
76+
name: kube-ovn-cni
77+
namespace: kube-system
78+
roleRef:
79+
kind: Role
80+
name: secret-reader-ovn-ipsec
81+
apiGroup: rbac.authorization.k8s.io
82+
---
83+
apiVersion: rbac.authorization.k8s.io/v1
7084
kind: ClusterRoleBinding
7185
metadata:
7286
name: kube-ovn-app

cmd/daemon/cniserver.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,15 @@ func main() {
9595
kubeovninformer.WithTweakListOptions(func(listOption *v1.ListOptions) {
9696
listOption.AllowWatchBookmarks = true
9797
}))
98-
ctl, err := daemon.NewController(config, stopCh, podInformerFactory, nodeInformerFactory, kubeovnInformerFactory)
98+
99+
caSecretInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(config.KubeClient, 0,
100+
kubeinformers.WithTweakListOptions(func(listOption *v1.ListOptions) {
101+
listOption.FieldSelector = fmt.Sprintf("metadata.name=%s", util.DefaultOVNIPSecCA)
102+
}),
103+
kubeinformers.WithNamespace("kube-system"),
104+
)
105+
106+
ctl, err := daemon.NewController(config, stopCh, podInformerFactory, nodeInformerFactory, caSecretInformerFactory, kubeovnInformerFactory)
99107
if err != nil {
100108
util.LogFatalAndExit(err, "failed to create controller")
101109
}

dist/images/install.sh

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ OVS_VSCTL_CONCURRENCY=${OVS_VSCTL_CONCURRENCY:-100}
4141
ENABLE_COMPACT=${ENABLE_COMPACT:-false}
4242
SECURE_SERVING=${SECURE_SERVING:-false}
4343
ENABLE_OVN_IPSEC=${ENABLE_OVN_IPSEC:-false}
44+
CERT_MANAGER_IPSEC_CERT=${CERT_MANAGER_IPSEC_CERT:-false}
45+
IPSEC_CERT_DURATION=${IPSEC_CERT_DURATION:-63072000} # 2 years in seconds
46+
CERT_MANAGER_ISSUER_NAME=${CERT_MANAGER_ISSUER_NAME:-kube-ovn}
4447
ENABLE_ANP=${ENABLE_ANP:-false}
4548
SET_VXLAN_TX_OFF=${SET_VXLAN_TX_OFF:-false}
4649
OVSDB_CON_TIMEOUT=${OVSDB_CON_TIMEOUT:-3}
@@ -3773,12 +3776,32 @@ rules:
37733776
- "list"
37743777
- "watch"
37753778
- "delete"
3776-
- apiGroups:
3777-
- ""
3778-
resources:
3779-
- "secrets"
3780-
verbs:
3781-
- "get"
3779+
---
3780+
apiVersion: rbac.authorization.k8s.io/v1
3781+
kind: Role
3782+
metadata:
3783+
name: secret-reader-ovn-ipsec
3784+
namespace: kube-system
3785+
rules:
3786+
- apiGroups:
3787+
- ""
3788+
resources:
3789+
- "secrets"
3790+
resourceNames:
3791+
- "ovn-ipsec-ca"
3792+
verbs:
3793+
- "get"
3794+
- "list"
3795+
- "watch"
3796+
- apiGroups:
3797+
- "cert-manager.io"
3798+
resources:
3799+
- "certificaterequests"
3800+
verbs:
3801+
- "get"
3802+
- "list"
3803+
- "create"
3804+
- "delete"
37823805
---
37833806
apiVersion: rbac.authorization.k8s.io/v1
37843807
kind: ClusterRoleBinding
@@ -3806,6 +3829,20 @@ subjects:
38063829
- kind: ServiceAccount
38073830
name: kube-ovn-cni
38083831
namespace: kube-system
3832+
---
3833+
apiVersion: rbac.authorization.k8s.io/v1
3834+
kind: RoleBinding
3835+
metadata:
3836+
name: kube-ovn-cni-secret-reader
3837+
namespace: kube-system
3838+
subjects:
3839+
- kind: ServiceAccount
3840+
name: kube-ovn-cni
3841+
namespace: kube-system
3842+
roleRef:
3843+
kind: Role
3844+
name: secret-reader-ovn-ipsec
3845+
apiGroup: rbac.authorization.k8s.io
38093846
EOF
38103847

38113848
cat <<EOF > kube-ovn-app-sa.yaml
@@ -4608,6 +4645,7 @@ spec:
46084645
- --enable-metrics=$ENABLE_METRICS
46094646
- --node-local-dns-ip=$NODE_LOCAL_DNS_IP
46104647
- --enable-ovn-ipsec=$ENABLE_OVN_IPSEC
4648+
- --cert-manager-ipsec-cert=$CERT_MANAGER_IPSEC_CERT
46114649
- --secure-serving=${SECURE_SERVING}
46124650
- --enable-anp=$ENABLE_ANP
46134651
- --ovsdb-con-timeout=$OVSDB_CON_TIMEOUT
@@ -4803,6 +4841,9 @@ spec:
48034841
- --ovs-vsctl-concurrency=$OVS_VSCTL_CONCURRENCY
48044842
- --secure-serving=${SECURE_SERVING}
48054843
- --enable-ovn-ipsec=$ENABLE_OVN_IPSEC
4844+
- --cert-manager-ipsec-cert=$CERT_MANAGER_IPSEC_CERT
4845+
- --ovn-ipsec-cert-duration=$IPSEC_CERT_DURATION
4846+
- --cert-manager-issuer-name=$CERT_MANAGER_ISSUER_NAME
48064847
- --set-vxlan-tx-off=$SET_VXLAN_TX_OFF
48074848
securityContext:
48084849
runAsUser: 0

e2e.mk

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,15 @@ kube-ovn-ipsec-e2e:
248248
ginkgo $(GINKGO_OUTPUT_OPT) $(GINKGO_PARALLEL_OPT) --randomize-all -v \
249249
--focus=CNI:Kube-OVN ./test/e2e/ipsec/ipsec.test -- $(TEST_BIN_ARGS)
250250

251+
.PHONY: kube-ovn-ipsec-e2e-cert-mgr
252+
kube-ovn-ipsec-e2e-cert-mgr:
253+
ginkgo build $(E2E_BUILD_FLAGS) ./test/e2e/ipsec-cert-mgr
254+
E2E_BRANCH=$(E2E_BRANCH) \
255+
E2E_IP_FAMILY=$(E2E_IP_FAMILY) \
256+
E2E_NETWORK_MODE=$(E2E_NETWORK_MODE) \
257+
ginkgo $(GINKGO_OUTPUT_OPT) $(GINKGO_PARALLEL_OPT) --randomize-all -v \
258+
--focus=CNI:Kube-OVN ./test/e2e/ipsec-cert-mgr/ipsec-cert-mgr.test -- $(TEST_BIN_ARGS)
259+
251260
.PHONY: kube-ovn-anp-e2e
252261
kube-ovn-anp-e2e:
253262
KUBECONFIG=$(KUBECONFIG) ./test/anp/conformance.sh

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ require (
7070
sigs.k8s.io/network-policy-api v0.1.5
7171
)
7272

73+
require sigs.k8s.io/gateway-api v1.1.0 // indirect
74+
7375
require (
7476
cel.dev/expr v0.24.0 // indirect
7577
cloud.google.com/go/compute/metadata v0.7.0 // indirect
@@ -88,6 +90,7 @@ require (
8890
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
8991
github.com/cenkalti/hub v1.0.2 // indirect
9092
github.com/cenkalti/rpc2 v1.0.4 // indirect
93+
github.com/cert-manager/cert-manager v1.17.1
9194
github.com/cespare/xxhash/v2 v2.3.0 // indirect
9295
github.com/chai2010/gettext-go v1.0.3 // indirect
9396
github.com/container-storage-interface/spec v1.11.0 // indirect
@@ -155,7 +158,7 @@ require (
155158
github.com/httprunner/funplugin v0.5.5 // indirect
156159
github.com/inconshreveable/mousetrap v1.1.0 // indirect
157160
github.com/jinzhu/copier v0.4.0 // indirect
158-
github.com/jmespath/go-jmespath v0.4.0 // indirect
161+
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect
159162
github.com/josharian/intern v1.0.0 // indirect
160163
github.com/josharian/native v1.1.0 // indirect
161164
github.com/json-iterator/go v1.1.12 // indirect

go.sum

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ github.com/cenkalti/hub v1.0.2/go.mod h1:8LAFAZcCasb83vfxatMUnZHRoQcffho2ELpHb+k
5454
github.com/cenkalti/rpc2 v1.0.4 h1:MJWmm7mbt8r/ZkQS+qr/e2KMMrhMLPr/62CYZIHybdI=
5555
github.com/cenkalti/rpc2 v1.0.4/go.mod h1:2yfU5b86vOr16+iY1jN3MvT6Kxc9Nf8j5iZWwUf7iaw=
5656
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
57+
github.com/cert-manager/cert-manager v1.17.1 h1:Aig+lWMoLsmpGd9TOlTvO4t0Ah3D+/vGB37x/f+ZKt0=
58+
github.com/cert-manager/cert-manager v1.17.1/go.mod h1:zeG4D+AdzqA7hFMNpYCJgcQ2VOfFNBa+Jzm3kAwiDU4=
5759
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
5860
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
5961
github.com/chai2010/gettext-go v1.0.3 h1:9liNh8t+u26xl5ddmWLmsOsdNLwkdRTg5AG+JnTiM80=
@@ -334,8 +336,8 @@ github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgf
334336
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
335337
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
336338
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
337-
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
338-
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
339+
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW34dhU4az1GN0pTPADwNmvoRSeoZ6PItiqnY=
340+
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
339341
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
340342
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
341343
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
@@ -1216,6 +1218,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.32.1 h1:Cf+ed5N8038zb
12161218
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.32.1/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
12171219
sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU=
12181220
sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY=
1221+
sigs.k8s.io/gateway-api v1.1.0 h1:DsLDXCi6jR+Xz8/xd0Z1PYl2Pn0TyaFMOPPZIj4inDM=
1222+
sigs.k8s.io/gateway-api v1.1.0/go.mod h1:ZH4lHrL2sDi0FHZ9jjneb8kKnGzFWyrTya35sWUTrRs=
12191223
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
12201224
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
12211225
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=

kind.mk

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,34 @@ kind-install-kwok:
650650
kind-install-ovn-ipsec:
651651
@$(MAKE) ENABLE_OVN_IPSEC=true kind-install
652652

653+
.PHONY: kind-install-cert-manager
654+
kind-install-cert-manager:
655+
$(call kind_load_image,kube-ovn,$(CERT_MANAGER_CONTROLLER),1)
656+
kubectl apply -f "$(CERT_MANAGER_YAML)"
657+
kubectl rollout status deployment/cert-manager -n cert-manager --timeout 120s
658+
659+
.PHONY: kind-install-ovn-ipsec-cert-manager
660+
kind-install-ovn-ipsec-cert-manager:
661+
@$(MAKE) ENABLE_OVN_IPSEC=true CERT_MANAGER_IPSEC_CERT=true kind-install
662+
@$(MAKE) kind-install-cert-manager
663+
664+
kubectl rollout status deployment/cert-manager-webhook -n cert-manager --timeout 120s
665+
666+
$(eval CA_KEY = $(shell mktemp))
667+
$(shell openssl genrsa -out $(CA_KEY) 2048)
668+
$(eval CA_CERT = $(shell openssl req -x509 -new -nodes \
669+
-key "$(CA_KEY)" \
670+
-days 3650 \
671+
-subj "/C=US/ST=California/L=Palo Alto/O=Open vSwitch/OU=Open vSwitch Certification Authority/CN=OVS CA" | \
672+
base64 -w 0 -))
673+
674+
$(eval CA_KEY64 = $(shell base64 -w 0 "$(CA_KEY)"))
675+
676+
sed -e 's/KUBE_OVN_CA_KEY/$(CA_KEY64)/g' \
677+
-e 's/KUBE_OVN_CA_CERT/$(CA_CERT)/g' yamls/ipsec-certs.yaml | \
678+
kubectl apply -f -
679+
rm $(CA_KEY)
680+
653681
.PHONY: kind-install-anp
654682
kind-install-anp: kind-load-image
655683
$(call kind_load_image,kube-ovn,$(ANP_TEST_IMAGE),1)

pkg/controller/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ type Configuration struct {
9797
EnableMetrics bool
9898
EnableANP bool
9999
EnableOVNIPSec bool
100+
CertManagerIPSECCert bool
100101
EnableLiveMigrationOptimize bool
101102

102103
ExternalGatewaySwitch string
@@ -188,6 +189,7 @@ func ParseFlags() (*Configuration, error) {
188189
argEnableMetrics = pflag.Bool("enable-metrics", true, "Whether to support metrics query")
189190
argEnableANP = pflag.Bool("enable-anp", false, "Enable support for admin network policy and baseline admin network policy")
190191
argEnableOVNIPSec = pflag.Bool("enable-ovn-ipsec", false, "Whether to enable ovn ipsec")
192+
argCertManagerIPSECCert = pflag.Bool("cert-manager-ipsec-cert", false, "Whether to use cert-manager for signing IPSec certificates")
191193
argEnableLiveMigrationOptimize = pflag.Bool("enable-live-migration-optimize", true, "Whether to enable kubevirt live migration optimize")
192194

193195
argExternalGatewayConfigNS = pflag.String("external-gateway-config-ns", "kube-system", "The namespace of configmap external-gateway-config, default: kube-system")
@@ -290,6 +292,7 @@ func ParseFlags() (*Configuration, error) {
290292
EnableOVNLBPreferLocal: *argEnableOVNLBPreferLocal,
291293
EnableMetrics: *argEnableMetrics,
292294
EnableOVNIPSec: *argEnableOVNIPSec,
295+
CertManagerIPSECCert: *argCertManagerIPSECCert,
293296
EnableLiveMigrationOptimize: *argEnableLiveMigrationOptimize,
294297
BfdMinTx: *argBfdMinTx,
295298
BfdMinRx: *argBfdMinRx,

pkg/controller/controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -982,7 +982,7 @@ func (c *Controller) Run(ctx context.Context) {
982982
util.LogFatalAndExit(err, "failed to sync crd vlans")
983983
}
984984

985-
if c.config.EnableOVNIPSec {
985+
if c.config.EnableOVNIPSec && !c.config.CertManagerIPSECCert {
986986
if err := c.InitDefaultOVNIPsecCA(); err != nil {
987987
util.LogFatalAndExit(err, "failed to init ovn ipsec CA")
988988
}

0 commit comments

Comments
 (0)