podman

Форк
0
537 строк · 15.9 Кб
1
// Copyright 2015 go-swagger maintainers
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//    http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
package client
16

17
import (
18
	"context"
19
	"crypto"
20
	"crypto/ecdsa"
21
	"crypto/rsa"
22
	"crypto/tls"
23
	"crypto/x509"
24
	"encoding/pem"
25
	"fmt"
26
	"mime"
27
	"net/http"
28
	"net/http/httputil"
29
	"os"
30
	"strings"
31
	"sync"
32
	"time"
33

34
	"github.com/opentracing/opentracing-go"
35

36
	"github.com/go-openapi/runtime"
37
	"github.com/go-openapi/runtime/logger"
38
	"github.com/go-openapi/runtime/middleware"
39
	"github.com/go-openapi/runtime/yamlpc"
40
	"github.com/go-openapi/strfmt"
41
)
42

43
// TLSClientOptions to configure client authentication with mutual TLS
44
type TLSClientOptions struct {
45
	// Certificate is the path to a PEM-encoded certificate to be used for
46
	// client authentication. If set then Key must also be set.
47
	Certificate string
48

49
	// LoadedCertificate is the certificate to be used for client authentication.
50
	// This field is ignored if Certificate is set. If this field is set, LoadedKey
51
	// is also required.
52
	LoadedCertificate *x509.Certificate
53

54
	// Key is the path to an unencrypted PEM-encoded private key for client
55
	// authentication. This field is required if Certificate is set.
56
	Key string
57

58
	// LoadedKey is the key for client authentication. This field is required if
59
	// LoadedCertificate is set.
60
	LoadedKey crypto.PrivateKey
61

62
	// CA is a path to a PEM-encoded certificate that specifies the root certificate
63
	// to use when validating the TLS certificate presented by the server. If this field
64
	// (and LoadedCA) is not set, the system certificate pool is used. This field is ignored if LoadedCA
65
	// is set.
66
	CA string
67

68
	// LoadedCA specifies the root certificate to use when validating the server's TLS certificate.
69
	// If this field (and CA) is not set, the system certificate pool is used.
70
	LoadedCA *x509.Certificate
71

72
	// LoadedCAPool specifies a pool of RootCAs to use when validating the server's TLS certificate.
73
	// If set, it will be combined with the the other loaded certificates (see LoadedCA and CA).
74
	// If neither LoadedCA or CA is set, the provided pool with override the system
75
	// certificate pool.
76
	// The caller must not use the supplied pool after calling TLSClientAuth.
77
	LoadedCAPool *x509.CertPool
78

79
	// ServerName specifies the hostname to use when verifying the server certificate.
80
	// If this field is set then InsecureSkipVerify will be ignored and treated as
81
	// false.
82
	ServerName string
83

84
	// InsecureSkipVerify controls whether the certificate chain and hostname presented
85
	// by the server are validated. If true, any certificate is accepted.
86
	InsecureSkipVerify bool
87

88
	// VerifyPeerCertificate, if not nil, is called after normal
89
	// certificate verification. It receives the raw ASN.1 certificates
90
	// provided by the peer and also any verified chains that normal processing found.
91
	// If it returns a non-nil error, the handshake is aborted and that error results.
92
	//
93
	// If normal verification fails then the handshake will abort before
94
	// considering this callback. If normal verification is disabled by
95
	// setting InsecureSkipVerify then this callback will be considered but
96
	// the verifiedChains argument will always be nil.
97
	VerifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
98

99
	// SessionTicketsDisabled may be set to true to disable session ticket and
100
	// PSK (resumption) support. Note that on clients, session ticket support is
101
	// also disabled if ClientSessionCache is nil.
102
	SessionTicketsDisabled bool
103

104
	// ClientSessionCache is a cache of ClientSessionState entries for TLS
105
	// session resumption. It is only used by clients.
106
	ClientSessionCache tls.ClientSessionCache
107

108
	// Prevents callers using unkeyed fields.
109
	_ struct{}
110
}
111

