Skip to content

Commit

Permalink
Merge pull request #29 from humio/mike/ca-cert
Browse files Browse the repository at this point in the history
Add flags for using specific CA certs and disabling TLS cert verification
  • Loading branch information
SaaldjorMike authored Jul 23, 2020
2 parents f764cfe + c7a28dc commit b8323ee
Show file tree
Hide file tree
Showing 8 changed files with 283 additions and 45 deletions.
40 changes: 24 additions & 16 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@ import (
"net/http"

"github.com/shurcooL/graphql"
"golang.org/x/oauth2"
)

type Client struct {
config Config
}

type Config struct {
Address string
Token string
Address string
Token string
CACertificate []byte
Insecure bool
}

func DefaultConfig() Config {
config := Config{
Address: "",
Token: "",
Address: "",
Token: "",
CACertificate: []byte{},
Insecure: false,
}

return config
Expand All @@ -35,18 +38,24 @@ func (c *Client) Token() string {
return c.config.Token
}

func (c *Client) CACertificate() []byte {
return c.config.CACertificate
}

func (c *Client) Insecure() bool {
return c.config.Insecure
}

func NewClient(config Config) (*Client, error) {
return &Client{
config: config,
}, nil
}

func (c *Client) newGraphQLClient() *graphql.Client {
src := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: c.config.Token},
)

httpClient := oauth2.NewClient(context.Background(), src)
httpClient := c.newHTTPClientWithHeaders(map[string]string{
"Authorization": "Bearer " + c.Token(),
})
return graphql.NewClient(c.Address()+"graphql", httpClient)
}

Expand All @@ -72,16 +81,15 @@ func (c *Client) HTTPRequestContext(ctx context.Context, httpMethod string, path
}

url := c.Address() + path

req, reqErr := http.NewRequestWithContext(ctx, httpMethod, url, body)
req.Header.Set("Authorization", "Bearer "+c.Token())
req.Header.Set("Content-Type", "application/json")

var client = &http.Client{}

if reqErr != nil {
return nil, reqErr
}

var client = c.newHTTPClientWithHeaders(map[string]string{
"Authorization": "Bearer " + c.Token(),
"Content-Type": "application/json",
})
return client.Do(req)
}

Expand Down
118 changes: 118 additions & 0 deletions api/httpclient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package api

import (
"crypto/tls"
"crypto/x509"
"net"
"net/http"
"time"
)

// We must our own http.Client which adds the authorization header in all requests sent to Humio.
// We use the approach described here: https://github.com/shurcooL/graphql/issues/28#issuecomment-464713908

type headerTransport struct {
base http.RoundTripper
headers map[string]string
}

// NewHTTPClientWithHeaders returns a *http.Client that attaches a defined set of Headers to all requests.
// If specified, the client will also trust the CA certificate specified in the client configuration.
func (c *Client) newHTTPClientWithHeaders(headers map[string]string) *http.Client {
if c.config.Insecure {
// Return HTTP client where we skip certificate verification
return &http.Client{
Transport: &headerTransport{
base: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,

TLSClientConfig: &tls.Config{
InsecureSkipVerify: c.config.Insecure,
},
},
headers: headers,
},
}
}

if len(c.config.CACertificate) > 0 {
// Create a certificate pool and return a HTTP client with the specified specified CA certificate.
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(c.config.CACertificate)
return &http.Client{
Transport: &headerTransport{
base: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,

TLSClientConfig: &tls.Config{
RootCAs: caCertPool,
InsecureSkipVerify: c.config.Insecure,
},
},
headers: headers,
},
}
}

// Return a regular default HTTP client
return &http.Client{
Transport: &headerTransport{
base: http.DefaultTransport,
headers: headers,
},
}
}

func (h *headerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req2 := CloneRequest(req)
for key, val := range h.headers {
req2.Header.Set(key, val)
}
return h.base.RoundTrip(req2)
}

// CloneRequest and CloneHeader copied from https://github.com/kubernetes/apimachinery/blob/a76b7114b20a2e56fd698bba815b1e2c82ec4bff/pkg/util/net/http.go#L469-L491

// CloneRequest creates a shallow copy of the request along with a deep copy of the Headers.
func CloneRequest(req *http.Request) *http.Request {
r := new(http.Request)

// shallow clone
*r = *req

// deep copy headers
r.Header = CloneHeader(req.Header)

return r
}

// CloneHeader creates a deep copy of an http.Header.
func CloneHeader(in http.Header) http.Header {
out := make(http.Header, len(in))
for key, values := range in {
newValues := make([]string, len(values))
copy(newValues, values)
out[key] = newValues
}
return out
}
8 changes: 4 additions & 4 deletions cmd/cluster_show.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,19 @@ func printClusterInfo(cmd *cobra.Command, cluster api.Cluster) {

header := []string{"Description", "Current", "Target"}
data := [][]string{
[]string{
{
"Under replicated segment (Size)",
ByteCountDecimal(int64(cluster.UnderReplicatedSegmentSize)),
ByteCountDecimal(int64(cluster.TargetUnderReplicatedSegmentSize))},
[]string{
{
"Over replicated segment (Size)",
ByteCountDecimal(int64(cluster.OverReplicatedSegmentSize)),
ByteCountDecimal(int64(cluster.TargetOverReplicatedSegmentSize))},
[]string{
{
"Missing segment (Size)",
ByteCountDecimal(int64(cluster.MissingSegmentSize)),
ByteCountDecimal(int64(cluster.TargetMissingSegmentSize))},
[]string{
{
"Properly replicated segment (Size)",
ByteCountDecimal(int64(cluster.ProperlyReplicatedSegmentSize)),
ByteCountDecimal(int64(cluster.TargetProperlyReplicatedSegmentSize))},
Expand Down
15 changes: 11 additions & 4 deletions cmd/profiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ package cmd

import (
"fmt"
"os"

"github.com/humio/cli/prompt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

type login struct {
address string
token string
username string
address string
token string
username string
caCertificate []byte
insecure bool
}

// usersCmd represents the users command
Expand All @@ -37,7 +40,11 @@ You can change the default profile using:
profiles := viper.GetStringMap("profiles")

for name, data := range profiles {
login := mapToLogin(data)
login, err := mapToLogin(data)
if err != nil {
cmd.Println(prompt.Colorize(fmt.Sprintf("* [red]Error reading existing Humio profile: %s\n Remove the configuration file %s and add the profile again.[reset]", err, cfgFile)))
os.Exit(1)
}
if isCurrentAccount(login.address, login.token) {
cmd.Println(prompt.Colorize(fmt.Sprintf("* [purple]%s (%s) - %s[reset]", name, login.username, login.address)))
} else {
Expand Down
Loading

0 comments on commit b8323ee

Please sign in to comment.