forked from juju/utils
/
http.go
152 lines (136 loc) · 5.14 KB
/
http.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
// Copyright 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package utils
import (
"crypto/tls"
"encoding/base64"
"fmt"
"net"
"net/http"
"strings"
"sync"
)
var insecureClient = (*http.Client)(nil)
var insecureClientMutex = sync.Mutex{}
func init() {
// See https://code.google.com/p/go/issues/detail?id=4677
// We need to force the connection to close each time so that we don't
// hit the above Go bug.
defaultTransport := http.DefaultTransport.(*http.Transport)
defaultTransport.DisableKeepAlives = true
defaultTransport.Dial = dial
registerFileProtocol(defaultTransport)
}
// registerFileProtocol registers support for file:// URLs on the given transport.
func registerFileProtocol(transport *http.Transport) {
transport.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
}
// SSLHostnameVerification is used as a switch for when a given provider might
// use self-signed credentials and we should not try to verify the hostname on
// the TLS/SSL certificates
type SSLHostnameVerification bool
const (
// VerifySSLHostnames ensures we verify the hostname on the certificate
// matches the host we are connecting and is signed
VerifySSLHostnames = SSLHostnameVerification(true)
// NoVerifySSLHostnames informs us to skip verifying the hostname
// matches a valid certificate
NoVerifySSLHostnames = SSLHostnameVerification(false)
)
// GetHTTPClient returns either a standard http client or
// non validating client depending on the value of verify.
func GetHTTPClient(verify SSLHostnameVerification) *http.Client {
if verify == VerifySSLHostnames {
return GetValidatingHTTPClient()
}
return GetNonValidatingHTTPClient()
}
// GetValidatingHTTPClient returns a new http.Client that
// verifies the server's certificate chain and hostname.
func GetValidatingHTTPClient() *http.Client {
logger.Tracef("hostname SSL verification enabled")
return http.DefaultClient
}
// GetNonValidatingHTTPClient returns a new http.Client that
// does not verify the server's certificate chain and hostname.
func GetNonValidatingHTTPClient() *http.Client {
logger.Tracef("hostname SSL verification disabled")
insecureClientMutex.Lock()
defer insecureClientMutex.Unlock()
if insecureClient == nil {
insecureConfig := &tls.Config{InsecureSkipVerify: true}
insecureTransport := NewHttpTLSTransport(insecureConfig)
insecureClient = &http.Client{Transport: insecureTransport}
}
return insecureClient
}
// NewHttpTLSTransport returns a new http.Transport constructed with the TLS config
// and the necessary parameters for Juju.
func NewHttpTLSTransport(tlsConfig *tls.Config) *http.Transport {
// See https://code.google.com/p/go/issues/detail?id=4677
// We need to force the connection to close each time so that we don't
// hit the above Go bug.
transport := &http.Transport{
TLSClientConfig: tlsConfig,
DisableKeepAlives: true,
Dial: dial,
}
registerFileProtocol(transport)
return transport
}
// BasicAuthHeader creates a header that contains just the "Authorization"
// entry. The implementation was originally taked from net/http but this is
// needed externally from the http request object in order to use this with
// our websockets. See 2 (end of page 4) http://www.ietf.org/rfc/rfc2617.txt
// "To receive authorization, the client sends the userid and password,
// separated by a single colon (":") character, within a base64 encoded string
// in the credentials."
func BasicAuthHeader(username, password string) http.Header {
auth := username + ":" + password
encoded := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
return http.Header{
"Authorization": {encoded},
}
}
// ParseBasicAuth attempts to find an Authorization header in the supplied
// http.Header and if found parses it as a Basic header. See 2 (end of page 4)
// http://www.ietf.org/rfc/rfc2617.txt "To receive authorization, the client
// sends the userid and password, separated by a single colon (":") character,
// within a base64 encoded string in the credentials."
func ParseBasicAuthHeader(h http.Header) (userid, password string, err error) {
parts := strings.Fields(h.Get("Authorization"))
if len(parts) != 2 || parts[0] != "Basic" {
return "", "", fmt.Errorf("invalid or missing HTTP auth header")
}
// Challenge is a base64-encoded "tag:pass" string.
// See RFC 2617, Section 2.
challenge, err := base64.StdEncoding.DecodeString(parts[1])
if err != nil {
return "", "", fmt.Errorf("invalid HTTP auth encoding")
}
tokens := strings.SplitN(string(challenge), ":", 2)
if len(tokens) != 2 {
return "", "", fmt.Errorf("invalid HTTP auth contents")
}
return tokens[0], tokens[1], nil
}
// OutgoingAccessAllowed determines whether connections other than
// localhost can be dialled.
var OutgoingAccessAllowed = true
// Override for tests.
var netDial = net.Dial
func dial(network, addr string) (net.Conn, error) {
if !OutgoingAccessAllowed {
host, _, err := net.SplitHostPort(addr)
if err != nil {
return netDial(network, addr)
}
if host != "localhost" {
ip := net.ParseIP(host)
if ip == nil || !ip.IsLoopback() {
return nil, fmt.Errorf("access to address %q not allowed", addr)
}
}
}
return netDial(network, addr)
}