Skip to content

Commit 0c9071d

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 2e1ffb8 commit 0c9071d

File tree

8 files changed

+124
-16
lines changed

8 files changed

+124
-16
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ all: manager
3232
KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_KUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p path)"
3333
test: tidy generate fmt vet manifests api-docs install-envtest
3434
KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) go test ./... -coverprofile cover.out
35-
cd api; go test ./... -coverprofile cover.out
35+
cd api; go test -run Test_parseImageReference ./... -coverprofile cover.out
3636

3737
# Build manager binary
3838
manager: generate fmt vet

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

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+
// errInsecureHTTP occurs when plain HTTP communication is tried
90+
// and such behaviour is blocked.
91+
var errInsecureHTTP = 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, errInsecureHTTP) {
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, errInsecureHTTP) {
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.
@@ -468,7 +486,7 @@ func (r *ImageRepositoryReconciler) shouldScan(obj imagev1.ImageRepository, now
468486

469487
// If the canonical image name of the image is different from the last
470488
// observed name, scan now.
471-
ref, err := parseImageReference(obj.Spec.Image)
489+
ref, err := r.parseImageReference(obj.Spec.Image, obj.Spec.Insecure)
472490
if err != nil {
473491
return false, scanInterval, "", err
474492
}
@@ -570,13 +588,23 @@ func eventLogf(ctx context.Context, r kuberecorder.EventRecorder, obj runtime.Ob
570588
}
571589

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

579-
ref, err := name.ParseReference(url)
598+
var opts []name.Option
599+
if insecure {
600+
if r.AllowInsecureHTTP {
601+
opts = append(opts, name.Insecure)
602+
} else {
603+
return nil, errInsecureHTTP
604+
}
605+
}
606+
607+
ref, err := name.ParseReference(url, opts...)
580608
if err != nil {
581609
return nil, err
582610
}

internal/controller/imagerepository_controller_test.go

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ func TestImageRepositoryReconciler_scan(t *testing.T) {
580580
repo.SetAnnotations(map[string]string{meta.ReconcileRequestAnnotation: tt.annotation})
581581
}
582582

583-
ref, err := parseImageReference(imgRepo)
583+
ref, err := r.parseImageReference(imgRepo, false)
584584
g.Expect(err).ToNot(HaveOccurred())
585585

586586
opts := []remote.Option{}
@@ -656,12 +656,15 @@ func TestGetLatestTags(t *testing.T) {
656656
}
657657
}
658658

659-
func TestParseImageReference(t *testing.T) {
659+
func Test_parseImageReference(t *testing.T) {
660660
tests := []struct {
661-
name string
662-
url string
663-
wantErr bool
664-
wantRef string
661+
name string
662+
url string
663+
insecure bool
664+
allowInsecure bool
665+
wantErr bool
666+
err error
667+
wantRef string
665668
}{
666669
{
667670
name: "simple valid url",
@@ -684,16 +687,39 @@ func TestParseImageReference(t *testing.T) {
684687
wantErr: false,
685688
wantRef: "example.com:9999/foo/bar",
686689
},
690+
{
691+
name: "with allowed insecure",
692+
url: "example.com/foo/bar",
693+
insecure: true,
694+
allowInsecure: true,
695+
wantRef: "example.com/foo/bar",
696+
},
697+
{
698+
name: "with disallowed insecure",
699+
url: "example.com/foo/bar",
700+
insecure: true,
701+
wantErr: true,
702+
err: errInsecureHTTP,
703+
},
687704
}
688705

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

693-
ref, err := parseImageReference(tt.url)
710+
r := &ImageRepositoryReconciler{
711+
AllowInsecureHTTP: tt.allowInsecure,
712+
}
713+
ref, err := r.parseImageReference(tt.url, tt.insecure)
694714
g.Expect(err != nil).To(Equal(tt.wantErr))
715+
if tt.err != nil {
716+
g.Expect(tt.err).To(Equal(err))
717+
}
695718
if err == nil {
696719
g.Expect(ref.String()).To(Equal(tt.wantRef))
720+
if tt.insecure {
721+
g.Expect(ref.Context().Registry.Scheme()).To(Equal("http"))
722+
}
697723
}
698724
})
699725
}

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)