Skip to content

Commit fb513e0

Browse files
committed
[cert] Make sure DNSNames and IPAddresses are sorted
To reduce possibility of changing certs, make sure lists of DNSNames and IPAddresses are sorted. Signed-off-by: Martin Schuppert <[email protected]>
1 parent 7fd3da6 commit fb513e0

File tree

4 files changed

+208
-1
lines changed

4 files changed

+208
-1
lines changed

modules/certmanager/certificate.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ package certmanager
1919
import (
2020
"context"
2121
"fmt"
22+
"sort"
2223
"time"
2324

2425
certmgrv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
2526
certmgrmetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
2627
"github.com/openstack-k8s-operators/lib-common/modules/common/helper"
28+
"github.com/openstack-k8s-operators/lib-common/modules/common/net"
2729
"github.com/openstack-k8s-operators/lib-common/modules/common/secret"
2830
"github.com/openstack-k8s-operators/lib-common/modules/common/service"
2931
"github.com/openstack-k8s-operators/lib-common/modules/common/util"
@@ -66,10 +68,15 @@ func NewCertificate(
6668
certificate *certmgrv1.Certificate,
6769
timeout time.Duration,
6870
) *Certificate {
69-
return &Certificate{
71+
crt := &Certificate{
7072
certificate: certificate,
7173
timeout: timeout,
7274
}
75+
76+
crt.certificate.Spec.IPAddresses = net.SortIPs(crt.certificate.Spec.IPAddresses)
77+
sort.Strings(crt.certificate.Spec.DNSNames)
78+
79+
return crt
7380
}
7481

7582
// Cert returns an initialized certificate request obj.

modules/certmanager/test/functional/certmanager_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,66 @@ var _ = Describe("certmanager module", func() {
166166
Expect(cert.Labels["f"]).To(Equal("l"))
167167
})
168168

169+
It("creates certificate with orderdered DNSNames", func() {
170+
c := certmanager.NewCertificate(
171+
certmanager.Cert(
172+
names.CertName.Name,
173+
names.CertName.Namespace,
174+
map[string]string{"f": "l"},
175+
certmgrv1.CertificateSpec{
176+
CommonName: "keystone-public-openstack.apps-crc.testing",
177+
DNSNames: []string{
178+
"keystone-public-openstack.apps-crc.testing",
179+
"keystone-public-openstack",
180+
},
181+
IssuerRef: certmgrmetav1.ObjectReference{
182+
Kind: "Issuer",
183+
Name: "issuerName",
184+
},
185+
SecretName: "secret",
186+
},
187+
),
188+
timeout,
189+
)
190+
191+
_, _, err := c.CreateOrPatch(ctx, h, nil)
192+
Expect(err).ShouldNot(HaveOccurred())
193+
cert := th.GetCert(names.CertName)
194+
Expect(cert.Spec.DNSNames[0]).To(Equal("keystone-public-openstack"))
195+
Expect(cert.Spec.DNSNames[1]).To(Equal("keystone-public-openstack.apps-crc.testing"))
196+
})
197+
198+
It("creates certificate with orderdered IPAddresses", func() {
199+
c := certmanager.NewCertificate(
200+
certmanager.Cert(
201+
names.CertName.Name,
202+
names.CertName.Namespace,
203+
map[string]string{"f": "l"},
204+
certmgrv1.CertificateSpec{
205+
CommonName: "keystone-public-openstack.apps-crc.testing",
206+
IPAddresses: []string{
207+
"2.2.2.2",
208+
"1.1.1.1",
209+
"2.2.2.1",
210+
},
211+
IssuerRef: certmgrmetav1.ObjectReference{
212+
Kind: "Issuer",
213+
Name: "issuerName",
214+
},
215+
SecretName: "secret",
216+
},
217+
),
218+
timeout,
219+
)
220+
221+
_, _, err := c.CreateOrPatch(ctx, h, nil)
222+
Expect(err).ShouldNot(HaveOccurred())
223+
cert := th.GetCert(names.CertName)
224+
Expect(cert.Spec.IPAddresses[0]).To(Equal("1.1.1.1"))
225+
Expect(cert.Spec.IPAddresses[1]).To(Equal("2.2.2.1"))
226+
Expect(cert.Spec.IPAddresses[2]).To(Equal("2.2.2.2"))
227+
})
228+
169229
It("deletes certificate", func() {
170230
c := certmanager.NewCertificate(
171231
certmanager.Cert(

modules/common/net/ip.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
Copyright 2023 Red Hat
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package net
18+
19+
import (
20+
"bytes"
21+
"net"
22+
"sort"
23+
)
24+
25+
// SortIPs - Get network-attachment-definition with name in namespace
26+
func SortIPs(
27+
ips []string,
28+
) []string {
29+
netIPs := make([]net.IP, 0, len(ips))
30+
31+
for _, ip := range ips {
32+
netIPs = append(netIPs, net.ParseIP(ip))
33+
}
34+
35+
sort.Slice(netIPs, func(i, j int) bool {
36+
return bytes.Compare(netIPs[i], netIPs[j]) < 0
37+
})
38+
39+
sortedIPs := make([]string, 0, len(netIPs))
40+
41+
for _, ip := range netIPs {
42+
sortedIPs = append(sortedIPs, ip.String())
43+
}
44+
45+
return sortedIPs
46+
}

modules/common/net/ip_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
Copyright 2023 Red Hat
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package net
18+
19+
import (
20+
"testing"
21+
22+
. "github.com/onsi/gomega"
23+
)
24+
25+
func TestSortIPs(t *testing.T) {
26+
27+
tests := []struct {
28+
name string
29+
ips []string
30+
want []string
31+
}{
32+
{
33+
name: "empty ip list",
34+
ips: []string{},
35+
want: []string{},
36+
},
37+
{
38+
name: "IPv4 - single ip in list",
39+
ips: []string{"1.1.1.1"},
40+
want: []string{"1.1.1.1"},
41+
},
42+
{
43+
name: "IPv4 - already sorted list",
44+
ips: []string{"1.1.1.1", "2.2.2.2"},
45+
want: []string{"1.1.1.1", "2.2.2.2"},
46+
},
47+
{
48+
name: "IPv4 - unsorted sorted list",
49+
ips: []string{"2.2.2.2", "1.1.1.1"},
50+
want: []string{"1.1.1.1", "2.2.2.2"},
51+
},
52+
{
53+
name: "IPv4 - another unsorted sorted list",
54+
ips: []string{"2.2.2.2", "1.1.1.2", "1.1.1.1"},
55+
want: []string{"1.1.1.1", "1.1.1.2", "2.2.2.2"},
56+
},
57+
{
58+
name: "IPv6 - single ip in list",
59+
ips: []string{"fd00:bbbb::1"},
60+
want: []string{"fd00:bbbb::1"},
61+
},
62+
{
63+
name: "IPv6 - already sorted list",
64+
ips: []string{"fd00:bbbb::1", "fd00:bbbb::2"},
65+
want: []string{"fd00:bbbb::1", "fd00:bbbb::2"},
66+
},
67+
{
68+
name: "IPv6 - unsorted sorted list",
69+
ips: []string{"fd00:bbbb::2", "fd00:bbbb::1"},
70+
want: []string{"fd00:bbbb::1", "fd00:bbbb::2"},
71+
},
72+
{
73+
name: "IPv6 - another unsorted sorted list",
74+
ips: []string{"fd00:bbbb::2", "fd00:aaaa::1", "fd00:bbbb::1"},
75+
want: []string{"fd00:aaaa::1", "fd00:bbbb::1", "fd00:bbbb::2"},
76+
},
77+
{
78+
name: "IPV4 and IPv6 - unsorted sorted list",
79+
ips: []string{"fd00:bbbb::2", "fd00:aaaa::1", "fd00:bbbb::1", "1.1.1.1"},
80+
want: []string{"1.1.1.1", "fd00:aaaa::1", "fd00:bbbb::1", "fd00:bbbb::2"},
81+
},
82+
}
83+
84+
for _, tt := range tests {
85+
t.Run(tt.name, func(t *testing.T) {
86+
g := NewWithT(t)
87+
88+
sortedIPs := SortIPs(tt.ips)
89+
g.Expect(sortedIPs).NotTo(BeNil())
90+
g.Expect(sortedIPs).To(HaveLen(len(tt.want)))
91+
g.Expect(sortedIPs).To(BeEquivalentTo(tt.want))
92+
})
93+
}
94+
}

0 commit comments

Comments
 (0)