Use fluxcd/pkg/{apis,runtime}/acl for ACLs

This commit replaces the local ACL-related API types, and the func for
checking access, with their standarised forms in fluxcd/pkg/apis/acl
and fluxcd/pkg/runtime/acl respectively.

The test case for "When the ACL is empty, it denies access" needed
adjustment because the zero value of acl.AccessFrom is not valid -- it
needs an explicit list of namespace selectors. Providing `nil` in the
test case is the equivalent of providing a zero value.

Signed-off-by: Michael Bridgen <michael@weave.works>
This commit is contained in:
Michael Bridgen 2022-01-27 15:42:54 +00:00
parent cfbe0304e8
commit 42aa8ed657
10 changed files with 51 additions and 176 deletions

View File

@ -3,6 +3,7 @@ module github.com/fluxcd/image-reflector-controller/api
go 1.17
require (
github.com/fluxcd/pkg/apis/acl v0.0.3
github.com/fluxcd/pkg/apis/meta v0.10.2
k8s.io/apimachinery v0.23.1
sigs.k8s.io/controller-runtime v0.11.0

View File

@ -121,6 +121,8 @@ github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMi
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fluxcd/pkg/apis/acl v0.0.3 h1:Lw0ZHdpnO4G7Zy9KjrzwwBmDZQuy4qEjaU/RvA6k1lc=
github.com/fluxcd/pkg/apis/acl v0.0.3/go.mod h1:XPts6lRJ9C9fIF9xVWofmQwftvhY25n1ps7W9xw0XLU=
github.com/fluxcd/pkg/apis/meta v0.10.2 h1:pnDBBEvfs4HaKiVAYgz+e/AQ8dLvcgmVfSeBroZ/KKI=
github.com/fluxcd/pkg/apis/meta v0.10.2/go.mod h1:KQ2er9xa6koy7uoPMZjIjNudB5p4tXs+w0GO6fRcy7I=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=

View File

@ -21,6 +21,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fluxcd/pkg/apis/acl"
"github.com/fluxcd/pkg/apis/meta"
)
@ -71,15 +72,7 @@ type ImageRepositorySpec struct {
// AccessFrom defines an ACL for allowing cross-namespace references
// to the ImageRepository object based on the caller's namespace labels.
// +optional
AccessFrom *AccessFrom `json:"accessFrom,omitempty"`
}
type AccessFrom struct {
NamespaceSelectors []NamespaceSelector `json:"namespaceSelectors,omitempty"`
}
type NamespaceSelector struct {
MatchLabels map[string]string `json:"matchLabels,omitempty"`
AccessFrom *acl.AccessFrom `json:"accessFrom,omitempty"`
}
type ScanResult struct {

View File

@ -22,33 +22,12 @@ limitations under the License.
package v1beta1
import (
"github.com/fluxcd/pkg/apis/acl"
"github.com/fluxcd/pkg/apis/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AccessFrom) DeepCopyInto(out *AccessFrom) {
*out = *in
if in.NamespaceSelectors != nil {
in, out := &in.NamespaceSelectors, &out.NamespaceSelectors
*out = make([]NamespaceSelector, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessFrom.
func (in *AccessFrom) DeepCopy() *AccessFrom {
if in == nil {
return nil
}
out := new(AccessFrom)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AlphabeticalPolicy) DeepCopyInto(out *AlphabeticalPolicy) {
*out = *in
@ -277,7 +256,7 @@ func (in *ImageRepositorySpec) DeepCopyInto(out *ImageRepositorySpec) {
}
if in.AccessFrom != nil {
in, out := &in.AccessFrom, &out.AccessFrom
*out = new(AccessFrom)
*out = new(acl.AccessFrom)
(*in).DeepCopyInto(*out)
}
}
@ -320,28 +299,6 @@ func (in *ImageRepositoryStatus) DeepCopy() *ImageRepositoryStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NamespaceSelector) DeepCopyInto(out *NamespaceSelector) {
*out = *in
if in.MatchLabels != nil {
in, out := &in.MatchLabels, &out.MatchLabels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceSelector.
func (in *NamespaceSelector) DeepCopy() *NamespaceSelector {
if in == nil {
return nil
}
out := new(NamespaceSelector)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NumericalPolicy) DeepCopyInto(out *NumericalPolicy) {
*out = *in

View File

@ -394,14 +394,27 @@ spec:
labels.
properties:
namespaceSelectors:
description: NamespaceSelectors is the list of namespace selectors
to which this ACL applies. Items in this list are evaluated
using a logical OR operation.
items:
description: NamespaceSelector selects the namespaces to which
this ACL applies. An empty map of MatchLabels matches all
namespaces in a cluster.
properties:
matchLabels:
additionalProperties:
type: string
description: MatchLabels is a map of {key,value} pairs.
A single {key,value} in the matchLabels map is equivalent
to an element of matchExpressions, whose key field is
"key", the operator is "In", and the values array contains
only "value". The requirements are ANDed.
type: object
type: object
type: array
required:
- namespaceSelectors
type: object
certSecretRef:
description: "CertSecretRef can be given the name of a secret containing

View File

@ -21,10 +21,8 @@ import (
"fmt"
"time"
v1 "k8s.io/api/core/v1"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
kuberecorder "k8s.io/client-go/tools/record"
@ -36,7 +34,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
aclapi "github.com/fluxcd/pkg/apis/acl"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/acl"
"github.com/fluxcd/pkg/runtime/events"
"github.com/fluxcd/pkg/runtime/metrics"
@ -115,11 +115,13 @@ func (r *ImagePolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request)
}
// check if we are allowed to use the referenced ImageRepository
if _, err := r.hasAccessToRepository(ctx, req, pol.Spec.ImageRepositoryRef, repo.Spec.AccessFrom); err != nil {
aclAuth := acl.NewAuthorization(r.Client)
if err := aclAuth.HasAccessToRef(ctx, &pol, repoNamespacedName, repo.Spec.AccessFrom); err != nil {
imagev1.SetImagePolicyReadiness(
&pol,
metav1.ConditionFalse,
"AccessDenied",
aclapi.AccessDeniedReason,
err.Error(),
)
if err := r.patchStatus(ctx, req, pol.Status); err != nil {
@ -327,37 +329,3 @@ func (r *ImagePolicyReconciler) patchStatus(ctx context.Context, req ctrl.Reques
return r.Status().Patch(ctx, &res, patch)
}
func (r *ImagePolicyReconciler) hasAccessToRepository(ctx context.Context, policy ctrl.Request, repo meta.NamespacedObjectReference, acl *imagev1.AccessFrom) (bool, error) {
// grant access if the policy is in the same namespace as the repository
if repo.Namespace == "" || policy.Namespace == repo.Namespace {
return true, nil
}
// deny access if the repository has no ACL defined
if acl == nil {
return false, fmt.Errorf("ImageRepository '%s/%s' can't be accessed due to missing access list",
repo.Namespace, repo.Name)
}
// get the policy namespace labels
var policyNamespace v1.Namespace
if err := r.Get(ctx, types.NamespacedName{Name: policy.Namespace}, &policyNamespace); err != nil {
return false, err
}
policyLabels := policyNamespace.GetLabels()
// check if the policy namespace labels match any ACL
for _, selector := range acl.NamespaceSelectors {
sel, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: selector.MatchLabels})
if err != nil {
return false, err
}
if sel.Matches(labels.Set(policyLabels)) {
return true, nil
}
}
return false, fmt.Errorf("ImageRepository '%s/%s' can't be accessed due to labels mismatch on namespace '%s'",
repo.Namespace, repo.Name, policy.Namespace)
}

View File

@ -28,6 +28,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"github.com/fluxcd/pkg/apis/acl"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
// +kubebuilder:scaffold:imports
)
@ -480,7 +482,7 @@ var _ = Describe("ImagePolicy controller", func() {
Spec: imagev1.ImageRepositorySpec{
Interval: metav1.Duration{Duration: reconciliationInterval},
Image: imgRepo,
AccessFrom: &imagev1.AccessFrom{},
AccessFrom: nil,
},
}
repoObjectName := types.NamespacedName{
@ -532,7 +534,7 @@ var _ = Describe("ImagePolicy controller", func() {
_ = r.Get(ctx, polObjectName, &pol)
return apimeta.IsStatusConditionFalse(pol.Status.Conditions, meta.ReadyCondition)
}, timeout, interval).Should(BeTrue())
Expect(apimeta.FindStatusCondition(pol.Status.Conditions, meta.ReadyCondition).Reason).To(Equal("AccessDenied"))
Expect(apimeta.FindStatusCondition(pol.Status.Conditions, meta.ReadyCondition).Reason).To(Equal(acl.AccessDeniedReason))
Expect(r.Delete(ctx, &pol)).To(Succeed())
})
@ -553,8 +555,8 @@ var _ = Describe("ImagePolicy controller", func() {
Spec: imagev1.ImageRepositorySpec{
Interval: metav1.Duration{Duration: reconciliationInterval},
Image: imgRepo,
AccessFrom: &imagev1.AccessFrom{
NamespaceSelectors: []imagev1.NamespaceSelector{
AccessFrom: &acl.AccessFrom{
NamespaceSelectors: []acl.NamespaceSelector{
{
MatchLabels: make(map[string]string),
},
@ -636,8 +638,8 @@ var _ = Describe("ImagePolicy controller", func() {
Spec: imagev1.ImageRepositorySpec{
Interval: metav1.Duration{Duration: reconciliationInterval},
Image: imgRepo,
AccessFrom: &imagev1.AccessFrom{
NamespaceSelectors: []imagev1.NamespaceSelector{
AccessFrom: &acl.AccessFrom{
NamespaceSelectors: []acl.NamespaceSelector{
{
MatchLabels: policyNamespace.Labels,
},
@ -737,8 +739,8 @@ var _ = Describe("ImagePolicy controller", func() {
Spec: imagev1.ImageRepositorySpec{
Interval: metav1.Duration{Duration: reconciliationInterval},
Image: imgRepo,
AccessFrom: &imagev1.AccessFrom{
NamespaceSelectors: []imagev1.NamespaceSelector{
AccessFrom: &acl.AccessFrom{
NamespaceSelectors: []acl.NamespaceSelector{
{
MatchLabels: map[string]string{
"tenant": "b",

View File

@ -12,38 +12,6 @@ OCI image repositories into a cluster, so they can be consulted for
e.g., automation.</p>
Resource Types:
<ul class="simple"></ul>
<h3 id="image.toolkit.fluxcd.io/v1beta1.AccessFrom">AccessFrom
</h3>
<p>
(<em>Appears on:</em>
<a href="#image.toolkit.fluxcd.io/v1beta1.ImageRepositorySpec">ImageRepositorySpec</a>)
</p>
<div class="md-typeset__scrollwrap">
<div class="md-typeset__table">
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>namespaceSelectors</code><br>
<em>
<a href="#image.toolkit.fluxcd.io/v1beta1.NamespaceSelector">
[]NamespaceSelector
</a>
</em>
</td>
<td>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="image.toolkit.fluxcd.io/v1beta1.AlphabeticalPolicy">AlphabeticalPolicy
</h3>
<p>
@ -509,9 +477,7 @@ It does not apply to already started scans. Defaults to false.</p>
<td>
<code>accessFrom</code><br>
<em>
<a href="#image.toolkit.fluxcd.io/v1beta1.AccessFrom">
AccessFrom
</a>
github.com/fluxcd/pkg/apis/acl.AccessFrom
</em>
</td>
<td>
@ -655,9 +621,7 @@ It does not apply to already started scans. Defaults to false.</p>
<td>
<code>accessFrom</code><br>
<em>
<a href="#image.toolkit.fluxcd.io/v1beta1.AccessFrom">
AccessFrom
</a>
github.com/fluxcd/pkg/apis/acl.AccessFrom
</em>
</td>
<td>
@ -759,36 +723,6 @@ github.com/fluxcd/pkg/apis/meta.ReconcileRequestStatus
</table>
</div>
</div>
<h3 id="image.toolkit.fluxcd.io/v1beta1.NamespaceSelector">NamespaceSelector
</h3>
<p>
(<em>Appears on:</em>
<a href="#image.toolkit.fluxcd.io/v1beta1.AccessFrom">AccessFrom</a>)
</p>
<div class="md-typeset__scrollwrap">
<div class="md-typeset__table">
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>matchLabels</code><br>
<em>
map[string]string
</em>
</td>
<td>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="image.toolkit.fluxcd.io/v1beta1.NumericalPolicy">NumericalPolicy
</h3>
<p>

9
go.mod
View File

@ -11,8 +11,9 @@ require (
github.com/aws/aws-sdk-go v1.42.9
github.com/dgraph-io/badger/v3 v3.2103.2
github.com/fluxcd/image-reflector-controller/api v0.15.0
github.com/fluxcd/pkg/apis/acl v0.0.3
github.com/fluxcd/pkg/apis/meta v0.10.2
github.com/fluxcd/pkg/runtime v0.12.3
github.com/fluxcd/pkg/runtime v0.12.4
github.com/fluxcd/pkg/version v0.1.0
github.com/google/go-containerregistry v0.7.0
github.com/onsi/ginkgo v1.16.5
@ -25,8 +26,8 @@ require (
)
require (
github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.1 // indirect
cloud.google.com/go v0.97.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
@ -49,7 +50,7 @@ require (
github.com/golang/snappy v0.0.3 // indirect
github.com/google/flatbuffers v1.12.1 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.2.0 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
@ -77,7 +78,7 @@ require (
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.19.1 // indirect
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect
golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect
golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9 // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 // indirect

12
go.sum
View File

@ -337,10 +337,12 @@ github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fluxcd/pkg/apis/acl v0.0.3 h1:Lw0ZHdpnO4G7Zy9KjrzwwBmDZQuy4qEjaU/RvA6k1lc=
github.com/fluxcd/pkg/apis/acl v0.0.3/go.mod h1:XPts6lRJ9C9fIF9xVWofmQwftvhY25n1ps7W9xw0XLU=
github.com/fluxcd/pkg/apis/meta v0.10.2 h1:pnDBBEvfs4HaKiVAYgz+e/AQ8dLvcgmVfSeBroZ/KKI=
github.com/fluxcd/pkg/apis/meta v0.10.2/go.mod h1:KQ2er9xa6koy7uoPMZjIjNudB5p4tXs+w0GO6fRcy7I=
github.com/fluxcd/pkg/runtime v0.12.3 h1:h21AZ3YG5MAP7DxFF9hfKrP+vFzys2L7CkUbPFjbP/0=
github.com/fluxcd/pkg/runtime v0.12.3/go.mod h1:imJ2xYy/d4PbSinX2IefmZk+iS2c1P5fY0js8mCE4SM=
github.com/fluxcd/pkg/runtime v0.12.4 h1:gA27RG/+adN2/7Qe03PB46Iwmye/MusPCpuS4zQ2fW0=
github.com/fluxcd/pkg/runtime v0.12.4/go.mod h1:gspNvhAqodZgSmK1ZhMtvARBf/NGAlxmaZaIOHkJYsc=
github.com/fluxcd/pkg/version v0.1.0 h1:v+SmCanmCB5Tj2Cx9TXlj+kNRfPGbAvirkeqsp7ZEAQ=
github.com/fluxcd/pkg/version v0.1.0/go.mod h1:V7Z/w8dxLQzv0FHqa5ox5TeyOd2zOd49EeuWFgnwyj4=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
@ -461,8 +463,9 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-containerregistry v0.7.0 h1:u0onUUOcyoCDHEiJoyR1R1gx5er1+r06V5DBhUU5ndk=
github.com/google/go-containerregistry v0.7.0/go.mod h1:2zaoelrL0d08gGbpdP3LqyUuBmhWbpD6IOe2s9nLS2k=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -1016,8 +1019,9 @@ golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211111160137-58aab5ef257a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9 h1:kmreh1vGI63l2FxOAYS3Yv6ATsi7lSTuwNSVbGfJV9I=
golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=