Skip to content

Commit

Permalink
feat: Add support for authentication credentials in ProviderConfig (#63)
Browse files Browse the repository at this point in the history
Signed-off-by: Ariel Septon <[email protected]>
  • Loading branch information
arielsepton authored Oct 2, 2024
1 parent 2b1f71c commit 0702bc5
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 16 deletions.
2 changes: 1 addition & 1 deletion cluster/test/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ metadata:
name: http-conf
spec:
credentials:
source: InjectedIdentity
source: None
EOF

cat <<EOF | ${KUBECTL} apply -f -
Expand Down
13 changes: 13 additions & 0 deletions examples/provider/auth-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: http.crossplane.io/v1alpha1
kind: ProviderConfig
metadata:
name: http-conf
spec:
credentials:
source: Secret
# The value of this secret will be used to set the "Authorization" header for all requests made with this config.
# It will be added as: "Authorization": "<secret-value>".
secretRef:
namespace: crossplane-system
name: http-provider-secret
key: token
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ metadata:
name: http-conf
spec:
credentials:
source: InjectedIdentity
source: None
File renamed without changes.
23 changes: 18 additions & 5 deletions internal/clients/http/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,19 @@ import (
"github.com/crossplane/crossplane-runtime/pkg/logging"
)

const (
authKey = "Authorization"
)

// 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)
}

type client struct {
log logging.Logger
timeout time.Duration
log logging.Logger
timeout time.Duration
authorizationToken string
}

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

// SendRequest sends an HTTP request to the specified URL with the given method, body, headers and skipTLSVerify.
func (hc *client) SendRequest(ctx context.Context, method string, url string, body Data, headers Data, skipTLSVerify bool) (details HttpDetails, err error) {
requestBody := []byte(body.Decrypted.(string))
request, err := http.NewRequestWithContext(ctx, method, url, bytes.NewBuffer(requestBody))
Expand All @@ -68,6 +74,11 @@ func (hc *client) SendRequest(ctx context.Context, method string, url string, bo
}
}

// Add the authorization token to the request if it doesn't already exist.
if _, exists := request.Header[authKey]; !exists && hc.authorizationToken != "" {
request.Header[authKey] = []string{hc.authorizationToken}
}

client := &http.Client{
Transport: &http.Transport{
// #nosec G402
Expand Down Expand Up @@ -113,13 +124,15 @@ 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, authorizationToken string) (Client, error) {
return &client{
log: log,
timeout: timeout,
log: log,
timeout: timeout,
authorizationToken: authorizationToken,
}, nil
}

// toJSON converts the request to a JSON string.
func toJSON(request HttpRequest) string {
jsonBytes, err := json.Marshal(request)
if err != nil {
Expand Down
16 changes: 14 additions & 2 deletions internal/controller/disposablerequest/disposablerequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const (
errConvertResToMap = "failed to convert response to map"
errGetLatestVersion = "failed to get the latest version of the resource"
errResponseFormat = "Response does not match the expected format, retries limit "
errExtractCredentials = "cannot extract credentials"
)

// Setup adds a controller that reconciles DisposableRequest managed resources.
Expand Down Expand Up @@ -94,9 +95,10 @@ 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, creds string) (httpClient.Client, error)
}

// Connect returns a new ExternalClient.
func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) {
cr, ok := mg.(*v1alpha2.DisposableRequest)
if !ok {
Expand All @@ -115,7 +117,17 @@ 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))
var creds string = ""
if pc.Spec.Credentials.Source == xpv1.CredentialsSourceSecret {
data, err := resource.CommonCredentialExtractor(ctx, pc.Spec.Credentials.Source, c.kube, pc.Spec.Credentials.CommonCredentialSelectors)
if err != nil {
return nil, errors.Wrap(err, errExtractCredentials)
}

creds = string(data)
}

h, err := c.newHttpClientFn(l, utils.WaitTimeout(cr.Spec.ForProvider.WaitTimeout), creds)
if err != nil {
return nil, errors.Wrap(err, errNewHttpClient)
}
Expand Down
21 changes: 14 additions & 7 deletions internal/controller/request/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const (
errFailedUpdateStatusConditions = "failed updating status conditions"
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"
errExtractCredentials = "cannot extract credentials"
)

// Setup adds a controller that reconciles Request managed resources.
Expand Down Expand Up @@ -91,14 +92,10 @@ 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, creds string) (httpClient.Client, error)
}

// Connect typically produces an ExternalClient by:
// 1. Tracking that the managed resource is using a ProviderConfig.
// 2. Getting the managed resource's ProviderConfig.
// 3. Getting the credentials specified by the ProviderConfig.
// 4. Using the credentials to form a client.
// Connect creates a new external client using the provider config.
func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) {
cr, ok := mg.(*v1alpha2.Request)
if !ok {
Expand All @@ -117,7 +114,17 @@ 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))
var creds string = ""
if pc.Spec.Credentials.Source == xpv1.CredentialsSourceSecret {
data, err := resource.CommonCredentialExtractor(ctx, pc.Spec.Credentials.Source, c.kube, pc.Spec.Credentials.CommonCredentialSelectors)
if err != nil {
return nil, errors.Wrap(err, errExtractCredentials)
}

creds = string(data)
}

h, err := c.newHttpClientFn(l, utils.WaitTimeout(cr.Spec.ForProvider.WaitTimeout), creds)
if err != nil {
return nil, errors.Wrap(err, errNewHttpClient)
}
Expand Down

0 comments on commit 0702bc5

Please sign in to comment.