Skip to content

Commit 83359dc

Browse files
committed
add mtls
Signed-off-by: Tim <[email protected]>
1 parent 0aa30d5 commit 83359dc

15 files changed

+152
-50
lines changed

apis/disposablerequest/v1alpha2/disposablerequest_types.go

+3
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ type DisposableRequestParameters struct {
4545
// InsecureSkipTLSVerify, when set to true, skips TLS certificate checks for the HTTP request
4646
InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"`
4747

48+
// TlsSecretRef expects a reference to an opaque secret containing tls.crt and tls.key or/and ca.crt
49+
TlsSecretRef xpv1.SecretReference `json:"tlsSecretRef,omitempty"`
50+
4851
// ExpectedResponse is a jq filter expression used to evaluate the HTTP response and determine if it matches the expected criteria.
4952
// The expression should return a boolean; if true, the response is considered expected.
5053
// Example: '.body.job_status == "success"'

apis/disposablerequest/v1alpha2/zz_generated.deepcopy.go

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apis/request/v1alpha2/request_types.go

+3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ type RequestParameters struct {
4242
// InsecureSkipTLSVerify, when set to true, skips TLS certificate checks for the HTTP request
4343
InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"`
4444

45+
// TlsSecretRef expects a reference to an opaque secret containing tls.crt and tls.key or/and ca.crt
46+
TlsSecretRef xpv1.SecretReference `json:"tlsSecretRef,omitempty"`
47+
4548
// SecretInjectionConfig specifies the secrets receiving patches for response data.
4649
SecretInjectionConfigs []SecretInjectionConfig `json:"secretInjectionConfigs,omitempty"`
4750
}

apis/request/v1alpha2/zz_generated.deepcopy.go

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/clients/http/client.go

+44-17
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"bytes"
55
"context"
66
"crypto/tls"
7+
"crypto/x509"
78
"encoding/json"
9+
"errors"
810
"fmt"
911
"io"
1012
"net/http"
@@ -15,12 +17,12 @@ import (
1517

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

2123
type client struct {
22-
log logging.Logger
23-
timeout time.Duration
24+
client http.Client
25+
log logging.Logger
2426
}
2527

2628
type HttpResponse struct {
@@ -46,7 +48,7 @@ type HttpDetails struct {
4648
HttpRequest HttpRequest
4749
}
4850

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

71-
client := &http.Client{
72-
Transport: &http.Transport{
73-
// #nosec G402
74-
TLSClientConfig: &tls.Config{InsecureSkipVerify: skipTLSVerify},
75-
Proxy: http.ProxyFromEnvironment, // Use proxy settings from environment
76-
},
77-
Timeout: hc.timeout,
78-
}
79-
80-
response, err := client.Do(request)
73+
response, err := hc.client.Do(request)
8174
if err != nil {
8275
return HttpDetails{
8376
HttpRequest: requestDetails,
@@ -113,10 +106,21 @@ func (hc *client) SendRequest(ctx context.Context, method string, url string, bo
113106
}
114107

115108
// NewClient returns a new Http Client
116-
func NewClient(log logging.Logger, timeout time.Duration) (Client, error) {
109+
func NewClient(log logging.Logger, timeout time.Duration, certPEMBlock, keyPEMBlock, caPEMBlock []byte, insecureSkipVerify bool) (Client, error) {
110+
tlsConfig, err := tlsConfig(certPEMBlock, keyPEMBlock, caPEMBlock, insecureSkipVerify)
111+
if err != nil {
112+
return nil, err
113+
}
114+
httpClient := http.Client{
115+
Transport: &http.Transport{
116+
TLSClientConfig: tlsConfig,
117+
Proxy: http.ProxyFromEnvironment, // Use proxy settings from environment
118+
},
119+
Timeout: timeout,
120+
}
117121
return &client{
118-
log: log,
119-
timeout: timeout,
122+
client: httpClient,
123+
log: log,
120124
}, nil
121125
}
122126

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

129133
return string(jsonBytes)
130134
}
135+
136+
func tlsConfig(certPEMBlock, keyPEMBlock, caPEMBlock []byte, insecureSkipVerify bool) (*tls.Config, error) {
137+
tlsConfig := &tls.Config{}
138+
if len(certPEMBlock) > 0 && len(keyPEMBlock) > 0 {
139+
certificate, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
140+
if err != nil {
141+
return nil, err
142+
}
143+
tlsConfig.Certificates = []tls.Certificate{certificate}
144+
}
145+
146+
if len(caPEMBlock) > 0 {
147+
caPool := x509.NewCertPool()
148+
if !caPool.AppendCertsFromPEM(caPEMBlock) {
149+
return nil, errors.New("some error appending the ca.crt")
150+
}
151+
tlsConfig.RootCAs = caPool
152+
}
153+
154+
tlsConfig.InsecureSkipVerify = insecureSkipVerify
155+
156+
return tlsConfig, nil
157+
}

internal/controller/disposablerequest/disposablerequest.go

+18-3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
apisv1alpha1 "github.com/crossplane-contrib/provider-http/apis/v1alpha1"
4343
httpClient "github.com/crossplane-contrib/provider-http/internal/clients/http"
4444
"github.com/crossplane-contrib/provider-http/internal/utils"
45+
corev1 "k8s.io/api/core/v1"
4546
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
4647
)
4748

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

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

118-
h, err := c.newHttpClientFn(l, utils.WaitTimeout(cr.Spec.ForProvider.WaitTimeout))
119+
secret := &corev1.Secret{}
120+
121+
if cr.Spec.ForProvider.TlsSecretRef.Name != "" && cr.Spec.ForProvider.TlsSecretRef.Namespace != "" {
122+
if err := c.kube.Get(ctx, types.NamespacedName{
123+
Namespace: cr.Spec.ForProvider.TlsSecretRef.Namespace,
124+
Name: cr.Spec.ForProvider.TlsSecretRef.Name,
125+
}, secret); err != nil {
126+
return nil, errors.Wrap(err, errGetReferencedSecret)
127+
}
128+
}
129+
certPEMBlock := secret.Data["tls.crt"]
130+
keyPEMBlock := secret.Data["tls.key"]
131+
caPEMBlock := secret.Data["ca.crt"]
132+
133+
h, err := c.newHttpClientFn(l, utils.WaitTimeout(cr.Spec.ForProvider.WaitTimeout), certPEMBlock, keyPEMBlock, caPEMBlock, cr.Spec.ForProvider.InsecureSkipTLSVerify)
119134
if err != nil {
120135
return nil, errors.Wrap(err, errNewHttpClient)
121136
}
@@ -184,7 +199,7 @@ func (c *external) deployAction(ctx context.Context, cr *v1alpha2.DisposableRequ
184199

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

189204
sensitiveResponse := details.HttpResponse
190205
resource := &utils.RequestResource{

internal/controller/disposablerequest/disposablerequest_test.go

+10-10
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,14 @@ func httpDisposableRequest(rm ...httpDisposableRequestModifier) *v1alpha2.Dispos
103103
return r
104104
}
105105

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

108108
type MockHttpClient struct {
109109
MockSendRequest MockSendRequestFn
110110
}
111111

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

116116
type notHttpDisposableRequest struct {
@@ -143,7 +143,7 @@ func Test_httpExternal_Create(t *testing.T) {
143143
"DisposableRequestFailed": {
144144
args: args{
145145
http: &MockHttpClient{
146-
MockSendRequest: func(ctx context.Context, method string, url string, body httpClient.Data, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
146+
MockSendRequest: func(ctx context.Context, method string, url string, body httpClient.Data, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
147147
return httpClient.HttpDetails{}, errBoom
148148
},
149149
},
@@ -161,7 +161,7 @@ func Test_httpExternal_Create(t *testing.T) {
161161
"Success": {
162162
args: args{
163163
http: &MockHttpClient{
164-
MockSendRequest: func(ctx context.Context, method string, url string, body httpClient.Data, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
164+
MockSendRequest: func(ctx context.Context, method string, url string, body httpClient.Data, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
165165
return httpClient.HttpDetails{}, nil
166166
},
167167
},
@@ -219,7 +219,7 @@ func Test_httpExternal_Update(t *testing.T) {
219219
"DisposableRequestFailed": {
220220
args: args{
221221
http: &MockHttpClient{
222-
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
222+
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
223223
return httpClient.HttpDetails{}, errBoom
224224
},
225225
},
@@ -236,7 +236,7 @@ func Test_httpExternal_Update(t *testing.T) {
236236
"Success": {
237237
args: args{
238238
http: &MockHttpClient{
239-
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
239+
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
240240
return httpClient.HttpDetails{}, nil
241241
},
242242
},
@@ -290,7 +290,7 @@ func Test_deployAction(t *testing.T) {
290290
"SuccessUpdateStatusRequestFailure": {
291291
args: args{
292292
http: &MockHttpClient{
293-
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
293+
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
294294
return httpClient.HttpDetails{}, errors.Errorf(utils.ErrInvalidURL, "invalid-url")
295295
},
296296
},
@@ -318,7 +318,7 @@ func Test_deployAction(t *testing.T) {
318318
"SuccessUpdateStatusCodeError": {
319319
args: args{
320320
http: &MockHttpClient{
321-
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
321+
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
322322
return httpClient.HttpDetails{
323323
HttpResponse: httpClient.HttpResponse{
324324
StatusCode: 400,
@@ -356,7 +356,7 @@ func Test_deployAction(t *testing.T) {
356356
"SuccessUpdateStatusSuccessfulRequest": {
357357
args: args{
358358
http: &MockHttpClient{
359-
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
359+
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
360360
return httpClient.HttpDetails{
361361
HttpResponse: httpClient.HttpResponse{
362362
StatusCode: 200,

internal/controller/request/observe.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func (c *external) isUpToDate(ctx context.Context, cr *v1alpha2.Request) (Observ
5454
return FailedObserve(), err
5555
}
5656

57-
details, responseErr := c.http.SendRequest(ctx, http.MethodGet, requestDetails.Url, requestDetails.Body, requestDetails.Headers, cr.Spec.ForProvider.InsecureSkipTLSVerify)
57+
details, responseErr := c.http.SendRequest(ctx, http.MethodGet, requestDetails.Url, requestDetails.Body, requestDetails.Headers)
5858
if details.HttpResponse.StatusCode == http.StatusNotFound {
5959
return FailedObserve(), errors.New(errObjectNotFound)
6060
}

internal/controller/request/observe_test.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func Test_isUpToDate(t *testing.T) {
3636
"ObjectNotFoundEmptyBody": {
3737
args: args{
3838
http: &MockHttpClient{
39-
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
39+
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
4040
return httpClient.HttpDetails{}, nil
4141
},
4242
},
@@ -54,7 +54,7 @@ func Test_isUpToDate(t *testing.T) {
5454
"ObjectNotFoundPostFailed": {
5555
args: args{
5656
http: &MockHttpClient{
57-
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
57+
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
5858
return httpClient.HttpDetails{}, nil
5959
},
6060
},
@@ -73,7 +73,7 @@ func Test_isUpToDate(t *testing.T) {
7373
"ObjectNotFound404StatusCode": {
7474
args: args{
7575
http: &MockHttpClient{
76-
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
76+
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
7777
return httpClient.HttpDetails{}, nil
7878
},
7979
},
@@ -91,7 +91,7 @@ func Test_isUpToDate(t *testing.T) {
9191
"FailBodyNotJSON": {
9292
args: args{
9393
http: &MockHttpClient{
94-
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
94+
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
9595
return httpClient.HttpDetails{
9696
HttpResponse: httpClient.HttpResponse{
9797
Body: "not a JSON",
@@ -113,7 +113,7 @@ func Test_isUpToDate(t *testing.T) {
113113
"SuccessNotSynced": {
114114
args: args{
115115
http: &MockHttpClient{
116-
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
116+
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
117117
return httpClient.HttpDetails{
118118
HttpResponse: httpClient.HttpResponse{
119119
Body: `{"username":"old_name"}`,
@@ -147,7 +147,7 @@ func Test_isUpToDate(t *testing.T) {
147147
"SuccessNoPUTMapping": {
148148
args: args{
149149
http: &MockHttpClient{
150-
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
150+
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
151151
return httpClient.HttpDetails{
152152
HttpResponse: httpClient.HttpResponse{
153153
Body: `{"username":"old_name"}`,
@@ -187,7 +187,7 @@ func Test_isUpToDate(t *testing.T) {
187187
"SuccessJSONBody": {
188188
args: args{
189189
http: &MockHttpClient{
190-
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data, skipTLSVerify bool) (resp httpClient.HttpDetails, err error) {
190+
MockSendRequest: func(ctx context.Context, method string, url string, body, headers httpClient.Data) (resp httpClient.HttpDetails, err error) {
191191
return httpClient.HttpDetails{
192192
HttpResponse: httpClient.HttpResponse{
193193
Body: `{"username":"john_doe_new_username"}`,

internal/controller/request/request.go

+19-3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
"github.com/crossplane-contrib/provider-http/internal/controller/request/statushandler"
4343
datapatcher "github.com/crossplane-contrib/provider-http/internal/data-patcher"
4444
"github.com/crossplane-contrib/provider-http/internal/utils"
45+
corev1 "k8s.io/api/core/v1"
4546
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
4647
)
4748

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

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

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

121-
h, err := c.newHttpClientFn(l, utils.WaitTimeout(cr.Spec.ForProvider.WaitTimeout))
123+
secret := &corev1.Secret{}
124+
125+
if cr.Spec.ForProvider.TlsSecretRef.Name != "" && cr.Spec.ForProvider.TlsSecretRef.Namespace != "" {
126+
if err := c.kube.Get(ctx, types.NamespacedName{
127+
Namespace: cr.Spec.ForProvider.TlsSecretRef.Namespace,
128+
Name: cr.Spec.ForProvider.TlsSecretRef.Name,
129+
}, secret); err != nil {
130+
return nil, errors.Wrap(err, errGetReferencedSecret)
131+
}
132+
}
133+
certPEMBlock := secret.Data["tls.crt"]
134+
keyPEMBlock := secret.Data["tls.key"]
135+
caPEMBlock := secret.Data["ca.crt"]
136+
137+
h, err := c.newHttpClientFn(l, utils.WaitTimeout(cr.Spec.ForProvider.WaitTimeout), certPEMBlock, keyPEMBlock, caPEMBlock, cr.Spec.ForProvider.InsecureSkipTLSVerify)
122138
if err != nil {
123139
return nil, errors.Wrap(err, errNewHttpClient)
124140
}
@@ -195,7 +211,7 @@ func (c *external) deployAction(ctx context.Context, cr *v1alpha2.Request, metho
195211
return err
196212
}
197213

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

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

0 commit comments

Comments
 (0)