Skip to content

Commit 5790e64

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 e809eb8 commit 5790e64

File tree

10 files changed

+133
-18
lines changed

10 files changed

+133
-18
lines changed

api/v1beta2/imagerepository_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ type ImageRepositorySpec struct {
9898
// +kubebuilder:default:=generic
9999
// +optional
100100
Provider string `json:"provider,omitempty"`
101+
102+
// Insecure, if set to true indicates that the image registry is hosted at an
103+
// HTTP endpoint.
104+
// +optional
105+
Insecure bool `json:"insecure,omitempty"`
101106
}
102107

103108
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
@@ -313,6 +313,10 @@ spec:
313313
image:
314314
description: Image is the name of the image repository
315315
type: string
316+
insecure:
317+
description: Insecure, if set to true indicates that the image registry
318+
is hosted at an HTTP endpoint.
319+
type: boolean
316320
interval:
317321
description: Interval is the length of time to wait between scans
318322
of the image repository.

docs/api/image-reflector.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,19 @@ string
540540
When not specified, defaults to &lsquo;generic&rsquo;.</p>
541541
</td>
542542
</tr>
543+
<tr>
544+
<td>
545+
<code>insecure</code><br>
546+
<em>
547+
bool
548+
</em>
549+
</td>
550+
<td>
551+
<em>(Optional)</em>
552+
<p>Insecure, if set to true indicates that the image registry is hosted at an
553+
HTTP endpoint.</p>
554+
</td>
555+
</tr>
543556
</table>
544557
</td>
545558
</tr>
@@ -725,6 +738,19 @@ string
725738
When not specified, defaults to &lsquo;generic&rsquo;.</p>
726739
</td>
727740
</tr>
741+
<tr>
742+
<td>
743+
<code>insecure</code><br>
744+
<em>
745+
bool
746+
</em>
747+
</td>
748+
<td>
749+
<em>(Optional)</em>
750+
<p>Insecure, if set to true indicates that the image registry is hosted at an
751+
HTTP endpoint.</p>
752+
</td>
753+
</tr>
728754
</tbody>
729755
</table>
730756
</div>

