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  }