-
Notifications
You must be signed in to change notification settings - Fork 20
/
sos.go
204 lines (163 loc) · 5.26 KB
/
sos.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
package cmd
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"time"
minio "github.com/minio/minio-go/v6"
"github.com/spf13/cobra"
"golang.org/x/net/http2"
)
const (
minioMaxRetry = 2
)
// sosCmd represents the sos command
var sosCmdLongHelp = func() string {
var long = "Manage Exoscale Object Storage (SOS)"
if runtime.GOOS == "windows" {
long += `
IMPORTANT: Due to a bug in the Microsoft Windows support in the Go
programming language (https://github.com/golang/go/issues/16736) Windows
users are required to extract the sos-certs.pem file next to their exo.exe
file from the archive. You can obtain a fresh copy of the exo CLI from
this address:
https://github.com/exoscale/cli/releases
The required file can also be obtained from the following address:
https://www.exoscale.com/static/files/sos-certs.pem
If you have located your certificate chain in a different location you
can also use the '--certs-file' parameter to indicate the location.
We apologize for the inconvenience.
`
}
return long
}
var sosCmd = &cobra.Command{
Use: "sos",
Short: "Simple Object Storage management",
Long: sosCmdLongHelp(),
TraverseChildren: true,
}
type sosClient struct {
*minio.Client
certPool *x509.CertPool
}
// sosGetExternalCertsFile returns the path to an external certificates file on Windows platforms as a workaround
// for Golang issue #16736 on Windows (https://github.com/golang/go/issues/16736).
func sosGetExternalCertsFile(certsFile string) (string, error) {
var warningMessage = `error: missing SOS certificates file.
It seems you are running on Windows and your "sos-certs.pem" file is missing.
Please download and extract all files from the exo CLI release, not just the
executable. Please run the "exo sos help" command for more information.`
if certsFile != "" || runtime.GOOS != "windows" {
return certsFile, nil
}
path, err := os.Executable()
if err != nil {
return "", fmt.Errorf("unable to retrieve the executable path: %s", err)
}
dir, err := filepath.Abs(filepath.Dir(path))
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, warningMessage)
os.Exit(1)
}
tmpCertsFile := filepath.Join(dir, "sos-certs.pem")
if stat, err := os.Stat(tmpCertsFile); err != nil || stat.IsDir() {
_, _ = fmt.Fprintln(os.Stderr, warningMessage)
os.Exit(1)
}
return tmpCertsFile, nil
}
func newSOSClient(certsFile string) (*sosClient, error) {
var (
c sosClient
err error
)
certsFile, err = sosGetExternalCertsFile(certsFile)
if err != nil {
return nil, err
}
if certsFile != "" {
c.certPool = x509.NewCertPool()
certs, err := ioutil.ReadFile(certsFile)
if err != nil {
return nil, fmt.Errorf("unable to read certificates from file: %s", err)
}
if !c.certPool.AppendCertsFromPEM(certs) {
return nil, errors.New("unable to load local certificates")
}
}
z := gCurrentAccount.DefaultZone
if err = c.setZone(z); err != nil {
return nil, err
}
_, ok := os.LookupEnv("EXOSCALE_TRACE")
if ok {
c.TraceOn(os.Stderr)
}
return &c, nil
}
func (s *sosClient) setZone(zone string) error {
// When a user wants to set the SOS zone to use for an operation, we actually have to re-create the
// underlying Minio S3 client to specify the zone-based endpoint.
endpoint := strings.Replace(gCurrentAccount.SosEndpoint, "https://", "", -1)
endpoint = strings.Replace(endpoint, "{zone}", zone, -1)
minioClient, err := minio.NewV4(endpoint, gCurrentAccount.Key, gCurrentAccount.APISecret(), true)
if err != nil {
return err
}
// This is a workaround to support SOS on the Windows platform because of a bug preventing access to system-wide
// trusted CA certificates with Go:
// - https://github.com/golang/go/issues/16736
// - https://golang.org/src/crypto/x509/root_windows.go#L228
//
// Pending resolution, we have to inject the user-provided PEM certificates chain for the TLS SOS API endpoints
// in our HTTPS client to avoid "https://sos-<zone>.exo.io/: x509: certificate signed by unknown authority" error.
if s.certPool != nil {
customTransport, err := func() (http.RoundTripper, error) {
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
MaxIdleConns: 1024,
MaxIdleConnsPerHost: 1024,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
DisableCompression: true,
TLSClientConfig: &tls.Config{
RootCAs: s.certPool,
MinVersion: tls.VersionTLS12,
},
}
if err := http2.ConfigureTransport(tr); err != nil {
return nil, err
}
return tr, nil
}()
if err != nil {
return fmt.Errorf("unable to initialize custom HTTP transport: %s", err)
}
minioClient.SetCustomTransport(customTransport)
}
minioClient.SetAppInfo("Exoscale-CLI", gVersion)
if _, ok := os.LookupEnv("EXOSCALE_TRACE"); ok {
minioClient.TraceOn(os.Stderr)
}
s.Client = minioClient
return nil
}
func init() {
minio.MaxRetry = minioMaxRetry
RootCmd.AddCommand(sosCmd)
sosCmd.PersistentFlags().String("certs-file", "", "Path to file containing additional SOS API X.509 certificates")
}