Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions apis/cluster/v1alpha1/providerconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ type ProviderConfigSpec struct {
// Credentials required to authenticate to this provider.
Credentials ProviderCredentials `json:"credentials"`

// Identity used to authenticate outgoing requests. The identity
// credentials supplement 'credentials' by configuring a bearer token
// source such as OAuth. A request that carries its own Authorization
// header keeps that value.
// +optional
Identity *common.Identity `json:"identity,omitempty"`

// TLS configuration for HTTPS requests.
// +optional
TLS *common.TLSConfig `json:"tls,omitempty"`
Expand Down
5 changes: 5 additions & 0 deletions apis/cluster/v1alpha1/zz_generated.deepcopy.go

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

54 changes: 54 additions & 0 deletions apis/common/identity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
Copyright 2023 The Crossplane Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package common

import (
xpv2 "github.com/crossplane/crossplane/apis/v2/core/v2"
)

// IdentityType used to obtain a bearer token for outgoing requests.
// +kubebuilder:validation:Enum=GoogleApplicationCredentials
type IdentityType string

// Supported identity types.
const (
// IdentityTypeGoogleApplicationCredentials authenticates using Google
// Application Credentials, exchanging them for an OAuth2 access token.
IdentityTypeGoogleApplicationCredentials = "GoogleApplicationCredentials"
)

// IdentityCredentials required to obtain a token.
type IdentityCredentials struct {
// Source of the identity credentials. Use InjectedIdentity to resolve
// credentials from the provider pod's environment, for example through
// Workload Identity on GKE.
// +kubebuilder:validation:Enum=Secret;InjectedIdentity;Environment;Filesystem
Source xpv2.CredentialsSource `json:"source"`

xpv2.CommonCredentialSelectors `json:",inline"`
}

// Identity used to authenticate outgoing requests.
type Identity struct {
// Type of identity.
Type IdentityType `json:"type"`

IdentityCredentials `json:",inline"`

// Scopes requested for the access token. Defaults to
// https://www.googleapis.com/auth/cloud-platform for
// GoogleApplicationCredentials.
// +optional
Scopes []string `json:"scopes,omitempty"`
}
37 changes: 37 additions & 0 deletions apis/common/zz_generated.deepcopy.go

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

7 changes: 7 additions & 0 deletions apis/namespaced/v1alpha2/providerconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ type ProviderConfigSpec struct {
// Credentials required to authenticate to this provider.
Credentials ProviderCredentials `json:"credentials"`

// Identity used to authenticate outgoing requests. The identity
// credentials supplement 'credentials' by configuring a bearer token
// source such as OAuth. A request that carries its own Authorization
// header keeps that value.
// +optional
Identity *common.Identity `json:"identity,omitempty"`

// TLS configuration for HTTPS requests.
// +optional
TLS *common.TLSConfig `json:"tls,omitempty"`
Expand Down
5 changes: 5 additions & 0 deletions apis/namespaced/v1alpha2/zz_generated.deepcopy.go

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

68 changes: 68 additions & 0 deletions examples/provider/identity-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
# ProviderConfig that authenticates outgoing requests with Google Application
# Credentials held in a Secret. The provider exchanges the service account key
# for an OAuth2 access token and sends it as a bearer token.
apiVersion: http.crossplane.io/v1alpha1
kind: ProviderConfig
metadata:
name: google-identity-from-secret
spec:
credentials:
source: None
identity:
type: GoogleApplicationCredentials
source: Secret
secretRef:
name: gcp-credentials
namespace: crossplane-system
key: credentials.json
---
# ProviderConfig that resolves Google credentials from the provider pod's
# environment. On GKE this means Workload Identity; no key material is stored
# in the cluster.
apiVersion: http.crossplane.io/v1alpha1
kind: ProviderConfig
metadata:
name: google-identity-injected
spec:
credentials:
source: None
identity:
type: GoogleApplicationCredentials
source: InjectedIdentity
---
# ProviderConfig requesting a narrower scope than the default
# https://www.googleapis.com/auth/cloud-platform.
apiVersion: http.crossplane.io/v1alpha1
kind: ProviderConfig
metadata:
name: google-identity-scoped
spec:
credentials:
source: None
identity:
type: GoogleApplicationCredentials
source: Secret
secretRef:
name: gcp-credentials
namespace: crossplane-system
key: credentials.json
scopes:
- https://www.googleapis.com/auth/compute
---
# Namespaced ProviderConfig with the same identity configuration.
apiVersion: http.m.crossplane.io/v1alpha2
kind: ProviderConfig
metadata:
name: google-identity-from-secret
namespace: default
spec:
credentials:
source: None
identity:
type: GoogleApplicationCredentials
source: Secret
secretRef:
name: gcp-credentials
namespace: default
key: credentials.json
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
)

require (
cloud.google.com/go/compute/metadata v0.9.0 // indirect
github.com/go-openapi/swag/cmdutils v0.25.5 // indirect
github.com/go-openapi/swag/conv v0.25.5 // indirect
github.com/go-openapi/swag/fileutils v0.25.5 // indirect
Expand Down Expand Up @@ -81,7 +82,7 @@ require (
golang.org/x/exp v0.0.0-20260112195511-716be5621a96
golang.org/x/mod v0.35.0 // indirect
golang.org/x/net v0.55.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/oauth2 v0.36.0
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.45.0 // indirect
golang.org/x/term v0.43.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
Expand Down
43 changes: 35 additions & 8 deletions internal/clients/http/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"github.com/crossplane-contrib/provider-http/apis/interfaces"
"github.com/crossplane/crossplane-runtime/v2/pkg/logging"
"golang.org/x/oauth2"
)

const (
Expand Down Expand Up @@ -40,6 +41,18 @@ type client struct {
log logging.Logger
timeout time.Duration
authorizationToken string
tokenSource oauth2.TokenSource
}

// Option configures a Client.
type Option func(*client)

// WithTokenSource sets the OAuth2 token source used to authenticate requests
// that do not carry their own Authorization header.
func WithTokenSource(ts oauth2.TokenSource) Option {
return func(c *client) {
c.tokenSource = ts
}
}

type HttpResponse struct {
Expand Down Expand Up @@ -128,12 +141,20 @@ func (hc *client) SendRequest(ctx context.Context, method string, url string, bo
}, fmt.Errorf("failed to build TLS config: %w", err)
}

var transport http.RoundTripper = &http.Transport{
TLSClientConfig: tlsConfig,
Proxy: http.ProxyFromEnvironment, // Use proxy settings from environment
}

// Authenticate through the token source unless the request already carries
// its own Authorization header.
if _, exists := request.Header[authKey]; !exists && hc.tokenSource != nil {
transport = &oauth2.Transport{Source: hc.tokenSource, Base: transport}
}

client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
Proxy: http.ProxyFromEnvironment, // Use proxy settings from environment
},
Timeout: hc.timeout,
Transport: transport,
Timeout: hc.timeout,
}

response, err := client.Do(request)
Expand Down Expand Up @@ -172,12 +193,18 @@ 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, authorizationToken string) (Client, error) {
return &client{
func NewClient(log logging.Logger, timeout time.Duration, authorizationToken string, opts ...Option) (Client, error) {
c := &client{
log: log,
timeout: timeout,
authorizationToken: authorizationToken,
}, nil
}

for _, opt := range opts {
opt(c)
}

return c, nil
}

// toJSON converts the request to a JSON string.
Expand Down
Loading