112
// TLSClientAuth creates a tls.Config for mutual auth
113
func TLSClientAuth(opts TLSClientOptions) (*tls.Config, error) {
114
	// create client tls config
115
	cfg := &tls.Config{}
116

117
	// load client cert if specified
118
	if opts.Certificate != "" {
119
		cert, err := tls.LoadX509KeyPair(opts.Certificate, opts.Key)
120
		if err != nil {
121
			return nil, fmt.Errorf("tls client cert: %v", err)
122
		}
123
		cfg.Certificates = []tls.Certificate{cert}
124
	} else if opts.LoadedCertificate != nil {
125
		block := pem.Block{Type: "CERTIFICATE", Bytes: opts.LoadedCertificate.Raw}
126
		certPem := pem.EncodeToMemory(&block)
127

128
		var keyBytes []byte
129
		switch k := opts.LoadedKey.(type) {
130
		case *rsa.PrivateKey:
131
			keyBytes = x509.MarshalPKCS1PrivateKey(k)
132
		case *ecdsa.PrivateKey:
133
			var err error
134
			keyBytes, err = x509.MarshalECPrivateKey(k)
135
			if err != nil {
136
				return nil, fmt.Errorf("tls client priv key: %v", err)
137
			}
138
		default:
139
			return nil, fmt.Errorf("tls client priv key: unsupported key type")
140
		}
141

142
		block = pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes}
143
		keyPem := pem.EncodeToMemory(&block)
144

145
		cert, err := tls.X509KeyPair(certPem, keyPem)
146
		if err != nil {
147
			return nil, fmt.Errorf("tls client cert: %v", err)
148
		}
149
		cfg.Certificates = []tls.Certificate{cert}
150
	}
151

152
	cfg.InsecureSkipVerify = opts.InsecureSkipVerify
153

154
	cfg.VerifyPeerCertificate = opts.VerifyPeerCertificate
155
	cfg.SessionTicketsDisabled = opts.SessionTicketsDisabled
156
	cfg.ClientSessionCache = opts.ClientSessionCache
157

158
	// When no CA certificate is provided, default to the system cert pool
159
	// that way when a request is made to a server known by the system trust store,
160
	// the name is still verified
161
	if opts.LoadedCA != nil {
162
		caCertPool := basePool(opts.LoadedCAPool)
163
		caCertPool.AddCert(opts.LoadedCA)
164
		cfg.RootCAs = caCertPool
165
	} else if opts.CA != "" {
166
		// load ca cert
167
		caCert, err := os.ReadFile(opts.CA)
168
		if err != nil {
169
			return nil, fmt.Errorf("tls client ca: %v", err)
170
		}
171
		caCertPool := basePool(opts.LoadedCAPool)
172
		caCertPool.AppendCertsFromPEM(caCert)
173
		cfg.RootCAs = caCertPool
174
	} else if opts.LoadedCAPool != nil {
175
		cfg.RootCAs = opts.LoadedCAPool
176
	}
177

178
	// apply servername overrride
179
	if opts.ServerName != "" {
180
		cfg.InsecureSkipVerify = false
181
		cfg.ServerName = opts.ServerName
182
	}
183

184
	return cfg, nil
185
}
186

187
func basePool(pool *x509.CertPool) *x509.CertPool {
188
	if pool == nil {
189
		return x509.NewCertPool()
190
	}
191
	return pool
192
}
193

194
// TLSTransport creates a http client transport suitable for mutual tls auth
195
func TLSTransport(opts TLSClientOptions) (http.RoundTripper, error) {
196
	cfg, err := TLSClientAuth(opts)
197
	if err != nil {
198
		return nil, err
199
	}
200

201
	return &http.Transport{TLSClientConfig: cfg}, nil
202
}
203

204
// TLSClient creates a http.Client for mutual auth
205
func TLSClient(opts TLSClientOptions) (*http.Client, error) {
206
	transport, err := TLSTransport(opts)
207
	if err != nil {
208
		return nil, err
209
	}
210
	return &http.Client{Transport: transport}, nil
211
}
212

