Skip to content

Commit

Permalink
add mtls
Browse files Browse the repository at this point in the history
Signed-off-by: Tim <[email protected]>
  • Loading branch information
Avarei committed Sep 11, 2024
1 parent 0aa30d5 commit 83359dc
Show file tree
Hide file tree
Showing 15 changed files with 152 additions and 50 deletions.
3 changes: 3 additions & 0 deletions apis/disposablerequest/v1alpha2/disposablerequest_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ type DisposableRequestParameters struct {
// InsecureSkipTLSVerify, when set to true, skips TLS certificate checks for the HTTP request
InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"`

// TlsSecretRef expects a reference to an opaque secret containing tls.crt and tls.key or/and ca.crt
TlsSecretRef xpv1.SecretReference `json:"tlsSecretRef,omitempty"`

// ExpectedResponse is a jq filter expression used to evaluate the HTTP response and determine if it matches the expected criteria.
// The expression should return a boolean; if true, the response is considered expected.
// Example: '.body.job_status == "success"'
Expand Down
1 change: 1 addition & 0 deletions apis/disposablerequest/v1alpha2/zz_generated.deepcopy.go

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

3 changes: 3 additions & 0 deletions apis/request/v1alpha2/request_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ type RequestParameters struct {
// InsecureSkipTLSVerify, when set to true, skips TLS certificate checks for the HTTP request
InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"`

// TlsSecretRef expects a reference to an opaque secret containing tls.crt and tls.key or/and ca.crt
TlsSecretRef xpv1.SecretReference `json:"tlsSecretRef,omitempty"`

// SecretInjectionConfig specifies the secrets receiving patches for response data.
SecretInjectionConfigs []SecretInjectionConfig `json:"secretInjectionConfigs,omitempty"`
}
Expand Down
1 change: 1 addition & 0 deletions apis/request/v1alpha2/zz_generated.deepcopy.go

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

61 changes: 44 additions & 17 deletions internal/clients/http/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
Expand All @@ -15,12 +17,12 @@ import (

// Client is the interface to interact with Http
type Client interface {
SendRequest(ctx context.Context, method string, url string, body Data, headers Data, skipTLSVerify bool) (resp HttpDetails, err error)
SendRequest(ctx context.Context, method string, url string, body Data, headers Data) (resp HttpDetails, err error)
}

type client struct {
log logging.Logger
timeout time.Duration
client http.Client
log logging.Logger
}

type HttpResponse struct {
Expand All @@ -46,7 +48,7 @@ type HttpDetails struct {
HttpRequest HttpRequest
}

func (hc *client) SendRequest(ctx context.Context, method string, url string, body Data, headers Data, skipTLSVerify bool) (details HttpDetails, err error) {
func (hc *client) SendRequest(ctx context.Context, method string, url string, body Data, headers Data) (details HttpDetails, err error) {
requestBody := []byte(body.Decrypted.(string))
request, err := http.NewRequestWithContext(ctx, method, url, bytes.NewBuffer(requestBody))
requestDetails := HttpRequest{
Expand All @@ -68,16 +70,7 @@ func (hc *client) SendRequest(ctx context.Context, method string, url string, bo
}
}

client := &http.Client{
Transport: &http.Transport{
// #nosec G402
TLSClientConfig: &tls.Config{InsecureSkipVerify: skipTLSVerify},
Proxy: http.ProxyFromEnvironment, // Use proxy settings from environment
},
Timeout: hc.timeout,
}

response, err := client.Do(request)
response, err := hc.client.Do(request)
if err != nil {
return HttpDetails{
HttpRequest: requestDetails,
Expand Down Expand Up @@ -113,10 +106,21 @@ func (hc *client) SendRequest(ctx context.Context, method string, url string, bo
}

// NewClient returns a new Http Client
func NewClient(log logging.Logger, timeout time.Duration) (Client, error) {
func NewClient(log logging.Logger, timeout time.Duration, certPEMBlock, keyPEMBlock, caPEMBlock []byte, insecureSkipVerify bool) (Client, error) {
tlsConfig, err := tlsConfig(certPEMBlock, keyPEMBlock, caPEMBlock, insecureSkipVerify)
if err != nil {
return nil, err
}
httpClient := http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
Proxy: http.ProxyFromEnvironment, // Use proxy settings from environment
},
Timeout: timeout,
}
return &client{
log: log,
timeout: timeout,
client: httpClient,
log: log,
}, nil
}

Expand All @@ -128,3 +132,26 @@ func toJSON(request HttpRequest) string {

return string(jsonBytes)
}

func tlsConfig(certPEMBlock, keyPEMBlock, caPEMBlock []byte, insecureSkipVerify bool) (*tls.Config, error) {
tlsConfig := &tls.Config{}
if len(certPEMBlock) > 0 && len(keyPEMBlock) > 0 {
certificate, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{certificate}
}

if len(caPEMBlock) > 0 {
caPool := x509.NewCertPool()
if !caPool.AppendCertsFromPEM(caPEMBlock) {
return nil, errors.New("some error appending the ca.crt")
}
tlsConfig.RootCAs = caPool
}

tlsConfig.InsecureSkipVerify = insecureSkipVerify

return tlsConfig, nil
}
21 changes: 18 additions & 3 deletions internal/controller/disposablerequest/disposablerequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
apisv1alpha1 "github.com/crossplane-contrib/provider-http/apis/v1alpha1"
httpClient "github.com/crossplane-contrib/provider-http/internal/clients/http"
"github.com/crossplane-contrib/provider-http/internal/utils"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand Down Expand Up @@ -94,7 +95,7 @@ type connector struct {
logger logging.Logger
kube client.Client
usage resource.Tracker
newHttpClientFn func(log logging.Logger, timeout time.Duration) (httpClient.Client, error)
newHttpClientFn func(log logging.Logger, timeout time.Duration, certPEMBlock, keyPEMBlock, caPEMBlock []byte, insecureSkipVerify bool) (httpClient.Client, error)
}

func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) {
Expand All @@ -115,7 +116,21 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E
return nil, errors.Wrap(err, errProviderNotRetrieved)
}

h, err := c.newHttpClientFn(l, utils.WaitTimeout(cr.Spec.ForProvider.WaitTimeout))
secret := &corev1.Secret{}

if cr.Spec.ForProvider.TlsSecretRef.Name != "" && cr.Spec.ForProvider.TlsSecretRef.Namespace != "" {
if err := c.kube.Get(ctx, types.NamespacedName{
Namespace: cr.Spec.ForProvider.TlsSecretRef.Namespace,
Name: cr.Spec.ForProvider.TlsSecretRef.Name,
}, secret); err != nil {
return nil, errors.Wrap(err, errGetReferencedSecret)
}
}
certPEMBlock := secret.Data["tls.crt"]
keyPEMBlock := secret.Data["tls.key"]
caPEMBlock := secret.Data["ca.crt"]

h, err := c.newHttpClientFn(l, utils.WaitTimeout(cr.Spec.ForProvider.WaitTimeout), certPEMBlock, keyPEMBlock, caPEMBlock, cr.Spec.ForProvider.InsecureSkipTLSVerify)
if err != nil {
return nil, errors.Wrap(err, errNewHttpClient)
}
Expand Down Expand Up @@ -184,7 +199,7 @@ func (c *external) deployAction(ctx context.Context, cr *v1alpha2.DisposableRequ

bodyData := httpClient.Data{Encrypted: cr.Spec.ForProvider.Body, Decrypted: sensitiveBody}
headersData := httpClient.Data{Encrypted: cr.Spec.ForProvider.Headers, Decrypted: sensitiveHeaders}
details, err := c.http.SendRequest(ctx, cr.Spec.ForProvider.Method, cr.Spec.ForProvider.URL, bodyData, headersData, cr.Spec.ForProvider.InsecureSkipTLSVerify)
details, err := c.http.SendRequest(ctx, cr.Spec.ForProvider.Method, cr.Spec.ForProvider.URL, bodyData, headersData)

sensitiveResponse := details.HttpResponse
resource := &utils.RequestResource{
Expand Down
20 changes: 10 additions & 10 deletions internal/controller/disposablerequest/disposablerequest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,14 @@ func httpDisposableRequest(rm ...httpDisposableRequestModifier) *v1alpha2.Dispos
return r
}

type MockSendRequestFn func(ctx context.Context, method string, url string, body httpClient.Data, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error)
type MockSendRequestFn func(ctx context.Context, method string, url string, body httpClient.Data, headers httpClient.Data) (resp httpClient.HttpDetails, err error)

type MockHttpClient struct {
MockSendRequest MockSendRequestFn
}

func (c *MockHttpClient) SendRequest(ctx context.Context, method string, url string, body httpClient.Data, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
return c.MockSendRequest(ctx, method, url, body, headers, skipTLSVerify)
func (c *MockHttpClient) SendRequest(ctx context.Context, method string, url string, body httpClient.Data, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
return c.MockSendRequest(ctx, method, url, body, headers)
}

type notHttpDisposableRequest struct {
Expand Down Expand Up @@ -143,7 +143,7 @@ func Test_httpExternal_Create(t *testing.T) {
"DisposableRequestFailed": {
args: args{
http: &MockHttpClient{
MockSendRequest: func(ctx context.Context, method string, url string, body httpClient.Data, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
MockSendRequest: func(ctx context.Context, method string, url string, body httpClient.Data, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
return httpClient.HttpDetails{}, errBoom
},
},
Expand All @@ -161,7 +161,7 @@ func Test_httpExternal_Create(t *testing.T) {
"Success": {
args: args{
http: &MockHttpClient{
MockSendRequest: func(ctx context.Context, method string, url string, body httpClient.Data, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
MockSendRequest: func(ctx context.Context, method string, url string, body httpClient.Data, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
return httpClient.HttpDetails{}, nil
},
},
Expand Down Expand Up @@ -219,7 +219,7 @@ func Test_httpExternal_Update(t *testing.T) {
"DisposableRequestFailed": {
args: args{
http: &MockHttpClient{
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
return httpClient.HttpDetails{}, errBoom
},
},
Expand All @@ -236,7 +236,7 @@ func Test_httpExternal_Update(t *testing.T) {
"Success": {
args: args{
http: &MockHttpClient{
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
return httpClient.HttpDetails{}, nil
},
},
Expand Down Expand Up @@ -290,7 +290,7 @@ func Test_deployAction(t *testing.T) {
"SuccessUpdateStatusRequestFailure": {
args: args{
http: &MockHttpClient{
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
return httpClient.HttpDetails{}, errors.Errorf(utils.ErrInvalidURL, "invalid-url")
},
},
Expand Down Expand Up @@ -318,7 +318,7 @@ func Test_deployAction(t *testing.T) {
"SuccessUpdateStatusCodeError": {
args: args{
http: &MockHttpClient{
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
return httpClient.HttpDetails{
HttpResponse: httpClient.HttpResponse{
StatusCode: 400,
Expand Down Expand Up @@ -356,7 +356,7 @@ func Test_deployAction(t *testing.T) {
"SuccessUpdateStatusSuccessfulRequest": {
args: args{
http: &MockHttpClient{
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
return httpClient.HttpDetails{
HttpResponse: httpClient.HttpResponse{
StatusCode: 200,
Expand Down
2 changes: 1 addition & 1 deletion internal/controller/request/observe.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (c *external) isUpToDate(ctx context.Context, cr *v1alpha2.Request) (Observ
return FailedObserve(), err
}

details, responseErr := c.http.SendRequest(ctx, http.MethodGet, requestDetails.Url, requestDetails.Body, requestDetails.Headers, cr.Spec.ForProvider.InsecureSkipTLSVerify)
details, responseErr := c.http.SendRequest(ctx, http.MethodGet, requestDetails.Url, requestDetails.Body, requestDetails.Headers)
if details.HttpResponse.StatusCode == http.StatusNotFound {
return FailedObserve(), errors.New(errObjectNotFound)
}
Expand Down
14 changes: 7 additions & 7 deletions internal/controller/request/observe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func Test_isUpToDate(t *testing.T) {
"ObjectNotFoundEmptyBody": {
args: args{
http: &MockHttpClient{
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
return httpClient.HttpDetails{}, nil
},
},
Expand All @@ -54,7 +54,7 @@ func Test_isUpToDate(t *testing.T) {
"ObjectNotFoundPostFailed": {
args: args{
http: &MockHttpClient{
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
return httpClient.HttpDetails{}, nil
},
},
Expand All @@ -73,7 +73,7 @@ func Test_isUpToDate(t *testing.T) {
"ObjectNotFound404StatusCode": {
args: args{
http: &MockHttpClient{
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
return httpClient.HttpDetails{}, nil
},
},
Expand All @@ -91,7 +91,7 @@ func Test_isUpToDate(t *testing.T) {
"FailBodyNotJSON": {
args: args{
http: &MockHttpClient{
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
return httpClient.HttpDetails{
HttpResponse: httpClient.HttpResponse{
Body: "not a JSON",
Expand All @@ -113,7 +113,7 @@ func Test_isUpToDate(t *testing.T) {
"SuccessNotSynced": {
args: args{
http: &MockHttpClient{
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
return httpClient.HttpDetails{
HttpResponse: httpClient.HttpResponse{
Body: `{"username":"old_name"}`,
Expand Down Expand Up @@ -147,7 +147,7 @@ func Test_isUpToDate(t *testing.T) {
"SuccessNoPUTMapping": {
args: args{
http: &MockHttpClient{
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
return httpClient.HttpDetails{
HttpResponse: httpClient.HttpResponse{
Body: `{"username":"old_name"}`,
Expand Down Expand Up @@ -187,7 +187,7 @@ func Test_isUpToDate(t *testing.T) {
"SuccessJSONBody": {
args: args{
http: &MockHttpClient{
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
return httpClient.HttpDetails{
HttpResponse: httpClient.HttpResponse{
Body: `{"username":"john_doe_new_username"}`,
Expand Down
22 changes: 19 additions & 3 deletions internal/controller/request/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
"github.com/crossplane-contrib/provider-http/internal/controller/request/statushandler"
datapatcher "github.com/crossplane-contrib/provider-http/internal/data-patcher"
"github.com/crossplane-contrib/provider-http/internal/utils"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand All @@ -57,6 +58,7 @@ const (
errMappingNotFound = "%s mapping doesn't exist in request, skipping operation"
errPatchDataToSecret = "Warning, couldn't patch data from request to secret %s:%s:%s, error: %s"
errGetLatestVersion = "failed to get the latest version of the resource"
errGetReferencedSecret = "cannot get referenced secret"
)

// Setup adds a controller that reconciles Request managed resources.
Expand Down Expand Up @@ -92,7 +94,7 @@ type connector struct {
logger logging.Logger
kube client.Client
usage resource.Tracker
newHttpClientFn func(log logging.Logger, timeout time.Duration) (httpClient.Client, error)
newHttpClientFn func(log logging.Logger, timeout time.Duration, certPEMBlock, keyPEMBlock, caPEMBlock []byte, insecureSkipVerify bool) (httpClient.Client, error)
}

// Connect typically produces an ExternalClient by:
Expand All @@ -118,7 +120,21 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E
return nil, errors.Wrap(err, errProviderNotRetrieved)
}

h, err := c.newHttpClientFn(l, utils.WaitTimeout(cr.Spec.ForProvider.WaitTimeout))
secret := &corev1.Secret{}

if cr.Spec.ForProvider.TlsSecretRef.Name != "" && cr.Spec.ForProvider.TlsSecretRef.Namespace != "" {
if err := c.kube.Get(ctx, types.NamespacedName{
Namespace: cr.Spec.ForProvider.TlsSecretRef.Namespace,
Name: cr.Spec.ForProvider.TlsSecretRef.Name,
}, secret); err != nil {
return nil, errors.Wrap(err, errGetReferencedSecret)
}
}
certPEMBlock := secret.Data["tls.crt"]
keyPEMBlock := secret.Data["tls.key"]
caPEMBlock := secret.Data["ca.crt"]

h, err := c.newHttpClientFn(l, utils.WaitTimeout(cr.Spec.ForProvider.WaitTimeout), certPEMBlock, keyPEMBlock, caPEMBlock, cr.Spec.ForProvider.InsecureSkipTLSVerify)
if err != nil {
return nil, errors.Wrap(err, errNewHttpClient)
}
Expand Down Expand Up @@ -195,7 +211,7 @@ func (c *external) deployAction(ctx context.Context, cr *v1alpha2.Request, metho
return err
}

details, err := c.http.SendRequest(ctx, mapping.Method, requestDetails.Url, requestDetails.Body, requestDetails.Headers, cr.Spec.ForProvider.InsecureSkipTLSVerify)
details, err := c.http.SendRequest(ctx, mapping.Method, requestDetails.Url, requestDetails.Body, requestDetails.Headers)
c.patchResponseToSecret(ctx, cr, &details.HttpResponse)

statusHandler, err := statushandler.NewStatusHandler(ctx, cr, details, err, c.localKube, c.logger)
Expand Down
Loading

0 comments on commit 83359dc

Please sign in to comment.