github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/ca/external_test.go (about) 1 package ca_test 2 3 import ( 4 "context" 5 "crypto/x509" 6 "net" 7 "net/http" 8 "net/http/httptest" 9 "testing" 10 "time" 11 12 "github.com/cloudflare/cfssl/helpers" 13 "github.com/docker/swarmkit/ca" 14 "github.com/docker/swarmkit/ca/testutils" 15 "github.com/docker/swarmkit/log" 16 "github.com/sirupsen/logrus" 17 "github.com/stretchr/testify/require" 18 ) 19 20 // Tests ExternalCA.CrossSignRootCA can produce an intermediate that can be used to 21 // validate a leaf certificate 22 func TestExternalCACrossSign(t *testing.T) { 23 t.Parallel() 24 25 if !testutils.External { 26 return // this is only tested using the external CA 27 } 28 29 tc := testutils.NewTestCA(t) 30 defer tc.Stop() 31 paths := ca.NewConfigPaths(tc.TempDir) 32 33 secConfig, cancel, err := tc.RootCA.CreateSecurityConfig(tc.Context, 34 ca.NewKeyReadWriter(paths.Node, nil, nil), ca.CertificateRequestConfig{}) 35 require.NoError(t, err) 36 cancel() 37 38 externalCA := ca.NewExternalCA(nil, 39 ca.NewExternalCATLSConfig(secConfig.ClientTLSCreds.Config().Certificates, tc.RootCA.Pool), 40 tc.ExternalSigningServer.URL) 41 42 for _, testcase := range []struct{ cert, key []byte }{ 43 { 44 cert: testutils.ECDSA256SHA256Cert, 45 key: testutils.ECDSA256Key, 46 }, 47 { 48 cert: testutils.RSA2048SHA256Cert, 49 key: testutils.RSA2048Key, 50 }, 51 } { 52 rootCA2, err := ca.NewRootCA(testcase.cert, testcase.cert, testcase.key, ca.DefaultNodeCertExpiration, nil) 53 require.NoError(t, err) 54 55 krw := ca.NewKeyReadWriter(paths.Node, nil, nil) 56 57 _, _, err = rootCA2.IssueAndSaveNewCertificates(krw, "cn", "ou", "org") 58 require.NoError(t, err) 59 certBytes, _, err := krw.Read() 60 require.NoError(t, err) 61 leafCert, err := helpers.ParseCertificatePEM(certBytes) 62 require.NoError(t, err) 63 64 // we have not enabled CA signing on the external server 65 tc.ExternalSigningServer.DisableCASigning() 66 _, err = externalCA.CrossSignRootCA(tc.Context, rootCA2) 67 require.Error(t, err) 68 69 require.NoError(t, tc.ExternalSigningServer.EnableCASigning()) 70 71 intermediate, err := externalCA.CrossSignRootCA(tc.Context, rootCA2) 72 require.NoError(t, err) 73 74 parsedIntermediate, err := helpers.ParseCertificatePEM(intermediate) 75 require.NoError(t, err) 76 parsedRoot2, err := helpers.ParseCertificatePEM(testcase.cert) 77 require.NoError(t, err) 78 require.Equal(t, parsedRoot2.RawSubject, parsedIntermediate.RawSubject) 79 require.Equal(t, parsedRoot2.RawSubjectPublicKeyInfo, parsedIntermediate.RawSubjectPublicKeyInfo) 80 require.True(t, parsedIntermediate.IsCA) 81 82 intermediatePool := x509.NewCertPool() 83 intermediatePool.AddCert(parsedIntermediate) 84 85 // we can validate a chain from the leaf to the first root through the intermediate, 86 // or from the leaf cert to the second root with or without the intermediate 87 _, err = leafCert.Verify(x509.VerifyOptions{Roots: tc.RootCA.Pool}) 88 require.Error(t, err) 89 _, err = leafCert.Verify(x509.VerifyOptions{Roots: tc.RootCA.Pool, Intermediates: intermediatePool}) 90 require.NoError(t, err) 91 92 _, err = leafCert.Verify(x509.VerifyOptions{Roots: rootCA2.Pool}) 93 require.NoError(t, err) 94 _, err = leafCert.Verify(x509.VerifyOptions{Roots: rootCA2.Pool, Intermediates: intermediatePool}) 95 require.NoError(t, err) 96 } 97 } 98 99 func TestExternalCASignRequestTimesOut(t *testing.T) { 100 t.Parallel() 101 102 if testutils.External { 103 return // this does not require the external CA in any way 104 } 105 106 ctx := log.WithLogger(context.Background(), log.L.WithFields(logrus.Fields{ 107 "testname": t.Name(), 108 "testHasExternalCA": false, 109 })) 110 111 signDone, allDone := make(chan error), make(chan struct{}) 112 defer close(signDone) 113 mux := http.NewServeMux() 114 mux.HandleFunc("/", func(http.ResponseWriter, *http.Request) { 115 // hang forever 116 <-allDone 117 }) 118 119 server := httptest.NewServer(mux) 120 defer server.Close() 121 defer server.CloseClientConnections() 122 defer close(allDone) 123 124 csr, _, err := ca.GenerateNewCSR() 125 require.NoError(t, err) 126 127 externalCA := ca.NewExternalCA(nil, nil, server.URL) 128 externalCA.ExternalRequestTimeout = time.Second 129 go func() { 130 _, err := externalCA.Sign(ctx, ca.PrepareCSR(csr, "cn", "ou", "org")) 131 select { 132 case <-allDone: 133 case signDone <- err: 134 } 135 }() 136 137 select { 138 case err = <-signDone: 139 require.Contains(t, err.Error(), context.DeadlineExceeded.Error()) 140 case <-time.After(3 * time.Second): 141 require.FailNow(t, "call to external CA signing should have timed out after 1 second - it's been 3") 142 } 143 } 144 145 // The ExternalCA object will stop reading the response from the server past a 146 // a certain size 147 func TestExternalCASignRequestSizeLimit(t *testing.T) { 148 t.Parallel() 149 150 if testutils.External { 151 return // this does not require the external CA in any way 152 } 153 154 ctx := log.WithLogger(context.Background(), log.L.WithFields(logrus.Fields{ 155 "testname": t.Name(), 156 "testHasExternalCA": false, 157 })) 158 159 rootCA, err := ca.CreateRootCA("rootCN") 160 require.NoError(t, err) 161 162 signDone, allDone, writeDone := make(chan error), make(chan struct{}), make(chan error) 163 defer close(signDone) 164 mux := http.NewServeMux() 165 166 mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { 167 garbage := []byte("abcdefghijklmnopqrstuvwxyz") 168 // keep writing until done 169 for { 170 select { 171 case <-allDone: 172 return 173 default: 174 if _, err := w.Write(garbage); err != nil { 175 writeDone <- err 176 return 177 } 178 } 179 } 180 }) 181 182 server := httptest.NewServer(mux) 183 defer server.Close() 184 defer server.CloseClientConnections() 185 defer close(allDone) 186 187 csr, _, err := ca.GenerateNewCSR() 188 require.NoError(t, err) 189 190 externalCA := ca.NewExternalCA(rootCA.Intermediates, nil, server.URL) 191 externalCA.ExternalRequestTimeout = time.Second 192 go func() { 193 _, err := externalCA.Sign(ctx, ca.PrepareCSR(csr, "cn", "ou", "org")) 194 select { 195 case <-allDone: 196 case signDone <- err: 197 } 198 }() 199 200 select { 201 case err = <-signDone: 202 require.Error(t, err) 203 require.Contains(t, err.Error(), "unable to parse JSON response") 204 case <-time.After(2 * time.Second): 205 require.FailNow(t, "call to external CA signing should have failed by now") 206 } 207 208 select { 209 case err := <-writeDone: 210 // due to buffering/client disconnecting, we don't know how much was written to the TCP socket, 211 // but the client should have terminated the connection after receiving the max amount, so the 212 // request should have finished and the write to the socket failed. 213 require.Error(t, err) 214 require.IsType(t, &net.OpError{}, err) 215 case <-time.After(time.Second): 216 require.FailNow(t, "the client connection to the server should have been closed by now") 217 } 218 }