Skip to content

Commit 801adfb

Browse files
committed
[RFC-0004] imagerepo: add support for insecure registries
Add a new field `.spec.insecure` to the `ImageRepository` API to allow indicating that the registry is an insecure registry, i.e. hosted at an HTTP endpoint. Furthermore, add a new flag `--insecure-allow-http` to allow the controller to make HTTP requests. By default, it is set to true to ensure backwards compatibility. Implements [RFC-0004](https://github.com/fluxcd/flux2/tree/main/rfcs/0004-insecure-http). Signed-off-by: Sanskar Jaiswal <[email protected]>
1 parent 7725a45 commit 801adfb

File tree

9 files changed

+141
-36
lines changed

9 files changed

+141
-36
lines changed

api/v1beta2/imagerepository_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ type ImageRepositorySpec struct {
104104
// +kubebuilder:default:=generic
105105
// +optional
106106
Provider string `json:"provider,omitempty"`
107+
108+
// Insecure, if set to true indicates that the image registry is hosted at an
109+
// HTTP endpoint.
110+
// +optional
111+
Insecure bool `json:"insecure,omitempty"`
107112
}
108113

109114
type ScanResult struct {

config/crd/bases/image.toolkit.fluxcd.io_imagerepositories.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,10 @@ spec:
315315
image:
316316
description: Image is the name of the image repository
317317
type: string
318+
insecure:
319+
description: Insecure, if set to true indicates that the image registry
320+
is hosted at an HTTP endpoint.
321+
type: boolean
318322
interval:
319323
description: Interval is the length of time to wait between scans
320324
of the image repository.

docs/api/v1beta2/image-reflector.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,19 @@ string
543543
When not specified, defaults to &lsquo;generic&rsquo;.</p>
544544
</td>
545545
</tr>
546+
<tr>
547+
<td>
548+
<code>insecure</code><br>
549+
<em>
550+
bool
551+
</em>
552+
</td>
553+
<td>
554+
<em>(Optional)</em>
555+
<p>Insecure, if set to true indicates that the image registry is hosted at an
556+
HTTP endpoint.</p>
557+
</td>
558+
</tr>
546559
</table>
547560
</td>
548561
</tr>
@@ -731,6 +744,19 @@ string
731744
When not specified, defaults to &lsquo;generic&rsquo;.</p>
732745
</td>
733746
</tr>
747+
<tr>
748+
<td>
749+
<code>insecure</code><br>
750+
<em>
751+
bool
752+
</em>
753+
</td>
754+
<td>
755+
<em>(Optional)</em>
756+
<p>Insecure, if set to true indicates that the image registry is hosted at an
757+
HTTP endpoint.</p>
758+
</td>
759+
</tr>
734760
</tbody>
735761
</table>
736762
</div>

docs/spec/v1beta2/imagerepositories.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,16 @@ spec:
318318
- "1.1.1|1.0.0"
319319
```
320320

321+
### Insecure
322+
323+
`.spec.insecure` is an optional field to specify that the image registry is
324+
hosted at a non-TLS endpoint and thus the controller should use plain HTTP
325+
requests to communicate with the registry.
326+
327+
> If an ImageRepository has `.spec.insecure` as `true` and the controller has
328+
`--insecure-allow-http` set to `false`, then the object is marked as stalled.
329+
For more details, see: https://github.com/fluxcd/flux2/tree/ddcc301ab6289e0640174cb9f3d46f1eeab57927/rfcs/0004-insecure-http#design-details
330+
321331
### Provider
322332

323333
`.spec.provider` is an optional field that allows specifying an OIDC provider

go.mod

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,19 @@ require (
1313
github.com/fluxcd/pkg/apis/event v0.5.2
1414
github.com/fluxcd/pkg/apis/meta v1.1.2
1515
github.com/fluxcd/pkg/oci v0.31.0
16-
github.com/fluxcd/pkg/runtime v0.42.0
16+
github.com/fluxcd/pkg/runtime v0.42.1-0.20231114032839-2a5dc7e6a305
1717
github.com/fluxcd/pkg/version v0.2.2
1818
github.com/google/go-containerregistry v0.16.1
1919
github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20230802205906-a54d64203cff
2020
github.com/onsi/ginkgo v1.16.5
2121
github.com/onsi/gomega v1.27.10
2222
github.com/spf13/pflag v1.0.5
2323
go.uber.org/zap v1.25.0
24-
k8s.io/api v0.27.4
25-
k8s.io/apimachinery v0.27.4
26-
k8s.io/client-go v0.27.4
24+
k8s.io/api v0.27.7
25+
k8s.io/apimachinery v0.27.7
26+
k8s.io/client-go v0.27.7
2727
k8s.io/utils v0.0.0-20230505201702-9f6742963106
28-
sigs.k8s.io/controller-runtime v0.15.1
28+
sigs.k8s.io/controller-runtime v0.15.3
2929
)
3030

3131
require (
@@ -156,9 +156,9 @@ require (
156156
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
157157
gopkg.in/yaml.v2 v2.4.0 // indirect
158158
gopkg.in/yaml.v3 v3.0.1 // indirect
159-
k8s.io/apiextensions-apiserver v0.27.3 // indirect
159+
k8s.io/apiextensions-apiserver v0.27.7 // indirect
160160
k8s.io/cli-runtime v0.27.2 // indirect
161-
k8s.io/component-base v0.27.4 // indirect
161+
k8s.io/component-base v0.27.7 // indirect
162162
k8s.io/klog/v2 v2.100.1 // indirect
163163
k8s.io/kube-openapi v0.0.0-20230515203736-54b630e78af5 // indirect
164164
k8s.io/kubectl v0.27.2 // indirect

go.sum

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,8 @@ github.com/fluxcd/pkg/apis/meta v1.1.2 h1:Unjo7hxadtB2dvGpeFqZZUdsjpRA08YYSBb7dF
176176
github.com/fluxcd/pkg/apis/meta v1.1.2/go.mod h1:BHQyRHCskGMEDf6kDGbgQ+cyiNpUHbLsCOsaMYM2maI=
177177
github.com/fluxcd/pkg/oci v0.31.0 h1:Zpp65vcFJKRfeltuswKztJh2OrB86X3VrA1LU/VjspQ=
178178
github.com/fluxcd/pkg/oci v0.31.0/go.mod h1:UL7nzm7p3fk5X0ZTsHl3qBhRy/NtuGqFSangXvPKUNw=
179-
github.com/fluxcd/pkg/runtime v0.42.0 h1:a5DQ/f90YjoHBmiXZUpnp4bDSLORjInbmqP7K11L4uY=
180-
github.com/fluxcd/pkg/runtime v0.42.0/go.mod h1:p6A3xWVV8cKLLQW0N90GehKgGMMmbNYv+OSJ/0qB0vg=
179+
github.com/fluxcd/pkg/runtime v0.42.1-0.20231114032839-2a5dc7e6a305 h1:8zhGZCjqLFZUfbLP4fc893KnMv805M4DTi9VLTwZjgQ=
180+
github.com/fluxcd/pkg/runtime v0.42.1-0.20231114032839-2a5dc7e6a305/go.mod h1:Acr6IqeAnjXs2so1m+5U25/JkZKhyLpRjfG844TbguA=
181181
github.com/fluxcd/pkg/version v0.2.2 h1:ZpVXECeLA5hIQMft11iLp6gN3cKcz6UNuVTQPw/bRdI=
182182
github.com/fluxcd/pkg/version v0.2.2/go.mod h1:NGnh/no8S6PyfCDxRFrPY3T5BUnqP48MxfxNRU0z8C0=
183183
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -617,18 +617,18 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
617617
gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk=
618618
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
619619
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
620-
k8s.io/api v0.27.4 h1:0pCo/AN9hONazBKlNUdhQymmnfLRbSZjd5H5H3f0bSs=
621-
k8s.io/api v0.27.4/go.mod h1:O3smaaX15NfxjzILfiln1D8Z3+gEYpjEpiNA/1EVK1Y=
622-
k8s.io/apiextensions-apiserver v0.27.3 h1:xAwC1iYabi+TDfpRhxh4Eapl14Hs2OftM2DN5MpgKX4=
623-
k8s.io/apiextensions-apiserver v0.27.3/go.mod h1:BH3wJ5NsB9XE1w+R6SSVpKmYNyIiyIz9xAmBl8Mb+84=
624-
k8s.io/apimachinery v0.27.4 h1:CdxflD4AF61yewuid0fLl6bM4a3q04jWel0IlP+aYjs=
625-
k8s.io/apimachinery v0.27.4/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E=
620+
k8s.io/api v0.27.7 h1:7yG4D3t/q4utJe2ptlRw9aPuxcSmroTsYxsofkQNl/A=
621+
k8s.io/api v0.27.7/go.mod h1:ZNExI/Lhrs9YrLgVWx6jjHZdoWCTXfBXuFjt1X6olro=
622+
k8s.io/apiextensions-apiserver v0.27.7 h1:YqIOwZAUokzxJIjunmUd4zS1v3JhK34EPXn+pP0/bsU=
623+
k8s.io/apiextensions-apiserver v0.27.7/go.mod h1:x0p+b5a955lfPz9gaDeBy43obM12s+N9dNHK6+dUL+g=
624+
k8s.io/apimachinery v0.27.7 h1:Gxgtb7Y/Rsu8ymgmUEaiErkxa6RY4oTd8kNUI6SUR58=
625+
k8s.io/apimachinery v0.27.7/go.mod h1:jBGQgTjkw99ef6q5hv1YurDd3BqKDk9YRxmX0Ozo0i8=
626626
k8s.io/cli-runtime v0.27.2 h1:9HI8gfReNujKXt16tGOAnb8b4NZ5E+e0mQQHKhFGwYw=
627627
k8s.io/cli-runtime v0.27.2/go.mod h1:9UecpyPDTkhiYY4d9htzRqN+rKomJgyb4wi0OfrmCjw=
628-
k8s.io/client-go v0.27.4 h1:vj2YTtSJ6J4KxaC88P4pMPEQECWMY8gqPqsTgUKzvjk=
629-
k8s.io/client-go v0.27.4/go.mod h1:ragcly7lUlN0SRPk5/ZkGnDjPknzb37TICq07WhI6Xc=
630-
k8s.io/component-base v0.27.4 h1:Wqc0jMKEDGjKXdae8hBXeskRP//vu1m6ypC+gwErj4c=
631-
k8s.io/component-base v0.27.4/go.mod h1:hoiEETnLc0ioLv6WPeDt8vD34DDeB35MfQnxCARq3kY=
628+
k8s.io/client-go v0.27.7 h1:+Xgh9OOKv6A3qdD4Dnl/0VOI5EvAv+0s/OseDxVVTwQ=
629+
k8s.io/client-go v0.27.7/go.mod h1:dZ2kqcalYp5YZ2EV12XIMc77G6PxHWOJp/kclZr4+5Q=
630+
k8s.io/component-base v0.27.7 h1:kngM58HR9W9Nqpv7e4rpdRyWnKl/ABpUhLAZ+HoliMs=
631+
k8s.io/component-base v0.27.7/go.mod h1:YGjlCVL1oeKvG3HSciyPHFh+LCjIEqsxz4BDR3cfHRs=
632632
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
633633
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
634634
k8s.io/kube-openapi v0.0.0-20230515203736-54b630e78af5 h1:azYPdzztXxPSa8wb+hksEKayiz0o+PPisO/d+QhWnoo=
@@ -639,8 +639,8 @@ k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/
639639
k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
640640
sigs.k8s.io/cli-utils v0.35.0 h1:dfSJaF1W0frW74PtjwiyoB4cwdRygbHnC7qe7HF0g/Y=
641641
sigs.k8s.io/cli-utils v0.35.0/go.mod h1:ITitykCJxP1vaj1Cew/FZEaVJ2YsTN9Q71m02jebkoE=
642-
sigs.k8s.io/controller-runtime v0.15.1 h1:9UvgKD4ZJGcj24vefUFgZFP3xej/3igL9BsOUTb/+4c=
643-
sigs.k8s.io/controller-runtime v0.15.1/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk=
642+
sigs.k8s.io/controller-runtime v0.15.3 h1:L+t5heIaI3zeejoIyyvLQs5vTVu/67IU2FfisVzFlBc=
643+
sigs.k8s.io/controller-runtime v0.15.3/go.mod h1:kp4jckA4vTx281S/0Yk2LFEEQe67mjg+ev/yknv47Ds=
644644
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
645645
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
646646
sigs.k8s.io/kustomize/api v0.13.4 h1:E38Hfx0G9R9v7vRgKshviPotJQETG0S2gD3JdHLCAsI=

internal/controller/imagerepository_controller.go

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ type ImageRepositoryReconciler struct {
113113
DatabaseReader
114114
}
115115
DeprecatedLoginOpts login.ProviderOptions
116+
AllowInsecureHTTP bool
116117

117118
patchOptions []patch.Option
118119
}
@@ -249,9 +250,15 @@ func (r *ImageRepositoryReconciler) reconcile(ctx context.Context, sp *patch.Ser
249250
}
250251

251252
// Parse image reference.
252-
ref, err := parseImageReference(obj.Spec.Image)
253+
ref, err := r.parseImageReference(obj.Spec.Image, obj.Spec.Insecure)
253254
if err != nil {
254-
conditions.MarkStalled(obj, imagev1.ImageURLInvalidReason, err.Error())
255+
var reason string
256+
if errors.Is(err, helper.ErrInsecureHTTPBlocked) {
257+
reason = meta.InsecureConnectionsDisallowedReason
258+
} else {
259+
reason = imagev1.ImageURLInvalidReason
260+
}
261+
conditions.MarkStalled(obj, reason, err.Error())
255262
result, retErr = ctrl.Result{}, nil
256263
return
257264
}
@@ -268,11 +275,18 @@ func (r *ImageRepositoryReconciler) reconcile(ctx context.Context, sp *patch.Ser
268275
// Check if it can be scanned now.
269276
ok, when, reasonMsg, err := r.shouldScan(*obj, startTime)
270277
if err != nil {
271-
e := fmt.Errorf("failed to determine if it's scan time: %w", err)
272-
conditions.MarkFalse(obj, meta.ReadyCondition, metav1.StatusFailure, e.Error())
278+
var e error
279+
if errors.Is(err, helper.ErrInsecureHTTPBlocked) {
280+
e = err
281+
conditions.MarkStalled(obj, meta.InsecureConnectionsDisallowedReason, e.Error())
282+
} else {
283+
e = fmt.Errorf("failed to determine if it's scan time: %w", err)
284+
conditions.MarkFalse(obj, meta.ReadyCondition, metav1.StatusFailure, e.Error())
285+
}
273286
result, retErr = ctrl.Result{}, e
274287
return
275288
}
289+
conditions.Delete(obj, meta.StalledCondition)
276290

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

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

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

579-
ref, err := name.ParseReference(url)
594+
var opts []name.Option
595+
if insecure {
596+
if r.AllowInsecureHTTP {
597+
opts = append(opts, name.Insecure)
598+
} else {
599+
return nil, helper.ErrInsecureHTTPBlocked
600+
}
601+
}
602+
603+
ref, err := name.ParseReference(url, opts...)
580604
if err != nil {
581605
return nil, err
582606
}

internal/controller/imagerepository_controller_test.go

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434

3535
"github.com/fluxcd/pkg/apis/meta"
3636
"github.com/fluxcd/pkg/runtime/conditions"
37+
"github.com/fluxcd/pkg/runtime/controller"
3738

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

583-
ref, err := parseImageReference(imgRepo)
584+
ref, err := r.parseImageReference(imgRepo, false)
584585
g.Expect(err).ToNot(HaveOccurred())
585586

586587
opts := []remote.Option{}
@@ -656,12 +657,15 @@ func TestGetLatestTags(t *testing.T) {
656657
}
657658
}
658659

659-
func TestParseImageReference(t *testing.T) {
660+
func Test_parseImageReference(t *testing.T) {
660661
tests := []struct {
661-
name string
662-
url string
663-
wantErr bool
664-
wantRef string
662+
name string
663+
url string
664+
insecure bool
665+
allowInsecure bool
666+
wantErr bool
667+
err error
668+
wantRef string
665669
}{
666670
{
667671
name: "simple valid url",
@@ -684,16 +688,39 @@ func TestParseImageReference(t *testing.T) {
684688
wantErr: false,
685689
wantRef: "example.com:9999/foo/bar",
686690
},
691+
{
692+
name: "with insecure allowed",
693+
url: "example.com/foo/bar",
694+
insecure: true,
695+
allowInsecure: true,
696+
wantRef: "example.com/foo/bar",
697+
},
698+
{
699+
name: "with insecure disallowed",
700+
url: "example.com/foo/bar",
701+
insecure: true,
702+
wantErr: true,
703+
err: controller.ErrInsecureHTTPBlocked,
704+
},
687705
}
688706

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

693-
ref, err := parseImageReference(tt.url)
711+
r := &ImageRepositoryReconciler{
712+
AllowInsecureHTTP: tt.allowInsecure,
713+
}
714+
ref, err := r.parseImageReference(tt.url, tt.insecure)
694715
g.Expect(err != nil).To(Equal(tt.wantErr))
716+
if tt.err != nil {
717+
g.Expect(tt.err).To(Equal(err))
718+
}
695719
if err == nil {
696720
g.Expect(ref.String()).To(Equal(tt.wantRef))
721+
if tt.insecure {
722+
g.Expect(ref.Context().Registry.Scheme()).To(Equal("http"))
723+
}
697724
}
698725
})
699726
}

main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ func main() {
7777
logOptions logger.Options
7878
leaderElectionOptions leaderelection.Options
7979
watchOptions helper.WatchOptions
80+
connOptions helper.ConnectionOptions
8081
storagePath string
8182
storageValueLogFileSize int64
8283
concurrent int
@@ -107,11 +108,18 @@ func main() {
107108
rateLimiterOptions.BindFlags(flag.CommandLine)
108109
featureGates.BindFlags(flag.CommandLine)
109110
watchOptions.BindFlags(flag.CommandLine)
111+
connOptions.BindFlags(flag.CommandLine)
110112

111113
flag.Parse()
112114

113115
logger.SetLogger(logger.NewLogger(logOptions))
114116

117+
if err := connOptions.CheckEnvironmentCompatibility(); err != nil {
118+
setupLog.Error(err,
119+
"please verify that your controller flag settings are compatible with the controller's environment")
120+
os.Exit(1)
121+
}
122+
115123
if awsAutoLogin || gcpAutoLogin || azureAutoLogin {
116124
setupLog.Error(errors.New("use of deprecated flags"),
117125
"autologin flags have been deprecated. These flags will be removed in a future release."+
@@ -216,6 +224,7 @@ func main() {
216224
AzureAutoLogin: azureAutoLogin,
217225
GcpAutoLogin: gcpAutoLogin,
218226
},
227+
AllowInsecureHTTP: connOptions.AllowHTTP,
219228
}).SetupWithManager(mgr, controller.ImageRepositoryReconcilerOptions{
220229
RateLimiter: helper.GetRateLimiter(rateLimiterOptions),
221230
}); err != nil {

0 commit comments

Comments
 (0)