From 42aa8ed65703f8d8f9e356a20756ede42605ecd1 Mon Sep 17 00:00:00 2001
From: Michael Bridgen
Date: Thu, 27 Jan 2022 15:42:54 +0000
Subject: [PATCH] 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
---
api/go.mod | 1 +
api/go.sum | 2 +
api/v1beta1/imagerepository_types.go | 11 +--
api/v1beta1/zz_generated.deepcopy.go | 47 +------------
...e.toolkit.fluxcd.io_imagerepositories.yaml | 13 ++++
controllers/imagepolicy_controller.go | 44 ++----------
controllers/policy_test.go | 18 ++---
docs/api/image-reflector.md | 70 +------------------
go.mod | 9 +--
go.sum | 12 ++--
10 files changed, 51 insertions(+), 176 deletions(-)
diff --git a/api/go.mod b/api/go.mod
index 16e2032..cf89ef0 100644
--- a/api/go.mod
+++ b/api/go.mod
@@ -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
diff --git a/api/go.sum b/api/go.sum
index 826d950..783d483 100644
--- a/api/go.sum
+++ b/api/go.sum
@@ -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=
diff --git a/api/v1beta1/imagerepository_types.go b/api/v1beta1/imagerepository_types.go
index cb0648f..582a096 100644
--- a/api/v1beta1/imagerepository_types.go
+++ b/api/v1beta1/imagerepository_types.go
@@ -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 {
diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go
index 340cf5b..366c010 100644
--- a/api/v1beta1/zz_generated.deepcopy.go
+++ b/api/v1beta1/zz_generated.deepcopy.go
@@ -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
diff --git a/config/crd/bases/image.toolkit.fluxcd.io_imagerepositories.yaml b/config/crd/bases/image.toolkit.fluxcd.io_imagerepositories.yaml
index 759b2fc..472d5c3 100644
--- a/config/crd/bases/image.toolkit.fluxcd.io_imagerepositories.yaml
+++ b/config/crd/bases/image.toolkit.fluxcd.io_imagerepositories.yaml
@@ -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
diff --git a/controllers/imagepolicy_controller.go b/controllers/imagepolicy_controller.go
index 7b39940..85076e3 100644
--- a/controllers/imagepolicy_controller.go
+++ b/controllers/imagepolicy_controller.go
@@ -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)
-}
diff --git a/controllers/policy_test.go b/controllers/policy_test.go
index 0369ef4..f05f671 100644
--- a/controllers/policy_test.go
+++ b/controllers/policy_test.go
@@ -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",
diff --git a/docs/api/image-reflector.md b/docs/api/image-reflector.md
index 2b1639e..606889e 100644
--- a/docs/api/image-reflector.md
+++ b/docs/api/image-reflector.md
@@ -12,38 +12,6 @@ OCI image repositories into a cluster, so they can be consulted for
e.g., automation.
Resource Types:
-
-
-(Appears on:
-ImageRepositorySpec)
-
-
@@ -509,9 +477,7 @@ It does not apply to already started scans. Defaults to false.
accessFrom
-
-AccessFrom
-
+github.com/fluxcd/pkg/apis/acl.AccessFrom
|
@@ -655,9 +621,7 @@ It does not apply to already started scans. Defaults to false.
|
accessFrom
-
-AccessFrom
-
+github.com/fluxcd/pkg/apis/acl.AccessFrom
|
@@ -759,36 +723,6 @@ github.com/fluxcd/pkg/apis/meta.ReconcileRequestStatus
-
-
-(Appears on:
-AccessFrom)
-
-
diff --git a/go.mod b/go.mod
index d8dea0a..6153c53 100644
--- a/go.mod
+++ b/go.mod
@@ -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
diff --git a/go.sum b/go.sum
index d22fca9..7006797 100644
--- a/go.sum
+++ b/go.sum
@@ -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=
|