Tizen_4.0 base
[platform/upstream/docker-engine.git] / vendor / github.com / docker / swarmkit / ca / external.go
1 package ca
2
3 import (
4         "bytes"
5         cryptorand "crypto/rand"
6         "crypto/tls"
7         "crypto/x509"
8         "encoding/hex"
9         "encoding/json"
10         "encoding/pem"
11         "io/ioutil"
12         "net/http"
13         "sync"
14         "time"
15
16         "github.com/Sirupsen/logrus"
17         "github.com/cloudflare/cfssl/api"
18         "github.com/cloudflare/cfssl/config"
19         "github.com/cloudflare/cfssl/csr"
20         "github.com/cloudflare/cfssl/signer"
21         "github.com/docker/swarmkit/log"
22         "github.com/pkg/errors"
23         "golang.org/x/net/context"
24         "golang.org/x/net/context/ctxhttp"
25 )
26
27 // ExternalCrossSignProfile is the profile that we will be sending cross-signing CSR sign requests with
28 const ExternalCrossSignProfile = "CA"
29
30 // ErrNoExternalCAURLs is an error used it indicate that an ExternalCA is
31 // configured with no URLs to which it can proxy certificate signing requests.
32 var ErrNoExternalCAURLs = errors.New("no external CA URLs")
33
34 // ExternalCA is able to make certificate signing requests to one of a list
35 // remote CFSSL API endpoints.
36 type ExternalCA struct {
37         ExternalRequestTimeout time.Duration
38
39         mu     sync.Mutex
40         rootCA *RootCA
41         urls   []string
42         client *http.Client
43 }
44
45 // NewExternalCA creates a new ExternalCA which uses the given tlsConfig to
46 // authenticate to any of the given URLS of CFSSL API endpoints.
47 func NewExternalCA(rootCA *RootCA, tlsConfig *tls.Config, urls ...string) *ExternalCA {
48         return &ExternalCA{
49                 ExternalRequestTimeout: 5 * time.Second,
50                 rootCA:                 rootCA,
51                 urls:                   urls,
52                 client: &http.Client{
53                         Transport: &http.Transport{
54                                 TLSClientConfig: tlsConfig,
55                         },
56                 },
57         }
58 }
59
60 // Copy returns a copy of the external CA that can be updated independently
61 func (eca *ExternalCA) Copy() *ExternalCA {
62         eca.mu.Lock()
63         defer eca.mu.Unlock()
64
65         return &ExternalCA{
66                 ExternalRequestTimeout: eca.ExternalRequestTimeout,
67                 rootCA:                 eca.rootCA,
68                 urls:                   eca.urls,
69                 client:                 eca.client,
70         }
71 }
72
73 // UpdateTLSConfig updates the HTTP Client for this ExternalCA by creating
74 // a new client which uses the given tlsConfig.
75 func (eca *ExternalCA) UpdateTLSConfig(tlsConfig *tls.Config) {
76         eca.mu.Lock()
77         defer eca.mu.Unlock()
78
79         eca.client = &http.Client{
80                 Transport: &http.Transport{
81                         TLSClientConfig: tlsConfig,
82                 },
83         }
84 }
85
86 // UpdateURLs updates the list of CSR API endpoints by setting it to the given urls.
87 func (eca *ExternalCA) UpdateURLs(urls ...string) {
88         eca.mu.Lock()
89         defer eca.mu.Unlock()
90
91         eca.urls = urls
92 }
93
94 // UpdateRootCA changes the root CA used to append intermediates
95 func (eca *ExternalCA) UpdateRootCA(rca *RootCA) {
96         eca.mu.Lock()
97         eca.rootCA = rca
98         eca.mu.Unlock()
99 }
100
101 // Sign signs a new certificate by proxying the given certificate signing
102 // request to an external CFSSL API server.
103 func (eca *ExternalCA) Sign(ctx context.Context, req signer.SignRequest) (cert []byte, err error) {
104         // Get the current HTTP client and list of URLs in a small critical
105         // section. We will use these to make certificate signing requests.
106         eca.mu.Lock()
107         urls := eca.urls
108         client := eca.client
109         intermediates := eca.rootCA.Intermediates
110         eca.mu.Unlock()
111
112         if len(urls) == 0 {
113                 return nil, ErrNoExternalCAURLs
114         }
115
116         csrJSON, err := json.Marshal(req)
117         if err != nil {
118                 return nil, errors.Wrap(err, "unable to JSON-encode CFSSL signing request")
119         }
120
121         // Try each configured proxy URL. Return after the first success. If
122         // all fail then the last error will be returned.
123         for _, url := range urls {
124                 requestCtx, cancel := context.WithTimeout(ctx, eca.ExternalRequestTimeout)
125                 cert, err = makeExternalSignRequest(requestCtx, client, url, csrJSON)
126                 cancel()
127                 if err == nil {
128                         return append(cert, intermediates...), err
129                 }
130                 log.G(ctx).Debugf("unable to proxy certificate signing request to %s: %s", url, err)
131         }
132
133         return nil, err
134 }
135
136 // CrossSignRootCA takes a RootCA object, generates a CA CSR, sends a signing request with the CA CSR to the external
137 // CFSSL API server in order to obtain a cross-signed root
138 func (eca *ExternalCA) CrossSignRootCA(ctx context.Context, rca RootCA) ([]byte, error) {
139         // ExtractCertificateRequest generates a new key request, and we want to continue to use the old
140         // key.  However, ExtractCertificateRequest will also convert the pkix.Name to csr.Name, which we
141         // need in order to generate a signing request
142         rcaSigner, err := rca.Signer()
143         if err != nil {
144                 return nil, err
145         }
146         rootCert := rcaSigner.parsedCert
147         cfCSRObj := csr.ExtractCertificateRequest(rootCert)
148
149         der, err := x509.CreateCertificateRequest(cryptorand.Reader, &x509.CertificateRequest{
150                 RawSubjectPublicKeyInfo: rootCert.RawSubjectPublicKeyInfo,
151                 RawSubject:              rootCert.RawSubject,
152                 PublicKeyAlgorithm:      rootCert.PublicKeyAlgorithm,
153                 Subject:                 rootCert.Subject,
154                 Extensions:              rootCert.Extensions,
155                 DNSNames:                rootCert.DNSNames,
156                 EmailAddresses:          rootCert.EmailAddresses,
157                 IPAddresses:             rootCert.IPAddresses,
158         }, rcaSigner.cryptoSigner)
159         if err != nil {
160                 return nil, err
161         }
162         req := signer.SignRequest{
163                 Request: string(pem.EncodeToMemory(&pem.Block{
164                         Type:  "CERTIFICATE REQUEST",
165                         Bytes: der,
166                 })),
167                 Subject: &signer.Subject{
168                         CN:    rootCert.Subject.CommonName,
169                         Names: cfCSRObj.Names,
170                 },
171                 Profile: ExternalCrossSignProfile,
172         }
173         // cfssl actually ignores non subject alt name extensions in the CSR, so we have to add the CA extension in the signing
174         // request as well
175         for _, ext := range rootCert.Extensions {
176                 if ext.Id.Equal(BasicConstraintsOID) {
177                         req.Extensions = append(req.Extensions, signer.Extension{
178                                 ID:       config.OID(ext.Id),
179                                 Critical: ext.Critical,
180                                 Value:    hex.EncodeToString(ext.Value),
181                         })
182                 }
183         }
184         return eca.Sign(ctx, req)
185 }
186
187 func makeExternalSignRequest(ctx context.Context, client *http.Client, url string, csrJSON []byte) (cert []byte, err error) {
188         resp, err := ctxhttp.Post(ctx, client, url, "application/json", bytes.NewReader(csrJSON))
189         if err != nil {
190                 return nil, recoverableErr{err: errors.Wrap(err, "unable to perform certificate signing request")}
191         }
192         defer resp.Body.Close()
193
194         body, err := ioutil.ReadAll(resp.Body)
195         if err != nil {
196                 return nil, recoverableErr{err: errors.Wrap(err, "unable to read CSR response body")}
197         }
198
199         if resp.StatusCode != http.StatusOK {
200                 return nil, recoverableErr{err: errors.Errorf("unexpected status code in CSR response: %d - %s", resp.StatusCode, string(body))}
201         }
202
203         var apiResponse api.Response
204         if err := json.Unmarshal(body, &apiResponse); err != nil {
205                 logrus.Debugf("unable to JSON-parse CFSSL API response body: %s", string(body))
206                 return nil, recoverableErr{err: errors.Wrap(err, "unable to parse JSON response")}
207         }
208
209         if !apiResponse.Success || apiResponse.Result == nil {
210                 if len(apiResponse.Errors) > 0 {
211                         return nil, errors.Errorf("response errors: %v", apiResponse.Errors)
212                 }
213
214                 return nil, errors.New("certificate signing request failed")
215         }
216
217         result, ok := apiResponse.Result.(map[string]interface{})
218         if !ok {
219                 return nil, errors.Errorf("invalid result type: %T", apiResponse.Result)
220         }
221
222         certPEM, ok := result["certificate"].(string)
223         if !ok {
224                 return nil, errors.Errorf("invalid result certificate field type: %T", result["certificate"])
225         }
226
227         return []byte(certPEM), nil
228 }