Skip to content

Commit 1a3ee85

Browse files
authored
ingress-controller: extract certificate and route names from config (#1405)
## Summary As part of provisioning missing certificates we will need to extract the certificate names from any existing certificates and match them with route names. ## Related issues - [ENG-4006](https://linear.app/pomerium/issue/ENG-4006/ingress-controller-add-function-to-convert-databroker-entities-to) ## Checklist - [x] reference any related issues - [ ] updated docs - [x] updated unit tests - [ ] updated UPGRADING.md - [ ] add appropriate tag (`improvement` / `bug` / etc) - [x] ready for review
1 parent c58c661 commit 1a3ee85

4 files changed

Lines changed: 609 additions & 0 deletions

File tree

internal/certificate/convert.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package certificate
2+
3+
import (
4+
"crypto/x509"
5+
"encoding/pem"
6+
"iter"
7+
"net/netip"
8+
"net/url"
9+
"slices"
10+
"strings"
11+
12+
"github.com/hashicorp/go-set/v3"
13+
14+
configpb "github.com/pomerium/pomerium/pkg/grpc/config"
15+
)
16+
17+
// GetNamesFromConfig gets all the certificate and route host names from
18+
// config.
19+
func GetNamesFromConfig(
20+
keyPairs []*configpb.KeyPair,
21+
routes []*configpb.Route,
22+
settings []*configpb.Settings,
23+
) (certificateNames []string, routeNames []string) {
24+
allCertificateNames := set.New[string](0)
25+
addCertificatesFromPEM := func(data []byte) {
26+
for c := range IterateServerCertificatesFromPEM(data) {
27+
for _, n := range c.DNSNames {
28+
if n != "" {
29+
allCertificateNames.Insert(n)
30+
}
31+
}
32+
}
33+
}
34+
for _, kp := range keyPairs {
35+
addCertificatesFromPEM(kp.GetCertificate())
36+
}
37+
for _, s := range settings {
38+
for _, sc := range s.Certificates {
39+
addCertificatesFromPEM(sc.GetCertBytes())
40+
}
41+
}
42+
43+
allRouteNames := set.New[string](0)
44+
for _, r := range routes {
45+
u, err := url.Parse(r.From)
46+
if err != nil {
47+
continue
48+
}
49+
50+
// ignore ip addresses
51+
if _, err := netip.ParseAddr(u.Hostname()); err == nil {
52+
continue
53+
}
54+
55+
// ignore non-secure URLs
56+
if !strings.Contains(u.Scheme, "https") {
57+
continue
58+
}
59+
60+
// ignore wildcard routes
61+
if strings.Contains(u.Hostname(), "*") || u.Hostname() == "" {
62+
continue
63+
}
64+
65+
allRouteNames.Insert(u.Hostname())
66+
}
67+
68+
return slices.Sorted(allCertificateNames.Items()), slices.Sorted(allRouteNames.Items())
69+
}
70+
71+
// IterateServerCertificatesFromPEM iterates all of the server certificates
72+
// found in raw PEM data.
73+
func IterateServerCertificatesFromPEM(data []byte) iter.Seq[*x509.Certificate] {
74+
return func(yield func(*x509.Certificate) bool) {
75+
for {
76+
var block *pem.Block
77+
block, data = pem.Decode(data)
78+
if block == nil {
79+
break
80+
}
81+
82+
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
83+
continue
84+
}
85+
86+
cert, err := x509.ParseCertificate(block.Bytes)
87+
if err != nil {
88+
continue
89+
}
90+
91+
if !slices.Contains(cert.ExtKeyUsage, x509.ExtKeyUsageServerAuth) {
92+
continue
93+
}
94+
95+
if !yield(cert) {
96+
return
97+
}
98+
}
99+
}
100+
}
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
package certificate_test
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
10+
"github.com/pomerium/ingress-controller/internal/certificate"
11+
configpb "github.com/pomerium/pomerium/pkg/grpc/config"
12+
)
13+
14+
func TestGetNamesFromConfig(t *testing.T) {
15+
t.Parallel()
16+
17+
amazonData, err := os.ReadFile("testdata/amazon.pem")
18+
require.NoError(t, err)
19+
20+
googleData, err := os.ReadFile("testdata/google.pem")
21+
require.NoError(t, err)
22+
23+
certificateNames, routeNames := certificate.GetNamesFromConfig(
24+
[]*configpb.KeyPair{{Certificate: amazonData}},
25+
[]*configpb.Route{
26+
{From: "https://www.example.com"},
27+
{From: "https://127.0.0.1"},
28+
{From: "http://insecure.example.com"},
29+
{From: "ssh://ssh.example.com"},
30+
{From: "https://*.wildcard.example.com"},
31+
{From: "https://api.example.com"},
32+
{},
33+
{From: "https://api.example.com:8443"},
34+
{From: "<NOT A URL>"},
35+
},
36+
[]*configpb.Settings{{Certificates: []*configpb.Settings_Certificate{{CertBytes: googleData}}}},
37+
)
38+
assert.Equal(t, []string{
39+
"*.2mdn-cn.net",
40+
"*.aa.peg.a2z.com",
41+
"*.ab.peg.a2z.com",
42+
"*.ac.peg.a2z.com",
43+
"*.admob-cn.com",
44+
"*.aistudio.google.com",
45+
"*.ampproject.net.cn",
46+
"*.ampproject.org.cn",
47+
"*.android.com",
48+
"*.android.google.cn",
49+
"*.app-measurement-cn.com",
50+
"*.appengine.google.com",
51+
"*.bdn.dev",
52+
"*.bz.peg.a2z.com",
53+
"*.chrome.google.cn",
54+
"*.cloud.google.com",
55+
"*.crowdsource.google.com",
56+
"*.dartsearch-cn.net",
57+
"*.datacompute.google.com",
58+
"*.developers.google.cn",
59+
"*.doubleclick-cn.net",
60+
"*.doubleclick.cn",
61+
"*.flash.android.com",
62+
"*.fls.doubleclick-cn.net",
63+
"*.fls.doubleclick.cn",
64+
"*.g.cn",
65+
"*.g.co",
66+
"*.g.doubleclick-cn.net",
67+
"*.g.doubleclick.cn",
68+
"*.gcp.gvt2.com",
69+
"*.gcpcdn.gvt1.com",
70+
"*.gemini.cloud.google.com",
71+
"*.ggpht.cn",
72+
"*.gkecnapps.cn",
73+
"*.google-analytics-cn.com",
74+
"*.google-analytics.com",
75+
"*.google.ca",
76+
"*.google.cl",
77+
"*.google.co.in",
78+
"*.google.co.jp",
79+
"*.google.co.uk",
80+
"*.google.com",
81+
"*.google.com.ar",
82+
"*.google.com.au",
83+
"*.google.com.br",
84+
"*.google.com.co",
85+
"*.google.com.mx",
86+
"*.google.com.tr",
87+
"*.google.com.vn",
88+
"*.google.de",
89+
"*.google.es",
90+
"*.google.fr",
91+
"*.google.hu",
92+
"*.google.it",
93+
"*.google.nl",
94+
"*.google.pl",
95+
"*.google.pt",
96+
"*.googleadservices-cn.com",
97+
"*.googleapis-cn.com",
98+
"*.googleapis.cn",
99+
"*.googleapps-cn.com",
100+
"*.googlecnapps.cn",
101+
"*.googlecommerce.com",
102+
"*.googledownloads.cn",
103+
"*.googleflights-cn.net",
104+
"*.googleoptimize-cn.com",
105+
"*.googlesandbox-cn.com",
106+
"*.googlesyndication-cn.com",
107+
"*.googletagmanager-cn.com",
108+
"*.googletagservices-cn.com",
109+
"*.googletraveladservices-cn.com",
110+
"*.googlevads-cn.com",
111+
"*.gstatic-cn.com",
112+
"*.gstatic.cn",
113+
"*.gstatic.com",
114+
"*.gvt1-cn.com",
115+
"*.gvt1.com",
116+
"*.gvt2-cn.com",
117+
"*.gvt2.com",
118+
"*.metric.gstatic.com",
119+
"*.music.youtube.com",
120+
"*.origin-test.bdn.dev",
121+
"*.peg.a2z.com",
122+
"*.recaptcha-cn.net",
123+
"*.recaptcha.net.cn",
124+
"*.safeframe.googlesyndication-cn.com",
125+
"*.safenup.googlesandbox-cn.com",
126+
"*.urchin.com",
127+
"*.url.google.com",
128+
"*.widevine.cn",
129+
"*.youtube-nocookie.com",
130+
"*.youtube.com",
131+
"*.youtubeeducation.com",
132+
"*.youtubekids.com",
133+
"*.yt.be",
134+
"*.ytimg.com",
135+
"2mdn-cn.net",
136+
"admob-cn.com",
137+
"ai.android",
138+
"amazon.co.jp",
139+
"amazon.co.uk",
140+
"amazon.com",
141+
"amazon.com.au",
142+
"amazon.de",
143+
"amazon.jp",
144+
"ampproject.net.cn",
145+
"ampproject.org.cn",
146+
"amzn.com",
147+
"android.clients.google.com",
148+
"android.com",
149+
"app-measurement-cn.com",
150+
"buckeye-retail-website.amazon.com",
151+
"buybox.amazon.com",
152+
"corporate.amazon.com",
153+
"dartsearch-cn.net",
154+
"doubleclick-cn.net",
155+
"doubleclick.cn",
156+
"edgeflow-dp.aero.04f01a85e-frontier.amazon.com.au",
157+
"edgeflow-dp.aero.47cf2c8c9-frontier.amazon.com",
158+
"edgeflow-dp.aero.4d5ad1d2b-frontier.amazon.co.jp",
159+
"edgeflow-dp.aero.abe2c2f23-frontier.amazon.de",
160+
"edgeflow-dp.aero.bfbdc3ca1-frontier.amazon.co.uk",
161+
"edgeflow.aero.04f01a85e-frontier.amazon.com.au",
162+
"edgeflow.aero.47cf2c8c9-frontier.amazon.com",
163+
"edgeflow.aero.4d5ad1d2b-frontier.amazon.co.jp",
164+
"edgeflow.aero.abe2c2f23-frontier.amazon.de",
165+
"edgeflow.aero.bfbdc3ca1-frontier.amazon.co.uk",
166+
"g.cn",
167+
"g.co",
168+
"ggpht.cn",
169+
"gkecnapps.cn",
170+
"goo.gl",
171+
"google-analytics-cn.com",
172+
"google-analytics.com",
173+
"google.com",
174+
"googleadservices-cn.com",
175+
"googleapis-cn.com",
176+
"googleapps-cn.com",
177+
"googlecnapps.cn",
178+
"googlecommerce.com",
179+
"googledownloads.cn",
180+
"googleflights-cn.net",
181+
"googleoptimize-cn.com",
182+
"googlesandbox-cn.com",
183+
"googlesyndication-cn.com",
184+
"googletagmanager-cn.com",
185+
"googletagservices-cn.com",
186+
"googletraveladservices-cn.com",
187+
"googlevads-cn.com",
188+
"gvt1-cn.com",
189+
"gvt2-cn.com",
190+
"home.amazon.com",
191+
"huddles.amazon.com",
192+
"iphone.amazon.com",
193+
"music.youtube.com",
194+
"origin-www.amazon.co.jp",
195+
"origin-www.amazon.co.uk",
196+
"origin-www.amazon.com",
197+
"origin-www.amazon.com.au",
198+
"origin-www.amazon.de",
199+
"origin2-www.amazon.co.jp",
200+
"origin2-www.amazon.com",
201+
"recaptcha-cn.net",
202+
"recaptcha.net.cn",
203+
"shop.business.amazon.com",
204+
"uedata.amazon.co.uk",
205+
"uedata.amazon.com",
206+
"urchin.com",
207+
"us.amazon.com",
208+
"widevine.cn",
209+
"www.amazon.co.jp",
210+
"www.amazon.co.uk",
211+
"www.amazon.com",
212+
"www.amazon.com.au",
213+
"www.amazon.de",
214+
"www.amazon.jp",
215+
"www.amzn.com",
216+
"www.goo.gl",
217+
"youtu.be",
218+
"youtube.com",
219+
"youtubeeducation.com",
220+
"youtubekids.com",
221+
"yp.amazon.com",
222+
"yt.be",
223+
}, certificateNames)
224+
assert.Equal(t, []string{
225+
"api.example.com",
226+
"www.example.com",
227+
}, routeNames)
228+
}
229+
230+
func TestIterateServerCertificatesFromPEM(t *testing.T) {
231+
t.Parallel()
232+
233+
amazonData, err := os.ReadFile("testdata/amazon.pem")
234+
require.NoError(t, err)
235+
236+
googleData, err := os.ReadFile("testdata/google.pem")
237+
require.NoError(t, err)
238+
239+
var serialNumbers []string
240+
for cert := range certificate.IterateServerCertificatesFromPEM(amazonData) {
241+
serialNumbers = append(serialNumbers, cert.SerialNumber.String())
242+
}
243+
for cert := range certificate.IterateServerCertificatesFromPEM(googleData) {
244+
serialNumbers = append(serialNumbers, cert.SerialNumber.String())
245+
}
246+
assert.Equal(t, []string{
247+
"17254315543325975286547466867573710433",
248+
"186718756357656129965259402690993979140",
249+
"170058220837755766831192027518741805976",
250+
}, serialNumbers)
251+
}

0 commit comments

Comments
 (0)