Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add proxy support for OCIRepository API #1536

Merged
merged 1 commit into from
Aug 15, 2024
Merged
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
5 changes: 5 additions & 0 deletions api/v1beta2/ocirepository_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ type OCIRepositorySpec struct {
// +optional
CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`

// ProxySecretRef specifies the Secret containing the proxy configuration
// to use while communicating with the container registry.
// +optional
ProxySecretRef *meta.LocalObjectReference `json:"proxySecretRef,omitempty"`

// Interval at which the OCIRepository URL is checked for updates.
// This interval is approximate and may be subject to jitter to ensure
// efficient use of resources.
Expand Down
5 changes: 5 additions & 0 deletions api/v1beta2/zz_generated.deepcopy.go

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

11 changes: 11 additions & 0 deletions config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,17 @@ spec:
- azure
- gcp
type: string
proxySecretRef:
description: |-
ProxySecretRef specifies the Secret containing the proxy configuration
to use while communicating with the container registry.
properties:
name:
description: Name of the referent.
type: string
required:
- name
type: object
ref:
description: |-
The OCI reference to pull and monitor for changes,
Expand Down
30 changes: 30 additions & 0 deletions docs/api/v1beta2/source.md
Original file line number Diff line number Diff line change
Expand Up @@ -1235,6 +1235,21 @@ been deprecated.</p>
</tr>
<tr>
<td>
<code>proxySecretRef</code><br>
<em>
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>ProxySecretRef specifies the Secret containing the proxy configuration
to use while communicating with the container registry.</p>
</td>
</tr>
<tr>
<td>
<code>interval</code><br>
<em>
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
Expand Down Expand Up @@ -3313,6 +3328,21 @@ been deprecated.</p>
</tr>
<tr>
<td>
<code>proxySecretRef</code><br>
<em>
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>ProxySecretRef specifies the Secret containing the proxy configuration
to use while communicating with the container registry.</p>
</td>
</tr>
<tr>
<td>
<code>interval</code><br>
<em>
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
Expand Down
41 changes: 41 additions & 0 deletions docs/spec/v1beta2/ocirepositories.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,47 @@ data:
deprecated. If you have any Secrets using these keys and specified in an
OCIRepository, the controller will log a deprecation warning.

### Proxy secret reference

`.spec.proxySecretRef.name` is an optional field used to specify the name of a
Secret that contains the proxy settings for the object. These settings are used
for all the remote operations related to the OCIRepository.
The Secret can contain three keys:

- `address`, to specify the address of the proxy server. This is a required key.
- `username`, to specify the username to use if the proxy server is protected by
basic authentication. This is an optional key.
- `password`, to specify the password to use if the proxy server is protected by
basic authentication. This is an optional key.

Example:

```yaml
---
apiVersion: v1
kind: Secret
metadata:
name: http-proxy
type: Opaque
stringData:
address: http://proxy.com
username: mandalorian
password: grogu
```

Proxying can also be configured in the source-controller Deployment directly by
using the standard environment variables such as `HTTPS_PROXY`, `ALL_PROXY`, etc.

`.spec.proxySecretRef.name` takes precedence over all environment variables.
matheuscscp marked this conversation as resolved.
Show resolved Hide resolved

**Warning:** [Cosign](https://github.com/sigstore/cosign) *keyless*
[verification](#verification) is not supported for this API. If you
require cosign keyless verification to use a proxy you must use the
standard environment variables mentioned above. If you specify a
`proxySecretRef` the controller will simply send out the requests
needed for keyless verification without the associated object-level
proxy settings.

### Insecure

`.spec.insecure` is an optional field to allow connecting to an insecure (HTTP)
Expand Down
76 changes: 69 additions & 7 deletions internal/controller/ocirepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"regexp"
Expand Down Expand Up @@ -437,7 +438,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
conditions.GetObservedGeneration(obj, sourcev1.SourceVerifiedCondition) != obj.Generation ||
conditions.IsFalse(obj, sourcev1.SourceVerifiedCondition) {

result, err := r.verifySignature(ctx, obj, ref, keychain, auth, opts...)
result, err := r.verifySignature(ctx, obj, ref, keychain, auth, transport, opts...)
if err != nil {
provider := obj.Spec.Verify.Provider
if obj.Spec.Verify.SecretRef == nil && obj.Spec.Verify.Provider == "cosign" {
Expand Down Expand Up @@ -623,7 +624,10 @@ func (r *OCIRepositoryReconciler) digestFromRevision(revision string) string {
// If not, when using cosign it falls back to a keyless approach for verification.
// When notation is used, a trust policy is required to verify the image.
// The verification result is returned as a VerificationResult and any error encountered.
func (r *OCIRepositoryReconciler) verifySignature(ctx context.Context, obj *ociv1.OCIRepository, ref name.Reference, keychain authn.Keychain, auth authn.Authenticator, opt ...remote.Option) (soci.VerificationResult, error) {
func (r *OCIRepositoryReconciler) verifySignature(ctx context.Context, obj *ociv1.OCIRepository,
ref name.Reference, keychain authn.Keychain, auth authn.Authenticator,
transport *http.Transport, opt ...remote.Option) (soci.VerificationResult, error) {

ctxTimeout, cancel := context.WithTimeout(ctx, obj.Spec.Timeout.Duration)
defer cancel()

Expand Down Expand Up @@ -753,6 +757,7 @@ func (r *OCIRepositoryReconciler) verifySignature(ctx context.Context, obj *ociv
notation.WithInsecureRegistry(obj.Spec.Insecure),
notation.WithLogger(ctrl.LoggerFrom(ctx)),
notation.WithRootCertificates(certs),
notation.WithTransport(transport),
}

verifier, err := notation.NewNotationVerifier(defaultNotationOciOpts...)
Expand Down Expand Up @@ -920,16 +925,40 @@ func (r *OCIRepositoryReconciler) keychain(ctx context.Context, obj *ociv1.OCIRe

// transport clones the default transport from remote and when a certSecretRef is specified,
// the returned transport will include the TLS client and/or CA certificates.
// If the insecure flag is set, the transport will skip the verification of the server's certificate.
// Additionally, if a proxy is specified, transport will use it.
matheuscscp marked this conversation as resolved.
Show resolved Hide resolved
func (r *OCIRepositoryReconciler) transport(ctx context.Context, obj *ociv1.OCIRepository) (*http.Transport, error) {
transport := remote.DefaultTransport.(*http.Transport).Clone()

tlsConfig, err := r.getTLSConfig(ctx, obj)
if err != nil {
return nil, err
}
if tlsConfig != nil {
transport.TLSClientConfig = tlsConfig
}

proxyURL, err := r.getProxyURL(ctx, obj)
if err != nil {
return nil, err
}
if proxyURL != nil {
transport.Proxy = http.ProxyURL(proxyURL)
}

return transport, nil
}

// getTLSConfig gets the TLS configuration for the transport based on the
// specified secret reference in the OCIRepository object, or the insecure flag.
func (r *OCIRepositoryReconciler) getTLSConfig(ctx context.Context, obj *ociv1.OCIRepository) (*cryptotls.Config, error) {
if obj.Spec.CertSecretRef == nil || obj.Spec.CertSecretRef.Name == "" {
if obj.Spec.Insecure {
transport.TLSClientConfig = &cryptotls.Config{
return &cryptotls.Config{
InsecureSkipVerify: true,
}
}, nil
}
return transport, nil
return nil, nil
}

certSecretName := types.NamespacedName{
Expand All @@ -955,9 +984,42 @@ func (r *OCIRepositoryReconciler) transport(ctx context.Context, obj *ociv1.OCIR
Info("warning: specifying TLS auth data via `certFile`/`keyFile`/`caFile` is deprecated, please use `tls.crt`/`tls.key`/`ca.crt` instead")
}
}
transport.TLSClientConfig = tlsConfig

return transport, nil
return tlsConfig, nil
}

// getProxyURL gets the proxy configuration for the transport based on the
// specified proxy secret reference in the OCIRepository object.
func (r *OCIRepositoryReconciler) getProxyURL(ctx context.Context, obj *ociv1.OCIRepository) (*url.URL, error) {
matheuscscp marked this conversation as resolved.
Show resolved Hide resolved
if obj.Spec.ProxySecretRef == nil || obj.Spec.ProxySecretRef.Name == "" {
return nil, nil
}

proxySecretName := types.NamespacedName{
Namespace: obj.Namespace,
Name: obj.Spec.ProxySecretRef.Name,
}
var proxySecret corev1.Secret
if err := r.Get(ctx, proxySecretName, &proxySecret); err != nil {
return nil, err
}

proxyData := proxySecret.Data
address, ok := proxyData["address"]
if !ok {
return nil, fmt.Errorf("invalid proxy secret '%s/%s': key 'address' is missing",
obj.Namespace, obj.Spec.ProxySecretRef.Name)
}
proxyURL, err := url.Parse(string(address))
if err != nil {
return nil, fmt.Errorf("failed to parse proxy address '%s': %w", address, err)
}
user, hasUser := proxyData["username"]
password, hasPassword := proxyData["password"]
if hasUser || hasPassword {
proxyURL.User = url.UserPassword(string(user), string(password))
}
return proxyURL, nil
}

// reconcileStorage ensures the current state of the storage matches the
Expand Down
Loading
Loading