github.com/projectcontour/contour@v1.28.2/cmd/contour/certgen_test.go (about) 1 // Copyright Project Contour Authors 2 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // you may not use this file except in compliance with the License. 4 // You may obtain a copy of the License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package main 15 16 import ( 17 "crypto/x509" 18 "encoding/pem" 19 "fmt" 20 "os" 21 "path/filepath" 22 "sort" 23 "testing" 24 25 "github.com/projectcontour/contour/internal/certgen" 26 "github.com/projectcontour/contour/internal/dag" 27 "github.com/projectcontour/contour/pkg/certs" 28 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/require" 31 corev1 "k8s.io/api/core/v1" 32 ) 33 34 func TestGeneratedSecretsValid(t *testing.T) { 35 conf := certgenConfig{ 36 KubeConfig: "", 37 InCluster: false, 38 Namespace: "foo", 39 OutputDir: "", 40 OutputKube: false, 41 OutputYAML: false, 42 OutputPEM: false, 43 Lifetime: 0, 44 Overwrite: false, 45 } 46 47 certificates, err := certs.GenerateCerts( 48 &certs.Configuration{ 49 Lifetime: conf.Lifetime, 50 Namespace: conf.Namespace, 51 }) 52 require.NoError(t, err, "failed to generate certificates") 53 54 secrets, errs := certgen.AsSecrets(conf.Namespace, "", certificates) 55 if len(errs) > 0 { 56 t.Errorf("expected no errors, got %d", len(errs)) 57 } 58 if len(secrets) != 2 { 59 t.Errorf("expected 2 secrets, got %d", len(secrets)) 60 } 61 62 wantedNames := map[string][]string{ 63 "envoycert": { 64 "envoy", 65 fmt.Sprintf("envoy.%s", conf.Namespace), 66 fmt.Sprintf("envoy.%s.svc", conf.Namespace), 67 fmt.Sprintf("envoy.%s.svc.cluster.local", conf.Namespace), 68 }, 69 "contourcert": { 70 "contour", 71 fmt.Sprintf("contour.%s", conf.Namespace), 72 fmt.Sprintf("contour.%s.svc", conf.Namespace), 73 fmt.Sprintf("contour.%s.svc.cluster.local", conf.Namespace), 74 }, 75 } 76 77 for _, s := range secrets { 78 if _, ok := wantedNames[s.Name]; !ok { 79 t.Errorf("unexpected Secret name %q", s.Name) 80 continue 81 } 82 83 // Check the keys we want are present. 84 for _, key := range []string{ 85 dag.CACertificateKey, 86 corev1.TLSCertKey, 87 corev1.TLSPrivateKeyKey, 88 } { 89 if _, ok := s.Data[key]; !ok { 90 t.Errorf("missing data key %q", key) 91 } 92 } 93 94 pemBlock, _ := pem.Decode(s.Data[corev1.TLSCertKey]) 95 assert.Equal(t, "CERTIFICATE", pemBlock.Type) 96 97 cert, err := x509.ParseCertificate(pemBlock.Bytes) 98 if err != nil { 99 t.Errorf("failed to parse X509 certificate: %s", err) 100 } 101 102 // Check that each certificate contains SAN entries for the right DNS names. 103 sort.Strings(cert.DNSNames) 104 sort.Strings(wantedNames[s.Name]) 105 assert.Equal(t, cert.DNSNames, wantedNames[s.Name]) 106 107 } 108 } 109 110 func TestSecretNamePrefix(t *testing.T) { 111 conf := certgenConfig{ 112 KubeConfig: "", 113 InCluster: false, 114 Namespace: "foo", 115 OutputDir: "", 116 OutputKube: false, 117 OutputYAML: false, 118 OutputPEM: false, 119 Lifetime: 0, 120 Overwrite: false, 121 NameSuffix: "-testsuffix", 122 } 123 124 certificates, err := certs.GenerateCerts( 125 &certs.Configuration{ 126 Lifetime: conf.Lifetime, 127 Namespace: conf.Namespace, 128 }) 129 require.NoError(t, err, "failed to generate certificates") 130 131 secrets, errs := certgen.AsSecrets(conf.Namespace, conf.NameSuffix, certificates) 132 if len(errs) > 0 { 133 t.Errorf("expected no errors, got %d", len(errs)) 134 } 135 if len(secrets) != 2 { 136 t.Errorf("expected 2 secrets, got %d", len(secrets)) 137 } 138 139 wantedNames := map[string][]string{ 140 "envoycert-testsuffix": { 141 "envoy", 142 fmt.Sprintf("envoy.%s", conf.Namespace), 143 fmt.Sprintf("envoy.%s.svc", conf.Namespace), 144 fmt.Sprintf("envoy.%s.svc.cluster.local", conf.Namespace), 145 }, 146 "contourcert-testsuffix": { 147 "contour", 148 fmt.Sprintf("contour.%s", conf.Namespace), 149 fmt.Sprintf("contour.%s.svc", conf.Namespace), 150 fmt.Sprintf("contour.%s.svc.cluster.local", conf.Namespace), 151 }, 152 } 153 154 for _, s := range secrets { 155 if _, ok := wantedNames[s.Name]; !ok { 156 t.Errorf("unexpected Secret name %q", s.Name) 157 continue 158 } 159 160 // Check the keys we want are present. 161 for _, key := range []string{ 162 dag.CACertificateKey, 163 corev1.TLSCertKey, 164 corev1.TLSPrivateKeyKey, 165 } { 166 if _, ok := s.Data[key]; !ok { 167 t.Errorf("missing data key %q", key) 168 } 169 } 170 171 pemBlock, _ := pem.Decode(s.Data[corev1.TLSCertKey]) 172 assert.Equal(t, "CERTIFICATE", pemBlock.Type) 173 174 cert, err := x509.ParseCertificate(pemBlock.Bytes) 175 require.NoError(t, err, "failed to parse X509 certificate") 176 177 // Check that each certificate contains SAN entries for the right DNS names. 178 sort.Strings(cert.DNSNames) 179 sort.Strings(wantedNames[s.Name]) 180 assert.Equal(t, cert.DNSNames, wantedNames[s.Name]) 181 } 182 } 183 184 func TestInvalidNamespaceAndName(t *testing.T) { 185 conf := certgenConfig{ 186 KubeConfig: "", 187 InCluster: false, 188 Namespace: "foo!!", // contains invalid characters 189 OutputDir: "", 190 OutputKube: false, 191 OutputYAML: false, 192 OutputPEM: false, 193 Lifetime: 0, 194 Overwrite: false, 195 NameSuffix: "-testsuffix$", // contains invalid characters 196 } 197 198 certificates, err := certs.GenerateCerts( 199 &certs.Configuration{ 200 Lifetime: conf.Lifetime, 201 Namespace: conf.Namespace, 202 }) 203 require.NoError(t, err, "failed to generate certificates") 204 205 secrets, errs := certgen.AsSecrets(conf.Namespace, conf.NameSuffix, certificates) 206 if len(errs) != 2 { 207 t.Errorf("expected 2 errors, got %d", len(errs)) 208 } 209 if len(secrets) != 0 { 210 t.Errorf("expected no secrets, got %d", len(secrets)) 211 } 212 } 213 214 func TestOutputFileMode(t *testing.T) { 215 testCases := []struct { 216 name string 217 insecureFile string 218 cc *certgenConfig 219 }{ 220 { 221 name: "pem format no overwrite", 222 cc: &certgenConfig{ 223 OutputPEM: true, 224 Overwrite: false, 225 }, 226 }, 227 { 228 name: "pem format with overwrite", 229 insecureFile: "contourcert.pem", 230 cc: &certgenConfig{ 231 OutputPEM: true, 232 Overwrite: true, 233 }, 234 }, 235 { 236 name: "yaml format no overwrite", 237 cc: &certgenConfig{ 238 OutputYAML: true, 239 Overwrite: false, 240 Format: "legacy", 241 Namespace: "foo", 242 }, 243 }, 244 { 245 name: "yaml format with overwrite", 246 insecureFile: "contourcert.yaml", 247 cc: &certgenConfig{ 248 OutputYAML: true, 249 Overwrite: true, 250 Format: "legacy", 251 Namespace: "foo", 252 }, 253 }, 254 } 255 for _, tc := range testCases { 256 t.Run(tc.name, func(t *testing.T) { 257 outputDir, err := os.MkdirTemp("", "") 258 require.NoError(t, err) 259 defer os.RemoveAll(outputDir) 260 tc.cc.OutputDir = outputDir 261 262 // Write a file with insecure mode to ensure overwrite works as expected. 263 if tc.cc.Overwrite { 264 _, err = os.Create(filepath.Join(outputDir, tc.insecureFile)) 265 require.NoError(t, err) 266 } 267 268 generatedCerts, err := certs.GenerateCerts( 269 &certs.Configuration{ 270 Lifetime: tc.cc.Lifetime, 271 Namespace: tc.cc.Namespace, 272 }) 273 require.NoError(t, err) 274 275 require.NoError(t, OutputCerts(tc.cc, nil, generatedCerts)) 276 277 err = filepath.Walk(outputDir, func(path string, info os.FileInfo, err error) error { 278 if !info.IsDir() { 279 assert.Equal(t, os.FileMode(0o600), info.Mode(), "incorrect mode for file "+path) 280 } 281 return nil 282 }) 283 require.NoError(t, err) 284 }) 285 } 286 }