github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/ca/testutils/externalutils.go (about) 1 package testutils 2 3 import ( 4 "crypto/tls" 5 "encoding/json" 6 "fmt" 7 "net" 8 "net/http" 9 "net/url" 10 "path/filepath" 11 "strconv" 12 "sync" 13 "sync/atomic" 14 15 "github.com/cloudflare/cfssl/api" 16 "github.com/cloudflare/cfssl/config" 17 cfsslerrors "github.com/cloudflare/cfssl/errors" 18 "github.com/cloudflare/cfssl/signer" 19 "github.com/docker/swarmkit/ca" 20 "github.com/pkg/errors" 21 ) 22 23 var crossSignPolicy = config.SigningProfile{ 24 Usage: []string{"cert sign", "crl sign"}, 25 // we don't want the intermediate to last for very long 26 Expiry: ca.DefaultNodeCertExpiration, 27 Backdate: ca.CertBackdate, 28 CAConstraint: config.CAConstraint{IsCA: true}, 29 ExtensionWhitelist: map[string]bool{ 30 ca.BasicConstraintsOID.String(): true, 31 }, 32 } 33 34 // NewExternalSigningServer creates and runs a new ExternalSigningServer which 35 // uses the given rootCA to sign node certificates. A server key and cert are 36 // generated and saved into the given basedir and then a TLS listener is 37 // started on a random available port. On success, an HTTPS server will be 38 // running in a separate goroutine. The URL of the singing endpoint is 39 // available in the returned *ExternalSignerServer value. Calling the Close() 40 // method will stop the server. 41 func NewExternalSigningServer(rootCA ca.RootCA, basedir string) (*ExternalSigningServer, error) { 42 serverCN := "external-ca-example-server" 43 serverOU := "localhost" // Make a valid server cert for localhost. 44 45 s, err := rootCA.Signer() 46 if err != nil { 47 return nil, err 48 } 49 // create our own copy of the local signer so we don't mutate the rootCA's signer as we enable and disable CA signing 50 copiedSigner := *s 51 52 // Create TLS credentials for the external CA server which we will run. 53 serverPaths := ca.CertPaths{ 54 Cert: filepath.Join(basedir, "server.crt"), 55 Key: filepath.Join(basedir, "server.key"), 56 } 57 serverCert, _, err := rootCA.IssueAndSaveNewCertificates(ca.NewKeyReadWriter(serverPaths, nil, nil), serverCN, serverOU, "") 58 if err != nil { 59 return nil, errors.Wrap(err, "unable to get TLS server certificate") 60 } 61 62 serverTLSConfig := &tls.Config{ 63 Certificates: []tls.Certificate{*serverCert}, 64 ClientAuth: tls.RequireAndVerifyClientCert, 65 ClientCAs: rootCA.Pool, 66 } 67 68 tlsListener, err := tls.Listen("tcp", "localhost:0", serverTLSConfig) 69 if err != nil { 70 return nil, errors.Wrap(err, "unable to create TLS connection listener") 71 } 72 73 assignedPort := tlsListener.Addr().(*net.TCPAddr).Port 74 75 signURL := url.URL{ 76 Scheme: "https", 77 Host: net.JoinHostPort("localhost", strconv.Itoa(assignedPort)), 78 Path: "/sign", 79 } 80 81 ess := &ExternalSigningServer{ 82 listener: tlsListener, 83 URL: signURL.String(), 84 } 85 86 mux := http.NewServeMux() 87 handler := &signHandler{ 88 numIssued: &ess.NumIssued, 89 localSigner: &copiedSigner, 90 origPolicy: copiedSigner.Policy(), 91 flaky: &ess.flaky, 92 } 93 mux.Handle(signURL.Path, handler) 94 ess.handler = handler 95 96 server := &http.Server{ 97 Handler: mux, 98 } 99 100 go server.Serve(tlsListener) 101 102 return ess, nil 103 } 104 105 // ExternalSigningServer runs an HTTPS server with an endpoint at a specified 106 // URL which signs node certificate requests from a swarm manager client. 107 type ExternalSigningServer struct { 108 listener net.Listener 109 NumIssued uint64 110 URL string 111 flaky uint32 112 handler *signHandler 113 } 114 115 // Stop stops this signing server by closing the underlying TCP/TLS listener. 116 func (ess *ExternalSigningServer) Stop() error { 117 return ess.listener.Close() 118 } 119 120 // Flake makes the signing server return HTTP 500 errors. 121 func (ess *ExternalSigningServer) Flake() { 122 atomic.StoreUint32(&ess.flaky, 1) 123 } 124 125 // Deflake restores normal operation after a call to Flake. 126 func (ess *ExternalSigningServer) Deflake() { 127 atomic.StoreUint32(&ess.flaky, 0) 128 } 129 130 // EnableCASigning updates the root CA signer to be able to sign CAs 131 func (ess *ExternalSigningServer) EnableCASigning() error { 132 ess.handler.mu.Lock() 133 defer ess.handler.mu.Unlock() 134 135 copied := *ess.handler.origPolicy 136 if copied.Profiles == nil { 137 copied.Profiles = make(map[string]*config.SigningProfile) 138 } 139 copied.Profiles[ca.ExternalCrossSignProfile] = &crossSignPolicy 140 141 ess.handler.localSigner.SetPolicy(&copied) 142 return nil 143 } 144 145 // DisableCASigning prevents the server from being able to sign CA certificates 146 func (ess *ExternalSigningServer) DisableCASigning() { 147 ess.handler.mu.Lock() 148 defer ess.handler.mu.Unlock() 149 ess.handler.localSigner.SetPolicy(ess.handler.origPolicy) 150 } 151 152 type signHandler struct { 153 mu sync.Mutex 154 numIssued *uint64 155 flaky *uint32 156 localSigner *ca.LocalSigner 157 origPolicy *config.Signing 158 } 159 160 func (h *signHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 161 if atomic.LoadUint32(h.flaky) == 1 { 162 w.WriteHeader(http.StatusInternalServerError) 163 } 164 165 // Check client authentication via mutual TLS. 166 if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 { 167 cfsslErr := cfsslerrors.New(cfsslerrors.APIClientError, cfsslerrors.AuthenticationFailure) 168 errResponse := api.NewErrorResponse("must authenticate sign request with mutual TLS", cfsslErr.ErrorCode) 169 json.NewEncoder(w).Encode(errResponse) 170 return 171 } 172 173 clientSub := r.TLS.PeerCertificates[0].Subject 174 175 // The client certificate OU should be for a swarm manager. 176 if len(clientSub.OrganizationalUnit) == 0 || clientSub.OrganizationalUnit[0] != ca.ManagerRole { 177 cfsslErr := cfsslerrors.New(cfsslerrors.APIClientError, cfsslerrors.AuthenticationFailure) 178 errResponse := api.NewErrorResponse(fmt.Sprintf("client certificate OU must be %q", ca.ManagerRole), cfsslErr.ErrorCode) 179 json.NewEncoder(w).Encode(errResponse) 180 return 181 } 182 183 // The client certificate must have an Org. 184 if len(clientSub.Organization) == 0 { 185 cfsslErr := cfsslerrors.New(cfsslerrors.APIClientError, cfsslerrors.AuthenticationFailure) 186 errResponse := api.NewErrorResponse("client certificate must have an Organization", cfsslErr.ErrorCode) 187 json.NewEncoder(w).Encode(errResponse) 188 return 189 } 190 clientOrg := clientSub.Organization[0] 191 192 // Decode the certificate signing request. 193 var signReq signer.SignRequest 194 if err := json.NewDecoder(r.Body).Decode(&signReq); err != nil { 195 cfsslErr := cfsslerrors.New(cfsslerrors.APIClientError, cfsslerrors.JSONError) 196 errResponse := api.NewErrorResponse(fmt.Sprintf("unable to decode sign request: %s", err), cfsslErr.ErrorCode) 197 json.NewEncoder(w).Encode(errResponse) 198 return 199 } 200 201 // The signReq should have additional subject info. 202 reqSub := signReq.Subject 203 if reqSub == nil { 204 cfsslErr := cfsslerrors.New(cfsslerrors.CSRError, cfsslerrors.BadRequest) 205 errResponse := api.NewErrorResponse("sign request must contain a subject field", cfsslErr.ErrorCode) 206 json.NewEncoder(w).Encode(errResponse) 207 return 208 } 209 210 if signReq.Profile != ca.ExternalCrossSignProfile { 211 // The client's Org should match the Org in the sign request subject. 212 if len(reqSub.Name().Organization) == 0 || reqSub.Name().Organization[0] != clientOrg { 213 cfsslErr := cfsslerrors.New(cfsslerrors.CSRError, cfsslerrors.BadRequest) 214 errResponse := api.NewErrorResponse("sign request subject org does not match client certificate org", cfsslErr.ErrorCode) 215 json.NewEncoder(w).Encode(errResponse) 216 return 217 } 218 } 219 220 // Sign the requested certificate. 221 certPEM, err := h.localSigner.Sign(signReq) 222 if err != nil { 223 cfsslErr := cfsslerrors.New(cfsslerrors.APIClientError, cfsslerrors.ServerRequestFailed) 224 errResponse := api.NewErrorResponse(fmt.Sprintf("unable to sign requested certificate: %s", err), cfsslErr.ErrorCode) 225 json.NewEncoder(w).Encode(errResponse) 226 return 227 } 228 229 result := map[string]string{ 230 "certificate": string(certPEM), 231 } 232 233 // Increment the number of certs issued. 234 atomic.AddUint64(h.numIssued, 1) 235 236 // Return a successful JSON response. 237 json.NewEncoder(w).Encode(api.NewSuccessResponse(result)) 238 }