Skip to content

Commit

Permalink
Merge pull request #2267 from eirini-forks/add-auth-to-logs
Browse files Browse the repository at this point in the history
Add auth header to logcache API requests for cf-on-k8s
  • Loading branch information
reedr3 authored Mar 25, 2022
2 parents 45447b3 + 504d4e8 commit aad94f8
Show file tree
Hide file tree
Showing 16 changed files with 967 additions and 120 deletions.
98 changes: 7 additions & 91 deletions api/cloudcontroller/wrapper/kubernetes_authentication.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,12 @@
package wrapper

import (
"bytes"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"net/http"

"code.cloudfoundry.org/cli/actor/v7action"
"code.cloudfoundry.org/cli/api/cloudcontroller"
"code.cloudfoundry.org/cli/api/shared"
"code.cloudfoundry.org/cli/command"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
"k8s.io/client-go/transport"

_ "k8s.io/client-go/plugin/pkg/client/auth/azure"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
)

type KubernetesAuthentication struct {
Expand All @@ -40,87 +27,16 @@ func NewKubernetesAuthentication(
}

func (a *KubernetesAuthentication) Make(request *cloudcontroller.Request, passedResponse *cloudcontroller.Response) error {
username, err := a.config.CurrentUserName()
roundTripper, err := shared.WrapForCFOnK8sAuth(a.config, a.k8sConfigGetter, connectionRoundTripper{
connection: a.connection,
ccRequest: request,
ccResponse: passedResponse,
})
if err != nil {
return err
}
if username == "" {
return errors.New("current user not set")
}

k8sConfig, err := a.k8sConfigGetter.Get()
if err != nil {
return err
}

restConfig, err := clientcmd.NewDefaultClientConfig(
*k8sConfig,
&clientcmd.ConfigOverrides{
Context: api.Context{AuthInfo: username},
}).ClientConfig()
if err != nil {
return err
}

tlsConfig, err := rest.TLSConfigFor(restConfig)
if err != nil {
return fmt.Errorf("failed to get tls config: %w", err)
}

if tlsConfig != nil && tlsConfig.GetClientCertificate != nil {
cert, err := tlsConfig.GetClientCertificate(nil)
if err != nil {
return fmt.Errorf("failed to get client certificate: %w", err)
}

if len(cert.Certificate) > 0 && cert.PrivateKey != nil {
var buf bytes.Buffer

if err := pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Certificate[0]}); err != nil {
return fmt.Errorf("could not convert certificate to PEM format: %w", err)
}

key, err := x509.MarshalPKCS8PrivateKey(cert.PrivateKey)
if err != nil {
return fmt.Errorf("could not marshal private key: %w", err)
}

if err := pem.Encode(&buf, &pem.Block{Type: "PRIVATE KEY", Bytes: key}); err != nil {
return fmt.Errorf("could not convert key to PEM format: %w", err)
}

auth := "ClientCert " + base64.StdEncoding.EncodeToString(buf.Bytes())
request.Header.Set("Authorization", auth)

return a.connection.Make(request, passedResponse)
}
}

transportConfig, err := restConfig.TransportConfig()
if err != nil {
return fmt.Errorf("failed to get transport config: %w", err)
}

var roundtripper http.RoundTripper
if transportConfig.WrapTransport == nil {
// i.e. not auth-provider or exec plugin
roundtripper, err = transport.HTTPWrappersForConfig(transportConfig, connectionRoundTripper{
connection: a.connection,
ccRequest: request,
ccResponse: passedResponse,
})
if err != nil {
return fmt.Errorf("failed to create new transport: %w", err)
}
} else {
roundtripper = transportConfig.WrapTransport(connectionRoundTripper{
connection: a.connection,
ccRequest: request,
ccResponse: passedResponse,
})
}

_, err = roundtripper.RoundTrip(request.Request)
_, err = roundTripper.RoundTrip(request.Request)

return err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ var _ = Describe("KubernetesAuthentication", func() {
})
})