213
// DefaultTimeout the default request timeout
214
var DefaultTimeout = 30 * time.Second
215

216
// Runtime represents an API client that uses the transport
217
// to make http requests based on a swagger specification.
218
type Runtime struct {
219
	DefaultMediaType      string
220
	DefaultAuthentication runtime.ClientAuthInfoWriter
221
	Consumers             map[string]runtime.Consumer
222
	Producers             map[string]runtime.Producer
223

224
	Transport http.RoundTripper
225
	Jar       http.CookieJar
226
	// Spec      *spec.Document
227
	Host     string
228
	BasePath string
229
	Formats  strfmt.Registry
230
	Context  context.Context
231

232
	Debug  bool
233
	logger logger.Logger
234

235
	clientOnce *sync.Once
236
	client     *http.Client
237
	schemes    []string
238
	response   ClientResponseFunc
239
}
240

241
// New creates a new default runtime for a swagger api runtime.Client
242
func New(host, basePath string, schemes []string) *Runtime {
243
	var rt Runtime
244
	rt.DefaultMediaType = runtime.JSONMime
245

246
	// TODO: actually infer this stuff from the spec
247
	rt.Consumers = map[string]runtime.Consumer{
248
		runtime.YAMLMime:    yamlpc.YAMLConsumer(),
249
		runtime.JSONMime:    runtime.JSONConsumer(),
250
		runtime.XMLMime:     runtime.XMLConsumer(),
251
		runtime.TextMime:    runtime.TextConsumer(),
252
		runtime.HTMLMime:    runtime.TextConsumer(),
253
		runtime.CSVMime:     runtime.CSVConsumer(),
254
		runtime.DefaultMime: runtime.ByteStreamConsumer(),
255
	}
256
	rt.Producers = map[string]runtime.Producer{
257
		runtime.YAMLMime:    yamlpc.YAMLProducer(),
258
		runtime.JSONMime:    runtime.JSONProducer(),
259
		runtime.XMLMime:     runtime.XMLProducer(),
260
		runtime.TextMime:    runtime.TextProducer(),
261
		runtime.HTMLMime:    runtime.TextProducer(),
262
		runtime.CSVMime:     runtime.CSVProducer(),
263
		runtime.DefaultMime: runtime.ByteStreamProducer(),
264
	}
265
	rt.Transport = http.DefaultTransport
266
	rt.Jar = nil
267
	rt.Host = host
268
	rt.BasePath = basePath
269
	rt.Context = context.Background()
270
	rt.clientOnce = new(sync.Once)
271
	if !strings.HasPrefix(rt.BasePath, "/") {
272
		rt.BasePath = "/" + rt.BasePath
273
	}
274

275
	rt.Debug = logger.DebugEnabled()
276
	rt.logger = logger.StandardLogger{}
277
	rt.response = newResponse
278

279
	if len(schemes) > 0 {
280
		rt.schemes = schemes
281
	}
282
	return &rt
283
}
284

285
// NewWithClient allows you to create a new transport with a configured http.Client
286
func NewWithClient(host, basePath string, schemes []string, client *http.Client) *Runtime {
287
	rt := New(host, basePath, schemes)
288
	if client != nil {
289
		rt.clientOnce.Do(func() {
290
			rt.client = client
291
		})
292
	}
293
	return rt
294
}
295

296
// WithOpenTracing adds opentracing support to the provided runtime.
297
// A new client span is created for each request.
298
// If the context of the client operation does not contain an active span, no span is created.
299
// The provided opts are applied to each spans - for example to add global tags.
300
func (r *Runtime) WithOpenTracing(opts ...opentracing.StartSpanOption) runtime.ClientTransport {
301
	return newOpenTracingTransport(r, r.Host, opts)
302
}
303