docs/spec/v1beta2/imagerepositories.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,16 @@ spec:
297297
- "1.1.1|1.0.0"
298298
```
299299

300+
### Insecure
301+
302+
`.spec.insecure` is an optional field to specify that the image registry is
303+
hosted at a non-TLS endpoint and thus the controller should use plain HTTP
304+
requests to communicate with the registry.
305+
306+
> If an ImageRepository has `.spec.insecure` as `true` and the controller has
307+
`--insecure-allow-http` set to `false`, then the object is marked as stalled.
308+
For more details, see: https://github.com/fluxcd/flux2/tree/ddcc301ab6289e0640174cb9f3d46f1eeab57927/rfcs/0004-insecure-http#design-details
309+
300310
### Provider
301311

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

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ require (
1313
github.com/fluxcd/pkg/apis/event v0.5.1
1414
github.com/fluxcd/pkg/apis/meta v1.1.1
1515
github.com/fluxcd/pkg/oci v0.28.0
16-
github.com/fluxcd/pkg/runtime v0.39.0
16+
github.com/fluxcd/pkg/runtime v0.40.0
1717
github.com/fluxcd/pkg/version v0.2.2
1818
github.com/google/go-containerregistry v0.15.2
1919
github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20230625233257-b8504803389b

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,8 @@ github.com/fluxcd/pkg/apis/meta v1.1.1 h1:sLAKLbEu7rRzJ+Mytffu3NcpfdbOBTa6hcpOQz
175175
github.com/fluxcd/pkg/apis/meta v1.1.1/go.mod h1:soCfzjFWbm1mqybDcOywWKTCEYlH3skpoNGTboVk234=
176176
github.com/fluxcd/pkg/oci v0.28.0 h1:E8VvMFzU/+9vgM4IFbiwmCwaMPCq1WXPiKUmHtDVSbc=
177177
github.com/fluxcd/pkg/oci v0.28.0/go.mod h1:eFP5sQH4yWghFbcLWxdo0eI6wZ4h3HiTW0UoG33S2pg=
178-
github.com/fluxcd/pkg/runtime v0.39.0 h1:vgmzYS+DT0w8ikX9MqGsOdmMagoiKys2RMGdl/EDbgc=
179-
github.com/fluxcd/pkg/runtime v0.39.0/go.mod h1:0A/0kZv/MPciAj5AoSEDKVeqUFEF6371q7o+zk6l81g=
178+
github.com/fluxcd/pkg/runtime v0.40.0 h1:uGiiEbMZwd7xmbKaVmcH7iilCFW9betWbz0r1taK3G0=
179+
github.com/fluxcd/pkg/runtime v0.40.0/go.mod h1:BqHEOVrZmt19p0q1OlGFWAYh3rZ28+IBpxLB2yPjjQ4=
180180
github.com/fluxcd/pkg/version v0.2.2 h1:ZpVXECeLA5hIQMft11iLp6gN3cKcz6UNuVTQPw/bRdI=
181181
github.com/fluxcd/pkg/version v0.2.2/go.mod h1:NGnh/no8S6PyfCDxRFrPY3T5BUnqP48MxfxNRU0z8C0=
182182
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=

internal/controller/imagerepository_controller.go

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ const (
8686
scanReasonInterval = "triggered by interval"
8787
)
8888

89+
// insecureHTTPError occurs when insecure HTTP communication is tried
90+
// and such behaviour is blocked.
91+
var insecureHTTPError = errors.New("use of insecure plain HTTP connections is blocked")
92+
8993
// getPatchOptions composes patch options based on the given parameters.
9094
// It is used as the options used when patching an object.
9195
func getPatchOptions(ownedConditions []string, controllerName string) []patch.Option {
@@ -113,6 +117,7 @@ type ImageRepositoryReconciler struct {
113117
DatabaseReader
114118
}
115119
DeprecatedLoginOpts login.ProviderOptions
120+
AllowInsecureHTTP bool
116121

117122
patchOptions []patch.Option
118123
}
@@ -249,9 +254,15 @@ func (r *ImageRepositoryReconciler) reconcile(ctx context.Context, sp *patch.Ser
249254
}
250255

251256
// Parse image reference.
252-
ref, err := parseImageReference(obj.Spec.Image)
257+
ref, err := r.parseImageReference(obj.Spec.Image, obj.Spec.Insecure)
253258
if err != nil {
254-
conditions.MarkStalled(obj, imagev1.ImageURLInvalidReason, err.Error())
259+
var reason string
260+
if errors.Is(err, insecureHTTPError) {
261+
reason = meta.InsecureConnectionsDisallowedReason
262+
} else {
263+
reason = imagev1.ImageURLInvalidReason
264+
}
265+
conditions.MarkStalled(obj, reason, err.Error())
255266
result, retErr = ctrl.Result{}, nil
256267
return
257268
}
@@ -268,11 +279,18 @@ func (r *ImageRepositoryReconciler) reconcile(ctx context.Context, sp *patch.Ser
268279
// Check if it can be scanned now.
269280
ok, when, reasonMsg, err := r.shouldScan(*obj, startTime)
270281
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())
282+
var e error
283+
if errors.Is(err, insecureHTTPError) {
284+
e = err
285+
conditions.MarkStalled(obj, meta.InsecureConnectionsDisallowedReason, e.Error())
286+
} else {
287+
e = fmt.Errorf("failed to determine if it's scan time: %w", err)
288+
conditions.MarkFalse(obj, meta.ReadyCondition, metav1.StatusFailure, e.Error())
289+
}
273290
result, retErr = ctrl.Result{}, e
274291
return
275292
}
293+
conditions.Delete(obj, meta.StalledCondition)
276294

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

459477
// If the canonical image name of the image is different from the last
460478
// observed name, scan now.
461-
ref, err := parseImageReference(obj.Spec.Image)
479+
ref, err := r.parseImageReference(obj.Spec.Image, obj.Spec.Insecure)
462480
if err != nil {
463481
return false, scanInterval, "", err
464482
}
@@ -560,13 +578,23 @@ func eventLogf(ctx context.Context, r kuberecorder.EventRecorder, obj runtime.Ob
560578
}
561579

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

569-
ref, err := name.ParseReference(url)
588+
var opts []name.Option
589+
if insecure {
590+
if r.AllowInsecureHTTP {
591+
opts = append(opts, name.Insecure)
592+
} else {
593+
return nil, insecureHTTPError
594+
}
595+
}
596+
597+
ref, err := name.ParseReference(url, opts...)
570598
if err != nil {
571599
return nil, err
572600
}

internal/controller/imagerepository_controller_test.go

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ func TestImageRepositoryReconciler_scan(t *testing.T) {
527527
repo.SetAnnotations(map[string]string{meta.ReconcileRequestAnnotation: tt.annotation})
528528
}
529529

530-
ref, err := parseImageReference(imgRepo)
530+
ref, err := r.parseImageReference(imgRepo, false)
531531
g.Expect(err).ToNot(HaveOccurred())
532532

533533
opts := []remote.Option{}
@@ -603,12 +603,14 @@ func TestGetLatestTags(t *testing.T) {
603603
}
604604
}
605605

606-
func TestParseImageReference(t *testing.T) {
606+
func Test_parseImageReference(t *testing.T) {
607607
tests := []struct {
608-
name string
609-
url string
610-
wantErr bool
611-
wantRef string
608+
name string
609+
url string
610+
insecure bool
611+
allowInsecure bool
612+
wantErr bool
613+
wantRef string
612614
}{
613615
{
614616
name: "simple valid url",
@@ -631,16 +633,37 @@ func TestParseImageReference(t *testing.T) {
631633
wantErr: false,
632634
wantRef: "example.com:9999/foo/bar",
633635
},
636+
{
637+
name: "with allowed insecure",
638+
url: "example.com/foo/bar",
639+
insecure: true,
640+
allowInsecure: true,
641+
wantErr: false,
642+
wantRef: "example.com/foo/bar",
643+
},
644+
{
645+
name: "with disallowed insecure",
646+
url: "example.com/foo/bar",
647+
insecure: true,
648+
allowInsecure: false,
649+
wantErr: true,
650+
},
634651
}
635652

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

640-
ref, err := parseImageReference(tt.url)
657+
r := &ImageRepositoryReconciler{
658+
AllowInsecureHTTP: tt.allowInsecure,
659+
}
660+
ref, err := r.parseImageReference(tt.url, tt.insecure)
641661
g.Expect(err != nil).To(Equal(tt.wantErr))
642662
if err == nil {
643663
g.Expect(ref.String()).To(Equal(tt.wantRef))
664+
if tt.insecure {
665+
g.Expect(ref.Context().Registry.Scheme()).To(Equal("http"))
666+
}
644667
}
645668
})
646669
}

main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ func main() {
7676
logOptions logger.Options
7777
leaderElectionOptions leaderelection.Options
7878
watchOptions helper.WatchOptions
79+
connOptions helper.ConnectionOptions
7980
storagePath string
8081
storageValueLogFileSize int64
8182
concurrent int
@@ -106,11 +107,18 @@ func main() {
106107
rateLimiterOptions.BindFlags(flag.CommandLine)
107108
featureGates.BindFlags(flag.CommandLine)
108109
watchOptions.BindFlags(flag.CommandLine)
110+
connOptions.BindFlags(flag.CommandLine)
109111

110112
flag.Parse()
111113

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

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

podinfo-registry.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
apiVersion: image.toolkit.fluxcd.io/v1beta1
3+
kind: ImageRepository
4+
metadata:
5+
name: podinfo
6+
spec:
7+
image: ghcr.io/stefanprodan/podinfo
8+
interval: 1m0s
9+
exclusionList:
10+
- "^.*\\.4$"

0 commit comments

Comments
 (0)