When("the chosen kubeernetes auth info is not present in kubeconfig", func() {
When("the chosen kubernetes auth info is not present in kubeconfig", func() {
BeforeEach(func() {
config.CurrentUserNameReturns("not-present", nil)
})
Expand Down
56 changes: 41 additions & 15 deletions command/log_cache_client.go → api/logcache/log_cache_client.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package command
package logcache

import (
"fmt"
Expand All @@ -8,8 +8,12 @@ import (
"strings"
"time"

"code.cloudfoundry.org/cli/util"
logcache "code.cloudfoundry.org/go-log-cache"

"code.cloudfoundry.org/cli/actor/v7action"
"code.cloudfoundry.org/cli/api/shared"
"code.cloudfoundry.org/cli/command"
"code.cloudfoundry.org/cli/util"
)

type RequestLoggerOutput interface {
Expand Down Expand Up @@ -62,15 +66,23 @@ func (p *DebugPrinter) addOutput(output RequestLoggerOutput) {
p.outputs = append(p.outputs, output)
}

type userAgentHTTPClient struct {
c logcache.HTTPClient
userAgent string
}

func (c *userAgentHTTPClient) Do(req *http.Request) (*http.Response, error) {
req.Header.Set("User-Agent", c.userAgent)
return c.c.Do(req)
}

type tokenHTTPClient struct {
c logcache.HTTPClient
accessToken func() string
userAgent string
}

func (c *tokenHTTPClient) Do(req *http.Request) (*http.Response, error) {
req.Header.Set("Authorization", c.accessToken())
req.Header.Set("User-Agent", c.userAgent)
return c.c.Do(req)
}

Expand All @@ -93,9 +105,9 @@ func (c *httpDebugClient) Do(req *http.Request) (*http.Response, error) {
return resp, err
}

// NewLogCacheClient returns back a configured Log Cache Client.
func NewLogCacheClient(logCacheEndpoint string, config Config, ui UI) *logcache.Client {
tr := &http.Transport{
// NewClient returns back a configured Log Cache Client.
func NewClient(logCacheEndpoint string, config command.Config, ui command.UI, k8sConfigGetter v7action.KubernetesConfigGetter) (*logcache.Client, error) {
var tr http.RoundTripper = &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: util.NewTLSConfig(nil, config.SkipSSLValidation()),
DialContext: (&net.Dialer{
Expand All @@ -104,8 +116,19 @@ func NewLogCacheClient(logCacheEndpoint string, config Config, ui UI) *logcache.
}).DialContext,
}

if config.IsCFOnK8s() {
var err error
tr, err = shared.WrapForCFOnK8sAuth(config, k8sConfigGetter, tr)
if err != nil {
return nil, err
}
}

var client logcache.HTTPClient //nolint
client = &http.Client{Transport: tr}
client = &userAgentHTTPClient{
c: &http.Client{Transport: tr},
userAgent: fmt.Sprintf("%s/%s (%s; %s %s)", config.BinaryName(), config.BinaryVersion(), runtime.Version(), runtime.GOARCH, runtime.GOOS),
}

verbose, location := config.Verbose()
if verbose && ui != nil {
Expand All @@ -118,16 +141,19 @@ func NewLogCacheClient(logCacheEndpoint string, config Config, ui UI) *logcache.
client = &httpDebugClient{printer: printer, c: client}
}

userAgent := fmt.Sprintf("%s/%s (%s; %s %s)", config.BinaryName(), config.BinaryVersion(), runtime.Version(), runtime.GOARCH, runtime.GOOS)
return logcache.NewClient(
logCacheEndpoint,
logcache.WithHTTPClient(&tokenHTTPClient{
if !config.IsCFOnK8s() {
client = &tokenHTTPClient{
c: client,
accessToken: config.AccessToken,
userAgent: userAgent,
}),
)
}
}

return logcache.NewClient(
logCacheEndpoint,
logcache.WithHTTPClient(client),
), nil
}

func headersString(header http.Header) string {
var result string
for name, values := range header {
Expand Down
25 changes: 25 additions & 0 deletions api/shared/shared_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package shared_test

import (
"crypto/rand"
"crypto/rsa"
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestShared(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Shared Wrapper Suite")
}

var (
keyPair *rsa.PrivateKey
)

var _ = BeforeEach(func() {
var err error
keyPair, err = rsa.GenerateKey(rand.Reader, 2048)
Expect(err).NotTo(HaveOccurred())
})
114 changes: 114 additions & 0 deletions api/shared/sharedfakes/fake_round_tripper.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit aad94f8

Please sign in to comment.