304
// WithOpenTelemetry adds opentelemetry support to the provided runtime.
305
// A new client span is created for each request.
306
// If the context of the client operation does not contain an active span, no span is created.
307
// The provided opts are applied to each spans - for example to add global tags.
308
func (r *Runtime) WithOpenTelemetry(opts ...OpenTelemetryOpt) runtime.ClientTransport {
309
	return newOpenTelemetryTransport(r, r.Host, opts)
310
}
311

312
func (r *Runtime) pickScheme(schemes []string) string {
313
	if v := r.selectScheme(r.schemes); v != "" {
314
		return v
315
	}
316
	if v := r.selectScheme(schemes); v != "" {
317
		return v
318
	}
319
	return "http"
320
}
321

322
func (r *Runtime) selectScheme(schemes []string) string {
323
	schLen := len(schemes)
324
	if schLen == 0 {
325
		return ""
326
	}
327

328
	scheme := schemes[0]
329
	// prefer https, but skip when not possible
330
	if scheme != "https" && schLen > 1 {
331
		for _, sch := range schemes {
332
			if sch == "https" {
333
				scheme = sch
334
				break
335
			}
336
		}
337
	}
338
	return scheme
339
}
340

341
func transportOrDefault(left, right http.RoundTripper) http.RoundTripper {
342
	if left == nil {
343
		return right
344
	}
345
	return left
346
}
347

348
// EnableConnectionReuse drains the remaining body from a response
349
// so that go will reuse the TCP connections.
350
//
351
// This is not enabled by default because there are servers where
352
// the response never gets closed and that would make the code hang forever.
353
// So instead it's provided as a http client middleware that can be used to override
354
// any request.
355
func (r *Runtime) EnableConnectionReuse() {
356
	if r.client == nil {
357
		r.Transport = KeepAliveTransport(
358
			transportOrDefault(r.Transport, http.DefaultTransport),
359
		)
360
		return
361
	}
362

363
	r.client.Transport = KeepAliveTransport(
364
		transportOrDefault(r.client.Transport,
365
			transportOrDefault(r.Transport, http.DefaultTransport),
366
		),
367
	)
368
}
369

370
// takes a client operation and creates equivalent http.Request
371
func (r *Runtime) createHttpRequest(operation *runtime.ClientOperation) (*request, *http.Request, error) {
372
	params, _, auth := operation.Params, operation.Reader, operation.AuthInfo
373

374
	request, err := newRequest(operation.Method, operation.PathPattern, params)
375
	if err != nil {
376
		return nil, nil, err
377
	}
378

379
	var accept []string
380
	accept = append(accept, operation.ProducesMediaTypes...)
381
	if err = request.SetHeaderParam(runtime.HeaderAccept, accept...); err != nil {
382
		return nil, nil, err
383
	}
384

385
	if auth == nil && r.DefaultAuthentication != nil {
386
		auth = runtime.ClientAuthInfoWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error {
387
			if req.GetHeaderParams().Get(runtime.HeaderAuthorization) != "" {
388
				return nil
389
			}
390
			return r.DefaultAuthentication.AuthenticateRequest(req, reg)
391
		})
392
	}
393
	// if auth != nil {
394
	//	if err := auth.AuthenticateRequest(request, r.Formats); err != nil {
395
	//		return nil, err
396
	//	}
397
	//}
398

399
	// TODO: pick appropriate media type
400
	cmt := r.DefaultMediaType
401
	for _, mediaType := range operation.ConsumesMediaTypes {
402
		// Pick first non-empty media type
403
		if mediaType != "" {
404
			cmt = mediaType
405
			break
406
		}
407
	}
408

409
	if _, ok := r.Producers[cmt]; !ok && cmt != runtime.MultipartFormMime && cmt != runtime.URLencodedFormMime {
410
		return nil, nil, fmt.Errorf("none of producers: %v registered. try %s", r.Producers, cmt)
411
	}
412

413
	req, err := request.buildHTTP(cmt, r.BasePath, r.Producers, r.Formats, auth)
414
	if err != nil {
415
		return nil, nil, err
416
	}
417
	req.URL.Scheme = r.pickScheme(operation.Schemes)
