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

[RFC-0004]: imagerepo: Add support for insecure registries #395

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions api/v1beta2/imagerepository_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ type ImageRepositorySpec struct {
// +kubebuilder:default:=generic
// +optional
Provider string `json:"provider,omitempty"`

// Insecure, if set to true indicates that the image registry is hosted at an
// HTTP endpoint.
// +optional
Insecure bool `json:"insecure,omitempty"`
}

type ScanResult struct {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,10 @@ spec:
image:
description: Image is the name of the image repository
type: string
insecure:
description: Insecure, if set to true indicates that the image registry
is hosted at an HTTP endpoint.
type: boolean
interval:
description: Interval is the length of time to wait between scans
of the image repository.
Expand Down
26 changes: 26 additions & 0 deletions docs/api/v1beta2/image-reflector.md
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,19 @@ string
When not specified, defaults to &lsquo;generic&rsquo;.</p>
</td>
</tr>
<tr>
<td>
<code>insecure</code><br>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>Insecure, if set to true indicates that the image registry is hosted at an
HTTP endpoint.</p>
</td>
</tr>
</table>
</td>
</tr>
Expand Down Expand Up @@ -731,6 +744,19 @@ string
When not specified, defaults to &lsquo;generic&rsquo;.</p>
</td>
</tr>
<tr>
<td>
<code>insecure</code><br>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>Insecure, if set to true indicates that the image registry is hosted at an
HTTP endpoint.</p>
</td>
</tr>
</tbody>
</table>
</div>
Expand Down
10 changes: 10 additions & 0 deletions docs/spec/v1beta2/imagerepositories.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,16 @@ spec:
- "1.1.1|1.0.0"
```

### Insecure

`.spec.insecure` is an optional field to specify that the image registry is
hosted at a non-TLS endpoint and thus the controller should use plain HTTP
requests to communicate with the registry.

> If an ImageRepository has `.spec.insecure` as `true` and the controller has
`--insecure-allow-http` set to `false`, then the object is marked as stalled.
For more details, see: https://github.com/fluxcd/flux2/tree/ddcc301ab6289e0640174cb9f3d46f1eeab57927/rfcs/0004-insecure-http#design-details

### Provider

`.spec.provider` is an optional field that allows specifying an OIDC provider
Expand Down
14 changes: 7 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ require (
github.com/fluxcd/pkg/apis/event v0.5.2
github.com/fluxcd/pkg/apis/meta v1.1.2
github.com/fluxcd/pkg/oci v0.31.0
github.com/fluxcd/pkg/runtime v0.42.0
github.com/fluxcd/pkg/runtime v0.42.1-0.20231114032839-2a5dc7e6a305
github.com/fluxcd/pkg/version v0.2.2
github.com/google/go-containerregistry v0.16.1
github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20230802205906-a54d64203cff
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.27.10
github.com/spf13/pflag v1.0.5
go.uber.org/zap v1.25.0
k8s.io/api v0.27.4
k8s.io/apimachinery v0.27.4
k8s.io/client-go v0.27.4
k8s.io/api v0.27.7
k8s.io/apimachinery v0.27.7
k8s.io/client-go v0.27.7
k8s.io/utils v0.0.0-20230505201702-9f6742963106
sigs.k8s.io/controller-runtime v0.15.1
sigs.k8s.io/controller-runtime v0.15.3
)

require (
Expand Down Expand Up @@ -156,9 +156,9 @@ require (
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.27.3 // indirect
k8s.io/apiextensions-apiserver v0.27.7 // indirect
k8s.io/cli-runtime v0.27.2 // indirect
k8s.io/component-base v0.27.4 // indirect
k8s.io/component-base v0.27.7 // indirect
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/kube-openapi v0.0.0-20230515203736-54b630e78af5 // indirect
k8s.io/kubectl v0.27.2 // indirect
Expand Down
28 changes: 14 additions & 14 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@ github.com/fluxcd/pkg/apis/meta v1.1.2 h1:Unjo7hxadtB2dvGpeFqZZUdsjpRA08YYSBb7dF
github.com/fluxcd/pkg/apis/meta v1.1.2/go.mod h1:BHQyRHCskGMEDf6kDGbgQ+cyiNpUHbLsCOsaMYM2maI=
github.com/fluxcd/pkg/oci v0.31.0 h1:Zpp65vcFJKRfeltuswKztJh2OrB86X3VrA1LU/VjspQ=
github.com/fluxcd/pkg/oci v0.31.0/go.mod h1:UL7nzm7p3fk5X0ZTsHl3qBhRy/NtuGqFSangXvPKUNw=
github.com/fluxcd/pkg/runtime v0.42.0 h1:a5DQ/f90YjoHBmiXZUpnp4bDSLORjInbmqP7K11L4uY=
github.com/fluxcd/pkg/runtime v0.42.0/go.mod h1:p6A3xWVV8cKLLQW0N90GehKgGMMmbNYv+OSJ/0qB0vg=
github.com/fluxcd/pkg/runtime v0.42.1-0.20231114032839-2a5dc7e6a305 h1:8zhGZCjqLFZUfbLP4fc893KnMv805M4DTi9VLTwZjgQ=
github.com/fluxcd/pkg/runtime v0.42.1-0.20231114032839-2a5dc7e6a305/go.mod h1:Acr6IqeAnjXs2so1m+5U25/JkZKhyLpRjfG844TbguA=
github.com/fluxcd/pkg/version v0.2.2 h1:ZpVXECeLA5hIQMft11iLp6gN3cKcz6UNuVTQPw/bRdI=
github.com/fluxcd/pkg/version v0.2.2/go.mod h1:NGnh/no8S6PyfCDxRFrPY3T5BUnqP48MxfxNRU0z8C0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
Expand Down Expand Up @@ -617,18 +617,18 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.27.4 h1:0pCo/AN9hONazBKlNUdhQymmnfLRbSZjd5H5H3f0bSs=
k8s.io/api v0.27.4/go.mod h1:O3smaaX15NfxjzILfiln1D8Z3+gEYpjEpiNA/1EVK1Y=
k8s.io/apiextensions-apiserver v0.27.3 h1:xAwC1iYabi+TDfpRhxh4Eapl14Hs2OftM2DN5MpgKX4=
k8s.io/apiextensions-apiserver v0.27.3/go.mod h1:BH3wJ5NsB9XE1w+R6SSVpKmYNyIiyIz9xAmBl8Mb+84=
k8s.io/apimachinery v0.27.4 h1:CdxflD4AF61yewuid0fLl6bM4a3q04jWel0IlP+aYjs=
k8s.io/apimachinery v0.27.4/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E=
k8s.io/api v0.27.7 h1:7yG4D3t/q4utJe2ptlRw9aPuxcSmroTsYxsofkQNl/A=
k8s.io/api v0.27.7/go.mod h1:ZNExI/Lhrs9YrLgVWx6jjHZdoWCTXfBXuFjt1X6olro=
k8s.io/apiextensions-apiserver v0.27.7 h1:YqIOwZAUokzxJIjunmUd4zS1v3JhK34EPXn+pP0/bsU=
k8s.io/apiextensions-apiserver v0.27.7/go.mod h1:x0p+b5a955lfPz9gaDeBy43obM12s+N9dNHK6+dUL+g=
k8s.io/apimachinery v0.27.7 h1:Gxgtb7Y/Rsu8ymgmUEaiErkxa6RY4oTd8kNUI6SUR58=
k8s.io/apimachinery v0.27.7/go.mod h1:jBGQgTjkw99ef6q5hv1YurDd3BqKDk9YRxmX0Ozo0i8=
k8s.io/cli-runtime v0.27.2 h1:9HI8gfReNujKXt16tGOAnb8b4NZ5E+e0mQQHKhFGwYw=
k8s.io/cli-runtime v0.27.2/go.mod h1:9UecpyPDTkhiYY4d9htzRqN+rKomJgyb4wi0OfrmCjw=
k8s.io/client-go v0.27.4 h1:vj2YTtSJ6J4KxaC88P4pMPEQECWMY8gqPqsTgUKzvjk=
k8s.io/client-go v0.27.4/go.mod h1:ragcly7lUlN0SRPk5/ZkGnDjPknzb37TICq07WhI6Xc=
k8s.io/component-base v0.27.4 h1:Wqc0jMKEDGjKXdae8hBXeskRP//vu1m6ypC+gwErj4c=
k8s.io/component-base v0.27.4/go.mod h1:hoiEETnLc0ioLv6WPeDt8vD34DDeB35MfQnxCARq3kY=
k8s.io/client-go v0.27.7 h1:+Xgh9OOKv6A3qdD4Dnl/0VOI5EvAv+0s/OseDxVVTwQ=
k8s.io/client-go v0.27.7/go.mod h1:dZ2kqcalYp5YZ2EV12XIMc77G6PxHWOJp/kclZr4+5Q=
k8s.io/component-base v0.27.7 h1:kngM58HR9W9Nqpv7e4rpdRyWnKl/ABpUhLAZ+HoliMs=
k8s.io/component-base v0.27.7/go.mod h1:YGjlCVL1oeKvG3HSciyPHFh+LCjIEqsxz4BDR3cfHRs=
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20230515203736-54b630e78af5 h1:azYPdzztXxPSa8wb+hksEKayiz0o+PPisO/d+QhWnoo=
Expand All @@ -639,8 +639,8 @@ k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/
k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/cli-utils v0.35.0 h1:dfSJaF1W0frW74PtjwiyoB4cwdRygbHnC7qe7HF0g/Y=
sigs.k8s.io/cli-utils v0.35.0/go.mod h1:ITitykCJxP1vaj1Cew/FZEaVJ2YsTN9Q71m02jebkoE=
sigs.k8s.io/controller-runtime v0.15.1 h1:9UvgKD4ZJGcj24vefUFgZFP3xej/3igL9BsOUTb/+4c=
sigs.k8s.io/controller-runtime v0.15.1/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk=
sigs.k8s.io/controller-runtime v0.15.3 h1:L+t5heIaI3zeejoIyyvLQs5vTVu/67IU2FfisVzFlBc=
sigs.k8s.io/controller-runtime v0.15.3/go.mod h1:kp4jckA4vTx281S/0Yk2LFEEQe67mjg+ev/yknv47Ds=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/kustomize/api v0.13.4 h1:E38Hfx0G9R9v7vRgKshviPotJQETG0S2gD3JdHLCAsI=
Expand Down
40 changes: 32 additions & 8 deletions internal/controller/imagerepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ type ImageRepositoryReconciler struct {
DatabaseReader
}
DeprecatedLoginOpts login.ProviderOptions
AllowInsecureHTTP bool

patchOptions []patch.Option
}
Expand Down Expand Up @@ -249,9 +250,15 @@ func (r *ImageRepositoryReconciler) reconcile(ctx context.Context, sp *patch.Ser
}

// Parse image reference.
ref, err := parseImageReference(obj.Spec.Image)
ref, err := r.parseImageReference(obj.Spec.Image, obj.Spec.Insecure)
if err != nil {
conditions.MarkStalled(obj, imagev1.ImageURLInvalidReason, err.Error())
var reason string
if errors.Is(err, helper.ErrInsecureHTTPBlocked) {
reason = meta.InsecureConnectionsDisallowedReason
} else {
reason = imagev1.ImageURLInvalidReason
}
conditions.MarkStalled(obj, reason, err.Error())
result, retErr = ctrl.Result{}, nil
return
}
Expand All @@ -268,11 +275,18 @@ func (r *ImageRepositoryReconciler) reconcile(ctx context.Context, sp *patch.Ser
// Check if it can be scanned now.
ok, when, reasonMsg, err := r.shouldScan(*obj, startTime)
if err != nil {
e := fmt.Errorf("failed to determine if it's scan time: %w", err)
conditions.MarkFalse(obj, meta.ReadyCondition, metav1.StatusFailure, e.Error())
var e error
if errors.Is(err, helper.ErrInsecureHTTPBlocked) {
e = err
conditions.MarkStalled(obj, meta.InsecureConnectionsDisallowedReason, e.Error())
} else {
e = fmt.Errorf("failed to determine if it's scan time: %w", err)
conditions.MarkFalse(obj, meta.ReadyCondition, metav1.StatusFailure, e.Error())
}
result, retErr = ctrl.Result{}, e
return
}
conditions.Delete(obj, meta.StalledCondition)

// Scan the repository if it's scan time. No scan is a no-op reconciliation.
// The next scan time is not reset in case of no-op reconciliation.
Expand Down Expand Up @@ -468,7 +482,7 @@ func (r *ImageRepositoryReconciler) shouldScan(obj imagev1.ImageRepository, now

// If the canonical image name of the image is different from the last
// observed name, scan now.
ref, err := parseImageReference(obj.Spec.Image)
ref, err := r.parseImageReference(obj.Spec.Image, obj.Spec.Insecure)
if err != nil {
return false, scanInterval, "", err
}
Expand Down Expand Up @@ -570,13 +584,23 @@ func eventLogf(ctx context.Context, r kuberecorder.EventRecorder, obj runtime.Ob
}

// parseImageReference parses the given URL into a container registry repository
// reference.
func parseImageReference(url string) (name.Reference, error) {
// reference. If insecure is set to true, then the registry is deemed to be
// located at an HTTP endpoint.
func (r *ImageRepositoryReconciler) parseImageReference(url string, insecure bool) (name.Reference, error) {
if s := strings.Split(url, "://"); len(s) > 1 {
return nil, fmt.Errorf(".spec.image value should not start with URL scheme; remove '%s://'", s[0])
}

ref, err := name.ParseReference(url)
var opts []name.Option
if insecure {
if r.AllowInsecureHTTP {
opts = append(opts, name.Insecure)
} else {
return nil, helper.ErrInsecureHTTPBlocked
}
}

ref, err := name.ParseReference(url, opts...)
if err != nil {
return nil, err
}
Expand Down
41 changes: 34 additions & 7 deletions internal/controller/imagerepository_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (

"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
"github.com/fluxcd/pkg/runtime/controller"

imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
"github.com/fluxcd/image-reflector-controller/internal/secret"
Expand Down Expand Up @@ -580,7 +581,7 @@ func TestImageRepositoryReconciler_scan(t *testing.T) {
repo.SetAnnotations(map[string]string{meta.ReconcileRequestAnnotation: tt.annotation})
}

ref, err := parseImageReference(imgRepo)
ref, err := r.parseImageReference(imgRepo, false)
g.Expect(err).ToNot(HaveOccurred())

opts := []remote.Option{}
Expand Down Expand Up @@ -656,12 +657,15 @@ func TestGetLatestTags(t *testing.T) {
}
}

func TestParseImageReference(t *testing.T) {
func Test_parseImageReference(t *testing.T) {
tests := []struct {
name string
url string
wantErr bool
wantRef string
name string
url string
insecure bool
allowInsecure bool
wantErr bool
err error
wantRef string
}{
{
name: "simple valid url",
Expand All @@ -684,16 +688,39 @@ func TestParseImageReference(t *testing.T) {
wantErr: false,
wantRef: "example.com:9999/foo/bar",
},
{
name: "with insecure allowed",
url: "example.com/foo/bar",
insecure: true,
allowInsecure: true,
wantRef: "example.com/foo/bar",
},
{
name: "with insecure disallowed",
url: "example.com/foo/bar",
insecure: true,
wantErr: true,
err: controller.ErrInsecureHTTPBlocked,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)

ref, err := parseImageReference(tt.url)
r := &ImageRepositoryReconciler{
AllowInsecureHTTP: tt.allowInsecure,
}
ref, err := r.parseImageReference(tt.url, tt.insecure)
g.Expect(err != nil).To(Equal(tt.wantErr))
if tt.err != nil {
g.Expect(tt.err).To(Equal(err))
}
if err == nil {
g.Expect(ref.String()).To(Equal(tt.wantRef))
if tt.insecure {
g.Expect(ref.Context().Registry.Scheme()).To(Equal("http"))
}
}
})
}
Expand Down
9 changes: 9 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ func main() {
logOptions logger.Options
leaderElectionOptions leaderelection.Options
watchOptions helper.WatchOptions
connOptions helper.ConnectionOptions
storagePath string
storageValueLogFileSize int64
concurrent int
Expand Down Expand Up @@ -107,11 +108,18 @@ func main() {
rateLimiterOptions.BindFlags(flag.CommandLine)
featureGates.BindFlags(flag.CommandLine)
watchOptions.BindFlags(flag.CommandLine)
connOptions.BindFlags(flag.CommandLine)

flag.Parse()

logger.SetLogger(logger.NewLogger(logOptions))

if err := connOptions.CheckEnvironmentCompatibility(); err != nil {
setupLog.Error(err,
"please verify that your controller flag settings are compatible with the controller's environment")
os.Exit(1)
}

if awsAutoLogin || gcpAutoLogin || azureAutoLogin {
setupLog.Error(errors.New("use of deprecated flags"),
"autologin flags have been deprecated. These flags will be removed in a future release."+
Expand Down Expand Up @@ -216,6 +224,7 @@ func main() {
AzureAutoLogin: azureAutoLogin,
GcpAutoLogin: gcpAutoLogin,
},
AllowInsecureHTTP: connOptions.AllowHTTP,
}).SetupWithManager(mgr, controller.ImageRepositoryReconcilerOptions{
RateLimiter: helper.GetRateLimiter(rateLimiterOptions),
}); err != nil {
Expand Down