podman

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

16
package api
17

18
import (
19
	"bytes"
20
	"encoding/base64"
21
	"encoding/json"
22
	"encoding/pem"
23
	"errors"
24
	"fmt"
25
	"io"
26
	"net/http"
27
	"net/url"
28
	"path"
29
	"time"
30
)
31

32
type CertificateResponse struct {
33
	CertPEM  []byte
34
	ChainPEM []byte
35
	SCT      []byte
36
}
37

38
type RootResponse struct {
39
	ChainPEM []byte
40
}
41

42
type Key struct {
43
	// +required
44
	Content   []byte `json:"content"`
45
	Algorithm string `json:"algorithm,omitempty"`
46
}
47

48
type CertificateRequest struct {
49
	// +optional
50
	PublicKey Key `json:"publicKey"`
51

52
	// +optional
53
	SignedEmailAddress []byte `json:"signedEmailAddress"`
54

55
	// +optional
56
	CertificateSigningRequest []byte `json:"certificateSigningRequest"`
57
}
58

59
const (
60
	signingCertPath = "/api/v1/signingCert"
61
	rootCertPath    = "/api/v1/rootCert"
62
)
63

64
// SigstorePublicServerURL is the URL of Sigstore's public Fulcio service.
65
const SigstorePublicServerURL = "https://fulcio.sigstore.dev"
66

67
// LegacyClient is the interface for accessing the Fulcio API.
68
type LegacyClient interface {
69
	// SigningCert sends the provided CertificateRequest to the /api/v1/signingCert
70
	// endpoint of a Fulcio API, authenticated with the provided bearer token.
71
	SigningCert(cr CertificateRequest, token string) (*CertificateResponse, error)
72
	// RootCert sends a request to get the current CA used by Fulcio.
73
	RootCert() (*RootResponse, error)
74
}
75

76
// ClientOption is a functional option for customizing static signatures.
77
type ClientOption func(*clientOptions)
78

79
// NewClient creates a new Fulcio API client talking to the provided URL.
80
func NewClient(url *url.URL, opts ...ClientOption) LegacyClient {
81
	o := makeOptions(opts...)
82

83
	return &client{
84
		baseURL: url,
85
		client: &http.Client{
86
			Transport: createRoundTripper(http.DefaultTransport, o),
87
			Timeout:   o.Timeout,
88
		},
89
	}
90
}
91

92
type client struct {
93
	baseURL *url.URL
94
	client  *http.Client
95
}
96

97
var _ LegacyClient = (*client)(nil)
98

99
// SigningCert implements Client
100
func (c *client) SigningCert(cr CertificateRequest, token string) (*CertificateResponse, error) {
101
	// Construct the API endpoint for this handler
102
	endpoint := *c.baseURL
103
	endpoint.Path = path.Join(endpoint.Path, signingCertPath)
104

105
	b, err := json.Marshal(cr)
106
	if err != nil {
107
		return nil, fmt.Errorf("marshal: %w", err)
108
	}
109

110
	req, err := http.NewRequest(http.MethodPost, endpoint.String(), bytes.NewBuffer(b))
111
	if err != nil {
112
		return nil, fmt.Errorf("request: %w", err)
113
	}
114
	// Set the authorization header to our OIDC bearer token.
115
	req.Header.Set("Authorization", "Bearer "+token)
116
	// Set the content-type to reflect we're sending JSON.
117
	req.Header.Set("Content-Type", "application/json")
118

119
	resp, err := c.client.Do(req)
120
	if err != nil {
121
		return nil, fmt.Errorf("client: %w", err)
122
	}
123
	defer resp.Body.Close()
124

125
	body, err := io.ReadAll(resp.Body)
126
	if err != nil {
127
		return nil, fmt.Errorf("%s read: %w", endpoint.String(), err)
128
	}
129

130
	// The API should return a 201 Created on success.  If we see anything else,
131
	// then turn the response body into an error.
132
	if resp.StatusCode != http.StatusCreated {
133
		return nil, fmt.Errorf("%s %s returned %s: %q", http.MethodPost, endpoint.String(), resp.Status, body)
134
	}
135

136
	// Extract the SCT from the response header.
137
	sct, err := base64.StdEncoding.DecodeString(resp.Header.Get("SCT"))
138
	if err != nil {
139
		return nil, fmt.Errorf("decode: %w", err)
140
	}
141

142
	// Split the cert and the chain
143
	certBlock, chainPem := pem.Decode(body)
144
	if certBlock == nil {
145
		return nil, errors.New("did not find a cert from Fulcio")
146
	}
147
	certPem := pem.EncodeToMemory(certBlock)
148
	return &CertificateResponse{
149
		CertPEM:  certPem,
150
		ChainPEM: chainPem,
151
		SCT:      sct,
152
	}, nil
153
}
154

155
func (c *client) RootCert() (*RootResponse, error) {
156
	// Construct the API endpoint for this handler
157
	endpoint := *c.baseURL
158
	endpoint.Path = path.Join(endpoint.Path, rootCertPath)
159

160
	req, err := http.NewRequest(http.MethodGet, endpoint.String(), nil)
161
	if err != nil {
162
		return nil, fmt.Errorf("request: %w", err)
163
	}
164
	resp, err := c.client.Do(req)
165
	if err != nil {
166
		return nil, err
167
	}
168
	defer resp.Body.Close()
169
	body, err := io.ReadAll(resp.Body)
170

171
	if err != nil {
172
		return nil, err
173
	}
174

175
	if resp.StatusCode != http.StatusOK {
176
		return nil, errors.New(string(body))
177
	}
178
	return &RootResponse{ChainPEM: body}, nil
179
}
180

181
type clientOptions struct {
182
	UserAgent string
183
	Timeout   time.Duration
184
}
185

186
func makeOptions(opts ...ClientOption) *clientOptions {
187
	o := &clientOptions{
188
		UserAgent: "",
189
	}
190

191
	for _, opt := range opts {
192
		opt(o)
193
	}
194

195
	return o
196
}
197

198
// WithTimeout sets the request timeout for the client
199
func WithTimeout(timeout time.Duration) ClientOption {
200
	return func(o *clientOptions) {
201
		o.Timeout = timeout
202
	}
203
}
204

205
// WithUserAgent sets the media type of the signature.
206
func WithUserAgent(userAgent string) ClientOption {
207
	return func(o *clientOptions) {
208
		o.UserAgent = userAgent
209
	}
210
}
211

212
type roundTripper struct {
213
	http.RoundTripper
214
	UserAgent string
215
}
216

217
// RoundTrip implements `http.RoundTripper`
218
func (rt *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
219
	req.Header.Set("User-Agent", rt.UserAgent)
220
	return rt.RoundTripper.RoundTrip(req)
221
}
222

223
func createRoundTripper(inner http.RoundTripper, o *clientOptions) http.RoundTripper {
224
	if inner == nil {
225
		inner = http.DefaultTransport
226
	}
227
	if o.UserAgent == "" {
228
		// There's nothing to do...
229
		return inner
230
	}
231
	return &roundTripper{
232
		RoundTripper: inner,
233
		UserAgent:    o.UserAgent,
234
	}
235
}
236

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

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

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

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