418
	req.URL.Host = r.Host
419
	req.Host = r.Host
420
	return request, req, nil
421
}
422

423
func (r *Runtime) CreateHttpRequest(operation *runtime.ClientOperation) (req *http.Request, err error) {
424
	_, req, err = r.createHttpRequest(operation)
425
	return
426
}
427

428
// Submit a request and when there is a body on success it will turn that into the result
429
// all other things are turned into an api error for swagger which retains the status code
430
func (r *Runtime) Submit(operation *runtime.ClientOperation) (interface{}, error) {
431
	_, readResponse, _ := operation.Params, operation.Reader, operation.AuthInfo
432

433
	request, req, err := r.createHttpRequest(operation)
434
	if err != nil {
435
		return nil, err
436
	}
437

438
	r.clientOnce.Do(func() {
439
		r.client = &http.Client{
440
			Transport: r.Transport,
441
			Jar:       r.Jar,
442
		}
443
	})
444

445
	if r.Debug {
446
		b, err2 := httputil.DumpRequestOut(req, true)
447
		if err2 != nil {
448
			return nil, err2
449
		}
450
		r.logger.Debugf("%s\n", string(b))
451
	}
452

453
	var hasTimeout bool
454
	pctx := operation.Context
455
	if pctx == nil {
456
		pctx = r.Context
457
	} else {
458
		hasTimeout = true
459
	}
460
	if pctx == nil {
461
		pctx = context.Background()
462
	}
463
	var ctx context.Context
464
	var cancel context.CancelFunc
465
	if hasTimeout {
466
		ctx, cancel = context.WithCancel(pctx)
467
	} else {
468
		ctx, cancel = context.WithTimeout(pctx, request.timeout)
469
	}
470
	defer cancel()
471

472
	client := operation.Client
473
	if client == nil {
474
		client = r.client
475
	}
476
	req = req.WithContext(ctx)
477
	res, err := client.Do(req) // make requests, by default follows 10 redirects before failing
478
	if err != nil {
479
		return nil, err
480
	}
481
	defer res.Body.Close()
482

483
	ct := res.Header.Get(runtime.HeaderContentType)
484
	if ct == "" { // this should really really never occur
485
		ct = r.DefaultMediaType
486
	}
487

488
	if r.Debug {
489
		printBody := true
490
		if ct == runtime.DefaultMime {
491
			printBody = false // Spare the terminal from a binary blob.
492
		}
493
		b, err2 := httputil.DumpResponse(res, printBody)
494
		if err2 != nil {
495
			return nil, err2
496
		}
497
		r.logger.Debugf("%s\n", string(b))
498
	}
499

500
	mt, _, err := mime.ParseMediaType(ct)
501
	if err != nil {
502
		return nil, fmt.Errorf("parse content type: %s", err)
503
	}
504

505
	cons, ok := r.Consumers[mt]
506
	if !ok {
507
		if cons, ok = r.Consumers["*/*"]; !ok {
508
			// scream about not knowing what to do
509
			return nil, fmt.Errorf("no consumer: %q", ct)
510
		}
511
	}
512
	return readResponse.ReadResponse(r.response(res), cons)
513
}
514

515
// SetDebug changes the debug flag.
516
// It ensures that client and middlewares have the set debug level.
517
func (r *Runtime) SetDebug(debug bool) {
518
	r.Debug = debug
519
	middleware.Debug = debug
520
}
521

522
// SetLogger changes the logger stream.
523
// It ensures that client and middlewares use the same logger.
524
func (r *Runtime) SetLogger(logger logger.Logger) {
525
	r.logger = logger
526
	middleware.Logger = logger
527
}
528

529
type ClientResponseFunc = func(*http.Response) runtime.ClientResponse
530

531
// SetResponseReader changes the response reader implementation.
532
func (r *Runtime) SetResponseReader(f ClientResponseFunc) {
533
	if f == nil {
534
		return
535
	}
536
	r.response = f
537
}
538

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.