Skip to content

Commit

Permalink
[RFC-0004] imagerepo: add support for insecure registries
Browse files Browse the repository at this point in the history
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 <jaiswalsanskar078@gmail.com>
  • Loading branch information
aryan9600 committed Jul 3, 2023
1 parent e809eb8 commit 18e07e0
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 18 deletions.
5 changes: 5 additions & 0 deletions api/v1beta2/imagerepository_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,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 @@ -313,6 +313,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/image-reflector.md
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,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 @@ -725,6 +738,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 @@ -297,6 +297,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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
github.com/fluxcd/pkg/apis/event v0.5.1
github.com/fluxcd/pkg/apis/meta v1.1.1
github.com/fluxcd/pkg/oci v0.28.0
github.com/fluxcd/pkg/runtime v0.39.0
github.com/fluxcd/pkg/runtime v0.40.0
github.com/fluxcd/pkg/version v0.2.2
github.com/google/go-containerregistry v0.15.2
github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20230625233257-b8504803389b
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ github.com/fluxcd/pkg/apis/meta v1.1.1 h1:sLAKLbEu7rRzJ+Mytffu3NcpfdbOBTa6hcpOQz
github.com/fluxcd/pkg/apis/meta v1.1.1/go.mod h1:soCfzjFWbm1mqybDcOywWKTCEYlH3skpoNGTboVk234=
github.com/fluxcd/pkg/oci v0.28.0 h1:E8VvMFzU/+9vgM4IFbiwmCwaMPCq1WXPiKUmHtDVSbc=
github.com/fluxcd/pkg/oci v0.28.0/go.mod h1:eFP5sQH4yWghFbcLWxdo0eI6wZ4h3HiTW0UoG33S2pg=
github.com/fluxcd/pkg/runtime v0.39.0 h1:vgmzYS+DT0w8ikX9MqGsOdmMagoiKys2RMGdl/EDbgc=
github.com/fluxcd/pkg/runtime v0.39.0/go.mod h1:0A/0kZv/MPciAj5AoSEDKVeqUFEF6371q7o+zk6l81g=
github.com/fluxcd/pkg/runtime v0.40.0 h1:uGiiEbMZwd7xmbKaVmcH7iilCFW9betWbz0r1taK3G0=
github.com/fluxcd/pkg/runtime v0.40.0/go.mod h1:BqHEOVrZmt19p0q1OlGFWAYh3rZ28+IBpxLB2yPjjQ4=
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
44 changes: 36 additions & 8 deletions internal/controller/imagerepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ const (
scanReasonInterval = "triggered by interval"
)

// insecureHTTPError occurs when insecure HTTP communication is tried
// and such behaviour is blocked.
var insecureHTTPError = errors.New("use of insecure plain HTTP connections is blocked")

// getPatchOptions composes patch options based on the given parameters.
// It is used as the options used when patching an object.
func getPatchOptions(ownedConditions []string, controllerName string) []patch.Option {
Expand Down Expand Up @@ -113,6 +117,7 @@ type ImageRepositoryReconciler struct {
DatabaseReader
}
DeprecatedLoginOpts login.ProviderOptions
AllowInsecureHTTP bool

patchOptions []patch.Option
}
Expand Down Expand Up @@ -249,9 +254,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, insecureHTTPError) {
reason = meta.InsecureConnectionsDisallowedReason
} else {
reason = imagev1.ImageURLInvalidReason
}
conditions.MarkStalled(obj, reason, err.Error())
result, retErr = ctrl.Result{}, nil
return
}
Expand All @@ -268,11 +279,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, insecureHTTPError) {
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 @@ -458,7 +476,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 @@ -560,13 +578,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, insecureHTTPError
}
}

ref, err := name.ParseReference(url, opts...)
if err != nil {
return nil, err
}
Expand Down
37 changes: 30 additions & 7 deletions internal/controller/imagerepository_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,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 @@ -603,12 +603,14 @@ 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
wantRef string
}{
{
name: "simple valid url",
Expand All @@ -631,16 +633,37 @@ func TestParseImageReference(t *testing.T) {
wantErr: false,
wantRef: "example.com:9999/foo/bar",
},
{
name: "with allowed insecure",
url: "example.com/foo/bar",
insecure: true,
allowInsecure: true,
wantErr: false,
wantRef: "example.com/foo/bar",
},
{
name: "with disallowed insecure",
url: "example.com/foo/bar",
insecure: true,
allowInsecure: false,
wantErr: true,
},
}

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 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 @@ -76,6 +76,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 @@ -106,11 +107,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 @@ -215,6 +223,7 @@ func main() {
AzureAutoLogin: azureAutoLogin,
GcpAutoLogin: gcpAutoLogin,
},
AllowInsecureHTTP: connOptions.AllowHTTP,
}).SetupWithManager(mgr, controller.ImageRepositoryReconcilerOptions{
RateLimiter: helper.GetRateLimiter(rateLimiterOptions),
}); err != nil {
Expand Down

0 comments on commit 18e07e0

Please sign in to comment.