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  }