From 29896f29a506bfa7fabb703ae9fb3b2be2e1d8fa Mon Sep 17 00:00:00 2001 From: Luther Monson Date: Fri, 26 May 2023 11:44:13 -0700 Subject: [PATCH 1/2] adding IRSA functionality through an OIDC provider using an s3 bucket --- api/v1beta1/awscluster_conversion.go | 7 + api/v1beta1/conversion_test.go | 19 +- api/v1beta1/s3bucket.go | 8 - api/v1beta1/zz_generated.conversion.go | 21 +- api/v1beta2/awscluster_types.go | 15 +- api/v1beta2/types.go | 8 + api/v1beta2/zz_generated.deepcopy.go | 16 + .../bootstrap/cluster_api_controller.go | 8 + .../bootstrap/fixtures/customsuffix.yaml | 5 + .../bootstrap/fixtures/default.yaml | 5 + .../fixtures/with_all_secret_backends.yaml | 5 + .../fixtures/with_allow_assume_role.yaml | 5 + .../fixtures/with_bootstrap_user.yaml | 5 + .../fixtures/with_custom_bootstrap_user.yaml | 5 + .../with_different_instance_profiles.yaml | 5 + .../bootstrap/fixtures/with_eks_console.yaml | 5 + .../fixtures/with_eks_default_roles.yaml | 5 + .../bootstrap/fixtures/with_eks_disable.yaml | 5 + .../fixtures/with_eks_kms_prefix.yaml | 5 + .../fixtures/with_extra_statements.yaml | 5 + .../bootstrap/fixtures/with_s3_bucket.yaml | 8 + .../fixtures/with_ssm_secret_backend.yaml | 5 + ...tructure.cluster.x-k8s.io_awsclusters.yaml | 26 +- ....cluster.x-k8s.io_awsclustertemplates.yaml | 14 +- config/rbac/role.yaml | 1 + controllers/awscluster_controller.go | 31 +- controllers/awscluster_controller_test.go | 6 +- .../awscluster_controller_unit_test.go | 14 +- controllers/awsmachine_controller.go | 96 +++-- controllers/awsmachine_controller_test.go | 30 +- .../awsmachine_controller_unit_test.go | 71 +++- controllers/helpers_test.go | 33 ++ controlplane/eks/api/v1beta1/conversion.go | 12 + .../api/v1beta1/zz_generated.conversion.go | 42 +- .../v1beta2/awsmanagedcontrolplane_types.go | 10 +- .../eks/api/v1beta2/zz_generated.deepcopy.go | 15 - go.mod | 3 +- go.sum | 211 +++++++++- main.go | 6 + pkg/cloud/scope/cluster.go | 48 ++- pkg/cloud/scope/ec2.go | 17 + pkg/cloud/scope/iam.go | 10 + pkg/cloud/scope/machine.go | 6 + pkg/cloud/scope/managedcontrolplane.go | 24 +- pkg/cloud/scope/shared.go | 3 + pkg/cloud/services/eks/oidc.go | 32 +- pkg/cloud/services/iam/iam.go | 133 +++++++ pkg/cloud/services/iam/oidc.go | 375 ++++++++++++++++++ pkg/cloud/services/iam/podidentitywebhook.go | 340 ++++++++++++++++ pkg/cloud/services/iam/reconcilers.go | 180 +++++++++ pkg/cloud/services/interfaces.go | 12 +- .../objectstore_machine_interface_mock.go | 20 +- pkg/cloud/services/s3/s3.go | 137 +++++-- pkg/cloud/services/s3/s3_test.go | 98 +++-- 54 files changed, 1984 insertions(+), 247 deletions(-) create mode 100644 pkg/cloud/scope/iam.go create mode 100644 pkg/cloud/services/iam/iam.go create mode 100644 pkg/cloud/services/iam/oidc.go create mode 100644 pkg/cloud/services/iam/podidentitywebhook.go create mode 100644 pkg/cloud/services/iam/reconcilers.go diff --git a/api/v1beta1/awscluster_conversion.go b/api/v1beta1/awscluster_conversion.go index 433c3bacc2..b750d06bfc 100644 --- a/api/v1beta1/awscluster_conversion.go +++ b/api/v1beta1/awscluster_conversion.go @@ -18,6 +18,7 @@ package v1beta1 import ( apiconversion "k8s.io/apimachinery/pkg/conversion" + "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" infrav2 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" utilconversion "sigs.k8s.io/cluster-api/util/conversion" "sigs.k8s.io/controller-runtime/pkg/conversion" @@ -50,6 +51,8 @@ func (src *AWSCluster) ConvertTo(dstRaw conversion.Hub) error { dst.Status.Bastion.PlacementGroupName = restored.Status.Bastion.PlacementGroupName } dst.Spec.Partition = restored.Spec.Partition + dst.Spec.AssociateOIDCProvider = restored.Spec.AssociateOIDCProvider + dst.Status.OIDCProvider = restored.Status.OIDCProvider for role, sg := range restored.Status.Network.SecurityGroups { dst.Status.Network.SecurityGroups[role] = sg @@ -170,3 +173,7 @@ func (r *AWSClusterList) ConvertFrom(srcRaw conversion.Hub) error { func Convert_v1beta2_SubnetSpec_To_v1beta1_SubnetSpec(in *infrav2.SubnetSpec, out *SubnetSpec, s apiconversion.Scope) error { return autoConvert_v1beta2_SubnetSpec_To_v1beta1_SubnetSpec(in, out, s) } + +func Convert_v1beta2_AWSClusterStatus_To_v1beta1_AWSClusterStatus(in *v1beta2.AWSClusterStatus, out *AWSClusterStatus, scope apiconversion.Scope) error { + return autoConvert_v1beta2_AWSClusterStatus_To_v1beta1_AWSClusterStatus(in, out, scope) +} diff --git a/api/v1beta1/conversion_test.go b/api/v1beta1/conversion_test.go index 7579d59aa8..24aa530ac2 100644 --- a/api/v1beta1/conversion_test.go +++ b/api/v1beta1/conversion_test.go @@ -19,9 +19,8 @@ package v1beta1 import ( "testing" - . "github.com/onsi/gomega" - fuzz "github.com/google/gofuzz" + . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" "k8s.io/apimachinery/pkg/runtime" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" @@ -38,7 +37,7 @@ func fuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { func AWSMachineFuzzer(obj *AWSMachine, c fuzz.Continue) { c.FuzzNoCustom(obj) - + // AWSMachine.Spec.FailureDomain, AWSMachine.Spec.Subnet.ARN and AWSMachine.Spec.AdditionalSecurityGroups.ARN has been removed in v1beta2, so setting it to nil in order to avoid v1beta1 --> v1beta2 --> v1beta1 round trip errors. if obj.Spec.Subnet != nil { obj.Spec.Subnet.ARN = nil @@ -54,7 +53,7 @@ func AWSMachineFuzzer(obj *AWSMachine, c fuzz.Continue) { func AWSMachineTemplateFuzzer(obj *AWSMachineTemplate, c fuzz.Continue) { c.FuzzNoCustom(obj) - + // AWSMachineTemplate.Spec.Template.Spec.FailureDomain, AWSMachineTemplate.Spec.Template.Spec.Subnet.ARN and AWSMachineTemplate.Spec.Template.Spec.AdditionalSecurityGroups.ARN has been removed in v1beta2, so setting it to nil in order to avoid v1beta1 --> v1beta2 --> v1beta round trip errors. if obj.Spec.Template.Spec.Subnet != nil { obj.Spec.Template.Spec.Subnet.ARN = nil @@ -81,16 +80,16 @@ func TestFuzzyConversion(t *testing.T) { })) t.Run("for AWSMachine", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ - Scheme: scheme, - Hub: &v1beta2.AWSMachine{}, - Spoke: &AWSMachine{}, + Scheme: scheme, + Hub: &v1beta2.AWSMachine{}, + Spoke: &AWSMachine{}, FuzzerFuncs: []fuzzer.FuzzerFuncs{fuzzFuncs}, })) t.Run("for AWSMachineTemplate", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ - Scheme: scheme, - Hub: &v1beta2.AWSMachineTemplate{}, - Spoke: &AWSMachineTemplate{}, + Scheme: scheme, + Hub: &v1beta2.AWSMachineTemplate{}, + Spoke: &AWSMachineTemplate{}, FuzzerFuncs: []fuzzer.FuzzerFuncs{fuzzFuncs}, })) diff --git a/api/v1beta1/s3bucket.go b/api/v1beta1/s3bucket.go index 111cdc98f1..619cf27679 100644 --- a/api/v1beta1/s3bucket.go +++ b/api/v1beta1/s3bucket.go @@ -21,8 +21,6 @@ import ( "net" "k8s.io/apimachinery/pkg/util/validation/field" - - "sigs.k8s.io/cluster-api-provider-aws/v2/feature" ) // Validate validates S3Bucket fields. @@ -37,12 +35,6 @@ func (b *S3Bucket) Validate() []*field.Error { errs = append(errs, field.Required(field.NewPath("spec", "s3Bucket", "name"), "can't be empty")) } - // Feature gate is not enabled but ignition is enabled then send a forbidden error. - if !feature.Gates.Enabled(feature.BootstrapFormatIgnition) { - errs = append(errs, field.Forbidden(field.NewPath("spec", "s3Bucket"), - "can be set only if the BootstrapFormatIgnition feature gate is enabled")) - } - if b.ControlPlaneIAMInstanceProfile == "" { errs = append(errs, field.Required(field.NewPath("spec", "s3Bucket", "controlPlaneIAMInstanceProfiles"), "can't be empty")) diff --git a/api/v1beta1/zz_generated.conversion.go b/api/v1beta1/zz_generated.conversion.go index a00d63e148..0ad6c9919b 100644 --- a/api/v1beta1/zz_generated.conversion.go +++ b/api/v1beta1/zz_generated.conversion.go @@ -180,11 +180,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta2.AWSClusterStatus)(nil), (*AWSClusterStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta2_AWSClusterStatus_To_v1beta1_AWSClusterStatus(a.(*v1beta2.AWSClusterStatus), b.(*AWSClusterStatus), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*AWSClusterTemplate)(nil), (*v1beta2.AWSClusterTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_AWSClusterTemplate_To_v1beta2_AWSClusterTemplate(a.(*AWSClusterTemplate), b.(*v1beta2.AWSClusterTemplate), scope) }); err != nil { @@ -545,6 +540,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta2.AWSClusterStatus)(nil), (*AWSClusterStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta2_AWSClusterStatus_To_v1beta1_AWSClusterStatus(a.(*v1beta2.AWSClusterStatus), b.(*AWSClusterStatus), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta2.AWSLoadBalancerSpec)(nil), (*AWSLoadBalancerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta2_AWSLoadBalancerSpec_To_v1beta1_AWSLoadBalancerSpec(a.(*v1beta2.AWSLoadBalancerSpec), b.(*AWSLoadBalancerSpec), scope) }); err != nil { @@ -939,6 +939,7 @@ func autoConvert_v1beta2_AWSClusterSpec_To_v1beta1_AWSClusterSpec(in *v1beta2.AW return err } out.IdentityRef = (*AWSIdentityReference)(unsafe.Pointer(in.IdentityRef)) +<<<<<<< HEAD if in.S3Bucket != nil { in, out := &in.S3Bucket, &out.S3Bucket *out = new(S3Bucket) @@ -948,6 +949,10 @@ func autoConvert_v1beta2_AWSClusterSpec_To_v1beta1_AWSClusterSpec(in *v1beta2.AW } else { out.S3Bucket = nil } +======= + out.S3Bucket = (*S3Bucket)(unsafe.Pointer(in.S3Bucket)) + // WARNING: in.AssociateOIDCProvider requires manual conversion: does not exist in peer-type +>>>>>>> 17f26c99d (adding IRSA functionality through an OIDC provider using an s3 bucket) return nil } @@ -1065,14 +1070,10 @@ func autoConvert_v1beta2_AWSClusterStatus_To_v1beta1_AWSClusterStatus(in *v1beta out.Bastion = nil } out.Conditions = *(*apiv1beta1.Conditions)(unsafe.Pointer(&in.Conditions)) + // WARNING: in.OIDCProvider requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta2_AWSClusterStatus_To_v1beta1_AWSClusterStatus is an autogenerated conversion function. -func Convert_v1beta2_AWSClusterStatus_To_v1beta1_AWSClusterStatus(in *v1beta2.AWSClusterStatus, out *AWSClusterStatus, s conversion.Scope) error { - return autoConvert_v1beta2_AWSClusterStatus_To_v1beta1_AWSClusterStatus(in, out, s) -} - func autoConvert_v1beta1_AWSClusterTemplate_To_v1beta2_AWSClusterTemplate(in *AWSClusterTemplate, out *v1beta2.AWSClusterTemplate, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_v1beta1_AWSClusterTemplateSpec_To_v1beta2_AWSClusterTemplateSpec(&in.Spec, &out.Spec, s); err != nil { diff --git a/api/v1beta2/awscluster_types.go b/api/v1beta2/awscluster_types.go index 0a1bdafbfc..2ff52d8392 100644 --- a/api/v1beta2/awscluster_types.go +++ b/api/v1beta2/awscluster_types.go @@ -96,11 +96,18 @@ type AWSClusterSpec struct { IdentityRef *AWSIdentityReference `json:"identityRef,omitempty"` // S3Bucket contains options to configure a supporting S3 bucket for this - // cluster - currently used for nodes requiring Ignition + // cluster - Used for nodes requiring Ignition // (https://coreos.github.io/ignition/) for bootstrapping (requires - // BootstrapFormatIgnition feature flag to be enabled). + // BootstrapFormatIgnition feature flag to be enabled) and for storing OIDC endpoint + // certificates for use with IRSA // +optional S3Bucket *S3Bucket `json:"s3Bucket,omitempty"` + + // AssociateOIDCProvider can be enabled to automatically create an identity + // provider and install the pod identity webhook from AWS for use with IRSA. + // This will only work if the S3Bucket is configured properly. + // +kubebuilder:default=false + AssociateOIDCProvider bool `json:"associateOIDCProvider,omitempty"` } // AWSIdentityKind defines allowed AWS identity types. @@ -255,6 +262,10 @@ type AWSClusterStatus struct { FailureDomains clusterv1.FailureDomains `json:"failureDomains,omitempty"` Bastion *Instance `json:"bastion,omitempty"` Conditions clusterv1.Conditions `json:"conditions,omitempty"` + + // OIDCProvider holds the status of the identity provider for this cluster + // +optional + OIDCProvider OIDCProviderStatus `json:"oidcProvider,omitempty"` } type S3Bucket struct { diff --git a/api/v1beta2/types.go b/api/v1beta2/types.go index 9e77923bbd..67ba50997c 100644 --- a/api/v1beta2/types.go +++ b/api/v1beta2/types.go @@ -407,3 +407,11 @@ const ( // AmazonLinuxGPU is the AmazonLinux GPU AMI type. AmazonLinuxGPU EKSAMILookupType = "AmazonLinuxGPU" ) + +// OIDCProviderStatus holds the status of the AWS OIDC identity provider. +type OIDCProviderStatus struct { + // ARN holds the ARN of the provider + ARN string `json:"arn,omitempty"` + // TrustPolicy contains the boilerplate IAM trust policy to use for IRSA + TrustPolicy string `json:"trustPolicy,omitempty"` +} diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 40e92eb73a..6cc7f9bb46 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -429,6 +429,7 @@ func (in *AWSClusterStatus) DeepCopyInto(out *AWSClusterStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + out.OIDCProvider = in.OIDCProvider } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSClusterStatus. @@ -1649,6 +1650,21 @@ func (in *NetworkStatus) DeepCopy() *NetworkStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OIDCProviderStatus) DeepCopyInto(out *OIDCProviderStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OIDCProviderStatus. +func (in *OIDCProviderStatus) DeepCopy() *OIDCProviderStatus { + if in == nil { + return nil + } + out := new(OIDCProviderStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RouteTable) DeepCopyInto(out *RouteTable) { *out = *in diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go b/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go index 616e60d164..69c2790099 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go +++ b/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go @@ -174,6 +174,11 @@ func (t Template) ControllersPolicy() *iamv1.PolicyDocument { "ec2:DeleteLaunchTemplateVersions", "ec2:DescribeKeyPairs", "ec2:ModifyInstanceMetadataOptions", + "iam:CreateOpenIDConnectProvider", + "iam:DeleteOpenIDConnectProvider", + "iam:ListOpenIDConnectProviders", + "iam:GetOpenIDConnectProvider", + "iam:TagOpenIDConnectProvider", }, }, { @@ -284,6 +289,9 @@ func (t Template) ControllersPolicy() *iamv1.PolicyDocument { "s3:DeleteObject", "s3:PutBucketPolicy", "s3:PutBucketTagging", + "s3:PutBucketOwnershipControls", + "s3:PutObjectAcl", + "s3:PutBucketPublicAccessBlock", }, }) } diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/customsuffix.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/customsuffix.yaml index 74a6a8ebb1..7afd0dc3ab 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/customsuffix.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/customsuffix.yaml @@ -233,6 +233,11 @@ Resources: - ec2:DeleteLaunchTemplateVersions - ec2:DescribeKeyPairs - ec2:ModifyInstanceMetadataOptions + - iam:CreateOpenIDConnectProvider + - iam:DeleteOpenIDConnectProvider + - iam:ListOpenIDConnectProviders + - iam:GetOpenIDConnectProvider + - iam:TagOpenIDConnectProvider Effect: Allow Resource: - '*' diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/default.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/default.yaml index 74f0a3c0c6..204db5781b 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/default.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/default.yaml @@ -233,6 +233,11 @@ Resources: - ec2:DeleteLaunchTemplateVersions - ec2:DescribeKeyPairs - ec2:ModifyInstanceMetadataOptions + - iam:CreateOpenIDConnectProvider + - iam:DeleteOpenIDConnectProvider + - iam:ListOpenIDConnectProviders + - iam:GetOpenIDConnectProvider + - iam:TagOpenIDConnectProvider Effect: Allow Resource: - '*' diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_all_secret_backends.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_all_secret_backends.yaml index 65dc75ed70..08af8d5e4f 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_all_secret_backends.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_all_secret_backends.yaml @@ -239,6 +239,11 @@ Resources: - ec2:DeleteLaunchTemplateVersions - ec2:DescribeKeyPairs - ec2:ModifyInstanceMetadataOptions + - iam:CreateOpenIDConnectProvider + - iam:DeleteOpenIDConnectProvider + - iam:ListOpenIDConnectProviders + - iam:GetOpenIDConnectProvider + - iam:TagOpenIDConnectProvider Effect: Allow Resource: - '*' diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_allow_assume_role.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_allow_assume_role.yaml index 4a3080ac2c..a6eae7a082 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_allow_assume_role.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_allow_assume_role.yaml @@ -233,6 +233,11 @@ Resources: - ec2:DeleteLaunchTemplateVersions - ec2:DescribeKeyPairs - ec2:ModifyInstanceMetadataOptions + - iam:CreateOpenIDConnectProvider + - iam:DeleteOpenIDConnectProvider + - iam:ListOpenIDConnectProviders + - iam:GetOpenIDConnectProvider + - iam:TagOpenIDConnectProvider Effect: Allow Resource: - '*' diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_bootstrap_user.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_bootstrap_user.yaml index c28eee7bc7..d2b84847cb 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_bootstrap_user.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_bootstrap_user.yaml @@ -239,6 +239,11 @@ Resources: - ec2:DeleteLaunchTemplateVersions - ec2:DescribeKeyPairs - ec2:ModifyInstanceMetadataOptions + - iam:CreateOpenIDConnectProvider + - iam:DeleteOpenIDConnectProvider + - iam:ListOpenIDConnectProviders + - iam:GetOpenIDConnectProvider + - iam:TagOpenIDConnectProvider Effect: Allow Resource: - '*' diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_custom_bootstrap_user.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_custom_bootstrap_user.yaml index 0c87cb7a28..511f576c9d 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_custom_bootstrap_user.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_custom_bootstrap_user.yaml @@ -239,6 +239,11 @@ Resources: - ec2:DeleteLaunchTemplateVersions - ec2:DescribeKeyPairs - ec2:ModifyInstanceMetadataOptions + - iam:CreateOpenIDConnectProvider + - iam:DeleteOpenIDConnectProvider + - iam:ListOpenIDConnectProviders + - iam:GetOpenIDConnectProvider + - iam:TagOpenIDConnectProvider Effect: Allow Resource: - '*' diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_different_instance_profiles.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_different_instance_profiles.yaml index 24e9168542..e94b94336d 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_different_instance_profiles.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_different_instance_profiles.yaml @@ -233,6 +233,11 @@ Resources: - ec2:DeleteLaunchTemplateVersions - ec2:DescribeKeyPairs - ec2:ModifyInstanceMetadataOptions + - iam:CreateOpenIDConnectProvider + - iam:DeleteOpenIDConnectProvider + - iam:ListOpenIDConnectProviders + - iam:GetOpenIDConnectProvider + - iam:TagOpenIDConnectProvider Effect: Allow Resource: - '*' diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_console.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_console.yaml index 901e52da36..53d519a787 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_console.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_console.yaml @@ -233,6 +233,11 @@ Resources: - ec2:DeleteLaunchTemplateVersions - ec2:DescribeKeyPairs - ec2:ModifyInstanceMetadataOptions + - iam:CreateOpenIDConnectProvider + - iam:DeleteOpenIDConnectProvider + - iam:ListOpenIDConnectProviders + - iam:GetOpenIDConnectProvider + - iam:TagOpenIDConnectProvider Effect: Allow Resource: - '*' diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_default_roles.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_default_roles.yaml index 99ae19e1bc..8c327b053a 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_default_roles.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_default_roles.yaml @@ -233,6 +233,11 @@ Resources: - ec2:DeleteLaunchTemplateVersions - ec2:DescribeKeyPairs - ec2:ModifyInstanceMetadataOptions + - iam:CreateOpenIDConnectProvider + - iam:DeleteOpenIDConnectProvider + - iam:ListOpenIDConnectProviders + - iam:GetOpenIDConnectProvider + - iam:TagOpenIDConnectProvider Effect: Allow Resource: - '*' diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_disable.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_disable.yaml index 1e71d0dbac..14204bd927 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_disable.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_disable.yaml @@ -233,6 +233,11 @@ Resources: - ec2:DeleteLaunchTemplateVersions - ec2:DescribeKeyPairs - ec2:ModifyInstanceMetadataOptions + - iam:CreateOpenIDConnectProvider + - iam:DeleteOpenIDConnectProvider + - iam:ListOpenIDConnectProviders + - iam:GetOpenIDConnectProvider + - iam:TagOpenIDConnectProvider Effect: Allow Resource: - '*' diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_kms_prefix.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_kms_prefix.yaml index da8b66ef69..3f172ddd00 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_kms_prefix.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_eks_kms_prefix.yaml @@ -233,6 +233,11 @@ Resources: - ec2:DeleteLaunchTemplateVersions - ec2:DescribeKeyPairs - ec2:ModifyInstanceMetadataOptions + - iam:CreateOpenIDConnectProvider + - iam:DeleteOpenIDConnectProvider + - iam:ListOpenIDConnectProviders + - iam:GetOpenIDConnectProvider + - iam:TagOpenIDConnectProvider Effect: Allow Resource: - '*' diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_extra_statements.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_extra_statements.yaml index 3455ca7c4d..e16056e13e 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_extra_statements.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_extra_statements.yaml @@ -239,6 +239,11 @@ Resources: - ec2:DeleteLaunchTemplateVersions - ec2:DescribeKeyPairs - ec2:ModifyInstanceMetadataOptions + - iam:CreateOpenIDConnectProvider + - iam:DeleteOpenIDConnectProvider + - iam:ListOpenIDConnectProviders + - iam:GetOpenIDConnectProvider + - iam:TagOpenIDConnectProvider Effect: Allow Resource: - '*' diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_s3_bucket.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_s3_bucket.yaml index a045a10bf0..69634f6c16 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_s3_bucket.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_s3_bucket.yaml @@ -233,6 +233,11 @@ Resources: - ec2:DeleteLaunchTemplateVersions - ec2:DescribeKeyPairs - ec2:ModifyInstanceMetadataOptions + - iam:CreateOpenIDConnectProvider + - iam:DeleteOpenIDConnectProvider + - iam:ListOpenIDConnectProviders + - iam:GetOpenIDConnectProvider + - iam:TagOpenIDConnectProvider Effect: Allow Resource: - '*' @@ -289,6 +294,9 @@ Resources: - s3:DeleteObject - s3:PutBucketPolicy - s3:PutBucketTagging + - s3:PutBucketOwnershipControls + - s3:PutObjectAcl + - s3:PutBucketPublicAccessBlock Effect: Allow Resource: - arn:*:s3:::cluster-api-provider-aws-* diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_ssm_secret_backend.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_ssm_secret_backend.yaml index 9a3c810b38..102802213b 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_ssm_secret_backend.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_ssm_secret_backend.yaml @@ -233,6 +233,11 @@ Resources: - ec2:DeleteLaunchTemplateVersions - ec2:DescribeKeyPairs - ec2:ModifyInstanceMetadataOptions + - iam:CreateOpenIDConnectProvider + - iam:DeleteOpenIDConnectProvider + - iam:ListOpenIDConnectProviders + - iam:GetOpenIDConnectProvider + - iam:TagOpenIDConnectProvider Effect: Allow Resource: - '*' diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml index 400268cae9..2f75b31750 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml @@ -915,6 +915,13 @@ spec: resources managed by the AWS provider, in addition to the ones added by default. type: object + associateOIDCProvider: + default: false + description: AssociateOIDCProvider can be enabled to automatically + create an identity provider and install the pod identity webhook + from AWS for use with IRSA. This will only work if the S3Bucket + is configured properly. + type: boolean bastion: description: Bastion contains options to configure the bastion host. properties: @@ -1484,9 +1491,10 @@ spec: type: string s3Bucket: description: S3Bucket contains options to configure a supporting S3 - bucket for this cluster - currently used for nodes requiring Ignition - (https://coreos.github.io/ignition/) for bootstrapping (requires - BootstrapFormatIgnition feature flag to be enabled). + bucket for this cluster - Used for nodes requiring Ignition (https://coreos.github.io/ignition/) + for bootstrapping (requires BootstrapFormatIgnition feature flag + to be enabled) and for storing OIDC endpoint certificates for use + with IRSA properties: controlPlaneIAMInstanceProfile: description: ControlPlaneIAMInstanceProfile is a name of the IAMInstanceProfile, @@ -2151,6 +2159,18 @@ spec: security group to its unique name, if any. type: object type: object + oidcProvider: + description: OIDCProvider holds the status of the identity provider + for this cluster + properties: + arn: + description: ARN holds the ARN of the provider + type: string + trustPolicy: + description: TrustPolicy contains the boilerplate IAM trust policy + to use for IRSA + type: string + type: object ready: default: false type: boolean diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml index 49ba3ef552..d3b2de6769 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml @@ -505,6 +505,13 @@ spec: add to AWS resources managed by the AWS provider, in addition to the ones added by default. type: object + associateOIDCProvider: + default: false + description: AssociateOIDCProvider can be enabled to automatically + create an identity provider and install the pod identity + webhook from AWS for use with IRSA. This will only work + if the S3Bucket is configured properly. + type: boolean bastion: description: Bastion contains options to configure the bastion host. @@ -1103,9 +1110,10 @@ spec: type: string s3Bucket: description: S3Bucket contains options to configure a supporting - S3 bucket for this cluster - currently used for nodes requiring - Ignition (https://coreos.github.io/ignition/) for bootstrapping - (requires BootstrapFormatIgnition feature flag to be enabled). + S3 bucket for this cluster - Used for nodes requiring Ignition + (https://coreos.github.io/ignition/) for bootstrapping (requires + BootstrapFormatIgnition feature flag to be enabled) and + for storing OIDC endpoint certificates for use with IRSA properties: controlPlaneIAMInstanceProfile: description: ControlPlaneIAMInstanceProfile is a name diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 7cab425197..2f37b32478 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -187,6 +187,7 @@ rules: resources: - awsclusters verbs: + - create - delete - get - list diff --git a/controllers/awscluster_controller.go b/controllers/awscluster_controller.go index 49de7add0a..bbd4b32c59 100644 --- a/controllers/awscluster_controller.go +++ b/controllers/awscluster_controller.go @@ -46,6 +46,7 @@ import ( "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/ec2" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/elb" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/gc" + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/iam" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/instancestate" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/network" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/s3" @@ -74,6 +75,7 @@ type AWSClusterReconciler struct { networkServiceFactory func(scope.ClusterScope) services.NetworkInterface elbServiceFactory func(scope.ELBScope) services.ELBInterface securityGroupFactory func(scope.ClusterScope) services.SecurityGroupInterface + iamServiceFactory func(scope.EC2Scope) services.IAMInterface Endpoints []scope.ServiceEndpoint WatchFilterValue string ExternalResourceGC bool @@ -125,7 +127,15 @@ func (r *AWSClusterReconciler) getSecurityGroupService(scope scope.ClusterScope) return securitygroup.NewService(&scope, securityGroupRolesForCluster(scope)) } -// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=awsclusters,verbs=get;list;watch;update;patch;delete +// getIAMService factory func is added for testing purpose so that we can inject mocked EC2Service to the AWSClusterReconciler. +func (r *AWSClusterReconciler) getIAMService(scope scope.EC2Scope) services.IAMInterface { + if r.iamServiceFactory != nil { + return r.iamServiceFactory(scope) + } + return iam.NewService(scope) +} + +// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=awsclusters,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=awsclusters/status,verbs=get;update;patch // +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters;clusters/status,verbs=get;list;watch // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=awsclusterroleidentities;awsclusterstaticidentities,verbs=get;list;watch @@ -196,7 +206,7 @@ func (r *AWSClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) } // Handle non-deleted clusters - return r.reconcileNormal(clusterScope) + return r.reconcileNormal(ctx, clusterScope) } func (r *AWSClusterReconciler) reconcileDelete(ctx context.Context, clusterScope *scope.ClusterScope) error { @@ -207,6 +217,7 @@ func (r *AWSClusterReconciler) reconcileDelete(ctx context.Context, clusterScope networkSvc := r.getNetworkService(*clusterScope) sgService := r.getSecurityGroupService(*clusterScope) s3Service := s3.NewService(clusterScope) + iamService := r.getIAMService(clusterScope) if feature.Gates.Enabled(feature.EventBridgeInstanceState) { instancestateSvc := instancestate.NewService(clusterScope) @@ -252,6 +263,14 @@ func (r *AWSClusterReconciler) reconcileDelete(ctx context.Context, clusterScope allErrs = append(allErrs, errors.Wrap(err, "error deleting network")) } + if err := iamService.DeleteOIDCProvider(ctx); err != nil { + allErrs = append(allErrs, errors.Wrapf(err, "error deleting OIDC Provider")) + } + + if err := s3Service.DeleteBucket(); err != nil { + allErrs = append(allErrs, errors.Wrapf(err, "error deleting S3 Bucket")) + } + if len(allErrs) > 0 { return kerrors.NewAggregate(allErrs) } @@ -261,7 +280,7 @@ func (r *AWSClusterReconciler) reconcileDelete(ctx context.Context, clusterScope return nil } -func (r *AWSClusterReconciler) reconcileNormal(clusterScope *scope.ClusterScope) (reconcile.Result, error) { +func (r *AWSClusterReconciler) reconcileNormal(ctx context.Context, clusterScope *scope.ClusterScope) (reconcile.Result, error) { clusterScope.Info("Reconciling AWSCluster") awsCluster := clusterScope.AWSCluster @@ -279,6 +298,7 @@ func (r *AWSClusterReconciler) reconcileNormal(clusterScope *scope.ClusterScope) networkSvc := r.getNetworkService(*clusterScope) sgService := r.getSecurityGroupService(*clusterScope) s3Service := s3.NewService(clusterScope) + iamService := r.getIAMService(clusterScope) if err := networkSvc.ReconcileNetwork(); err != nil { clusterScope.Error(err, "failed to reconcile network") @@ -316,6 +336,11 @@ func (r *AWSClusterReconciler) reconcileNormal(clusterScope *scope.ClusterScope) return reconcile.Result{}, errors.Wrapf(err, "failed to reconcile S3 Bucket for AWSCluster %s/%s", awsCluster.Namespace, awsCluster.Name) } + if err := iamService.ReconcileOIDCProvider(ctx); err != nil { + clusterScope.Error(err, "failed to reconcile OIDC provider") + return reconcile.Result{RequeueAfter: 15 * time.Second}, nil + } + if awsCluster.Status.Network.APIServerELB.DNSName == "" { conditions.MarkFalse(awsCluster, infrav1.LoadBalancerReadyCondition, infrav1.WaitForDNSNameReason, clusterv1.ConditionSeverityInfo, "") clusterScope.Info("Waiting on API server ELB DNS name") diff --git a/controllers/awscluster_controller_test.go b/controllers/awscluster_controller_test.go index 28209c9412..ef10aa3cf2 100644 --- a/controllers/awscluster_controller_test.go +++ b/controllers/awscluster_controller_test.go @@ -152,7 +152,7 @@ func TestAWSClusterReconcilerIntegrationTests(t *testing.T) { IsPublic: false, }, }) - _, err = reconciler.reconcileNormal(cs) + _, err = reconciler.reconcileNormal(context.TODO(), cs) g.Expect(err).To(BeNil()) g.Expect(cs.VPC().ID).To(Equal("vpc-exists")) expectAWSClusterConditions(g, cs.AWSCluster, []conditionAssertion{ @@ -252,7 +252,7 @@ func TestAWSClusterReconcilerIntegrationTests(t *testing.T) { IsPublic: false, }, }) - _, err = reconciler.reconcileNormal(cs) + _, err = reconciler.reconcileNormal(context.TODO(), cs) g.Expect(err).To(BeNil()) g.Expect(cs.VPC().ID).To(Equal("vpc-exists")) expectAWSClusterConditions(g, cs.AWSCluster, []conditionAssertion{ @@ -330,7 +330,7 @@ func TestAWSClusterReconcilerIntegrationTests(t *testing.T) { return ec2Svc } - _, err = reconciler.reconcileNormal(cs) + _, err = reconciler.reconcileNormal(context.TODO(), cs) g.Expect(err.Error()).To(ContainSubstring("The maximum number of VPCs has been reached")) err = reconciler.reconcileDelete(ctx, cs) diff --git a/controllers/awscluster_controller_unit_test.go b/controllers/awscluster_controller_unit_test.go index 07328fe8d4..fc8ffd3cec 100644 --- a/controllers/awscluster_controller_unit_test.go +++ b/controllers/awscluster_controller_unit_test.go @@ -242,7 +242,7 @@ func TestAWSClusterReconcileOperations(t *testing.T) { IsPublic: false, }, }) - _, err = reconciler.reconcileNormal(cs) + _, err = reconciler.reconcileNormal(context.TODO(), cs) g.Expect(err).To(BeNil()) expectAWSClusterConditions(g, cs.AWSCluster, []conditionAssertion{{infrav1.LoadBalancerReadyCondition, corev1.ConditionTrue, "", ""}}) g.Expect(awsCluster.GetFinalizers()).To(ContainElement(infrav1.ClusterFinalizer)) @@ -267,7 +267,7 @@ func TestAWSClusterReconcileOperations(t *testing.T) { }, ) g.Expect(err).To(BeNil()) - _, err = reconciler.reconcileNormal(cs) + _, err = reconciler.reconcileNormal(context.TODO(), cs) g.Expect(err).Should(Equal(expectedErr)) }) t.Run("Should fail AWSCluster create with ClusterSecurityGroupsReadyCondition status false", func(t *testing.T) { @@ -288,7 +288,7 @@ func TestAWSClusterReconcileOperations(t *testing.T) { }, ) g.Expect(err).To(BeNil()) - _, err = reconciler.reconcileNormal(cs) + _, err = reconciler.reconcileNormal(context.TODO(), cs) g.Expect(err).ToNot(BeNil()) expectAWSClusterConditions(g, cs.AWSCluster, []conditionAssertion{{infrav1.ClusterSecurityGroupsReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityWarning, infrav1.ClusterSecurityGroupReconciliationFailedReason}}) }) @@ -311,7 +311,7 @@ func TestAWSClusterReconcileOperations(t *testing.T) { }, ) g.Expect(err).To(BeNil()) - _, err = reconciler.reconcileNormal(cs) + _, err = reconciler.reconcileNormal(context.TODO(), cs) g.Expect(err).ToNot(BeNil()) expectAWSClusterConditions(g, cs.AWSCluster, []conditionAssertion{{infrav1.BastionHostReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityWarning, infrav1.BastionHostFailedReason}}) }) @@ -335,7 +335,7 @@ func TestAWSClusterReconcileOperations(t *testing.T) { }, ) g.Expect(err).To(BeNil()) - _, err = reconciler.reconcileNormal(cs) + _, err = reconciler.reconcileNormal(context.TODO(), cs) g.Expect(err).ToNot(BeNil()) expectAWSClusterConditions(g, cs.AWSCluster, []conditionAssertion{{infrav1.LoadBalancerReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityWarning, infrav1.LoadBalancerFailedReason}}) }) @@ -359,7 +359,7 @@ func TestAWSClusterReconcileOperations(t *testing.T) { }, ) g.Expect(err).To(BeNil()) - _, err = reconciler.reconcileNormal(cs) + _, err = reconciler.reconcileNormal(context.TODO(), cs) g.Expect(err).To(BeNil()) expectAWSClusterConditions(g, cs.AWSCluster, []conditionAssertion{{infrav1.LoadBalancerReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityInfo, infrav1.WaitForDNSNameReason}}) }) @@ -384,7 +384,7 @@ func TestAWSClusterReconcileOperations(t *testing.T) { ) awsCluster.Status.Network.APIServerELB.DNSName = "test-apiserver.us-east-1.aws" g.Expect(err).To(BeNil()) - _, err = reconciler.reconcileNormal(cs) + _, err = reconciler.reconcileNormal(context.TODO(), cs) g.Expect(err).To(BeNil()) expectAWSClusterConditions(g, cs.AWSCluster, []conditionAssertion{{infrav1.LoadBalancerReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityInfo, infrav1.WaitForDNSNameResolveReason}}) }) diff --git a/controllers/awsmachine_controller.go b/controllers/awsmachine_controller.go index 5c86d3c234..afb4ec1e12 100644 --- a/controllers/awsmachine_controller.go +++ b/controllers/awsmachine_controller.go @@ -32,6 +32,7 @@ import ( "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "k8s.io/klog/v2" "k8s.io/utils/pointer" @@ -242,7 +243,7 @@ func (r *AWSMachineReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Ma log := logger.FromContext(ctx) AWSClusterToAWSMachines := r.AWSClusterToAWSMachines(log) - controller, err := ctrl.NewControllerManagedBy(mgr). + managedController, err := ctrl.NewControllerManagedBy(mgr). WithOptions(options). For(&infrav1.AWSMachine{}). Watches( @@ -290,7 +291,7 @@ func (r *AWSMachineReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Ma } requeueAWSMachinesForUnpausedCluster := r.requeueAWSMachinesForUnpausedCluster(log) - return controller.Watch( + return managedController.Watch( source.Kind(mgr.GetCache(), &clusterv1.Cluster{}), handler.EnqueueRequestsFromMapFunc(requeueAWSMachinesForUnpausedCluster), predicates.ClusterUnpausedAndInfrastructureReady(log.GetLogger()), @@ -451,7 +452,7 @@ func (r *AWSMachineReconciler) findInstance(scope *scope.MachineScope, ec2svc se return instance, nil } -func (r *AWSMachineReconciler) reconcileNormal(_ context.Context, machineScope *scope.MachineScope, clusterScope cloud.ClusterScoper, ec2Scope scope.EC2Scope, elbScope scope.ELBScope, objectStoreScope scope.S3Scope) (ctrl.Result, error) { +func (r *AWSMachineReconciler) reconcileNormal(ctx context.Context, machineScope *scope.MachineScope, clusterScope cloud.ClusterScoper, ec2Scope scope.EC2Scope, elbScope scope.ELBScope, objectStoreScope scope.S3Scope) (ctrl.Result, error) { machineScope.Trace("Reconciling AWSMachine") // If the AWSMachine is in an error state, return early. @@ -585,30 +586,19 @@ func (r *AWSMachineReconciler) reconcileNormal(_ context.Context, machineScope * // tasks that can take place during all known instance states if machineScope.InstanceIsInKnownState() { - _, err = r.ensureTags(ec2svc, machineScope.AWSMachine, machineScope.GetInstanceID(), machineScope.AdditionalTags()) - if err != nil { - machineScope.Error(err, "failed to ensure tags") - return ctrl.Result{}, err - } - - if instance != nil { - r.ensureStorageTags(ec2svc, instance, machineScope.AWSMachine, machineScope.AdditionalTags()) - } - - if err := r.reconcileLBAttachment(machineScope, elbScope, instance); err != nil { - machineScope.Error(err, "failed to reconcile LB attachment") + machineScope.Debug("running known state tasks", "instance", instance) + if err := r.reconcileKnownStateTasks(ctx, machineScope, ec2svc, elbScope, instance); err != nil { return ctrl.Result{}, err } } // tasks that can only take place during operational instance states if machineScope.InstanceIsOperational() { - err := r.reconcileOperationalState(ec2svc, machineScope, instance) + err := r.reconcileOperationalState(ctx, ec2svc, machineScope, instance) if err != nil { return ctrl.Result{}, err } } - machineScope.Debug("done reconciling instance", "instance", instance) if shouldRequeue { machineScope.Debug("but find the instance is pending, requeue", "instance", instance.ID) @@ -617,7 +607,26 @@ func (r *AWSMachineReconciler) reconcileNormal(_ context.Context, machineScope * return ctrl.Result{}, nil } -func (r *AWSMachineReconciler) reconcileOperationalState(ec2svc services.EC2Interface, machineScope *scope.MachineScope, instance *infrav1.Instance) error { +func (r *AWSMachineReconciler) reconcileKnownStateTasks(_ context.Context, machineScope *scope.MachineScope, ec2svc services.EC2Interface, elbScope scope.ELBScope, instance *infrav1.Instance) error { + _, err := r.ensureTags(ec2svc, machineScope.AWSMachine, machineScope.GetInstanceID(), machineScope.AdditionalTags()) + if err != nil { + machineScope.Error(err, "failed to ensure tags") + return err + } + + if instance != nil { + r.ensureStorageTags(ec2svc, instance, machineScope.AWSMachine, machineScope.AdditionalTags()) + } + + if err := r.reconcileLBAttachment(machineScope, elbScope, instance); err != nil { + machineScope.Error(err, "failed to reconcile LB attachment") + return err + } + + return nil +} + +func (r *AWSMachineReconciler) reconcileOperationalState(ctx context.Context, ec2svc services.EC2Interface, machineScope *scope.MachineScope, instance *infrav1.Instance) error { machineScope.SetAddresses(instance.Addresses) existingSecurityGroups, err := ec2svc.GetInstanceSecurityGroups(*machineScope.GetInstanceID()) @@ -641,6 +650,45 @@ func (r *AWSMachineReconciler) reconcileOperationalState(ec2svc services.EC2Inte return err } + // check if the remote kubeconfig works and annotate the cluster + _, ok := machineScope.InfraCluster.InfraCluster().GetAnnotations()[scope.KubeconfigReadyAnnotation] + if !ok && machineScope.IsControlPlane() { + // if a control plane node is operational check for a kubeconfig and a working control plane node + // and set the annotation so any reconciliation which requires workload api access can complete + remoteClient, err := machineScope.InfraCluster.RemoteClient() + if err != nil { + return err + } + + var nodes corev1.NodeList + if err := remoteClient.List(ctx, &nodes, client.MatchingLabels(map[string]string{"node-role.kubernetes.io/control-plane": ""})); err != nil { + return err + } + + oneReady := false + for i := range nodes.Items { + if util.IsNodeReady(&nodes.Items[i]) { + oneReady = true // if one control plane is ready return true + } + } + + if oneReady { + awsCluster := &infrav1.AWSCluster{} + key := types.NamespacedName{Namespace: machineScope.InfraCluster.Namespace(), Name: machineScope.InfraCluster.Name()} + if err := r.Client.Get(ctx, key, awsCluster); err != nil { + return err + } + anno := awsCluster.GetAnnotations() + anno[scope.KubeconfigReadyAnnotation] = "true" + awsCluster.SetAnnotations(anno) + if err := r.Client.Update(ctx, awsCluster); err != nil { + return err + } + } else { + r.Log.Info("waiting for a control plane node to be ready before annotating the cluster, do you need to deploy a CNI?") + } + } + return nil } @@ -750,7 +798,7 @@ func (r *AWSMachineReconciler) ignitionUserData(scope *scope.MachineScope, objec return nil, errors.New("object store service not available") } - objectURL, err := objectStoreSvc.Create(scope, userData) + objectURL, err := objectStoreSvc.Create(scope.GetBootstrapDataKey(), userData) if err != nil { return nil, errors.Wrap(err, "creating userdata object") } @@ -814,7 +862,7 @@ func (r *AWSMachineReconciler) deleteBootstrapData(machineScope *scope.MachineSc } if machineScope.UseSecretsManager(userDataFormat) { - if err := r.deleteEncryptedBootstrapDataSecret(machineScope, clusterScope); err != nil { + if err := r.deleteEncryptedBootstrapDataSecret(machineScope, clusterScope); err != nil && !apierrors.IsNotFound(err) { return err } } @@ -852,7 +900,7 @@ func (r *AWSMachineReconciler) deleteIgnitionBootstrapDataFromS3(machineScope *s machineScope.Info("Deleting unneeded entry from AWS S3", "secretPrefix", machineScope.GetSecretPrefix()) - if err := objectStoreSvc.Delete(machineScope); err != nil { + if err := objectStoreSvc.Delete(machineScope.GetBootstrapDataKey()); err != nil { return errors.Wrap(err, "deleting bootstrap data object") } @@ -1158,19 +1206,19 @@ func (r *AWSMachineReconciler) ensureStorageTags(ec2svc services.EC2Interface, i if err != nil { r.Log.Error(err, "Failed to fetch the changed volume tags in EC2 instance") } - annotations[volumeID] = newAnnotation + prevAnnotations[volumeID] = newAnnotation } else { newAnnotation, err := r.ensureVolumeTags(ec2svc, aws.String(volumeID), make(map[string]interface{}), additionalTags) if err != nil { r.Log.Error(err, "Failed to fetch the changed volume tags in EC2 instance") } - annotations[volumeID] = newAnnotation + prevAnnotations[volumeID] = newAnnotation } } if !cmp.Equal(prevAnnotations, annotations, cmpopts.EquateEmpty()) { // We also need to update the annotation if anything changed. - err = r.updateMachineAnnotationJSON(machine, VolumeTagsLastAppliedAnnotation, annotations) + err = r.updateMachineAnnotationJSON(machine, VolumeTagsLastAppliedAnnotation, prevAnnotations) if err != nil { r.Log.Error(err, "Failed to fetch the changed volume tags in EC2 instance") } diff --git a/controllers/awsmachine_controller_test.go b/controllers/awsmachine_controller_test.go index 15270f7dc7..f6bc93de85 100644 --- a/controllers/awsmachine_controller_test.go +++ b/controllers/awsmachine_controller_test.go @@ -31,7 +31,6 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/record" - "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" @@ -96,6 +95,17 @@ func TestAWSMachineReconcilerIntegrationTests(t *testing.T) { } g.Expect(testEnv.Create(ctx, secret)).To(Succeed()) + kubeconfig := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster-kubeconfig", + Namespace: ns.Name, + }, + Data: map[string][]byte{ + "value": []byte(fakeKubeconfig), + }, + } + g.Expect(testEnv.Create(ctx, kubeconfig)).To(Succeed()) + setup(t, g) awsMachine := getAWSMachine() awsMachine.Namespace = ns.Name @@ -103,7 +113,7 @@ func TestAWSMachineReconcilerIntegrationTests(t *testing.T) { defer teardown(g) defer t.Cleanup(func() { - g.Expect(testEnv.Cleanup(ctx, awsMachine, ns, secret)).To(Succeed()) + g.Expect(testEnv.Cleanup(ctx, awsMachine, ns, secret, kubeconfig)).To(Succeed()) }) cs, err := getClusterScope(infrav1.AWSCluster{ObjectMeta: metav1.ObjectMeta{Name: "test"}, Spec: infrav1.AWSClusterSpec{NetworkSpec: infrav1.NetworkSpec{Subnets: []infrav1.SubnetSpec{ @@ -113,7 +123,16 @@ func TestAWSMachineReconcilerIntegrationTests(t *testing.T) { }}, }}}) g.Expect(err).To(BeNil()) - cs.Cluster = &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "test-cluster"}} + cs.Cluster = &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: ns.Name, + }} + cs.AWSCluster.ObjectMeta = metav1.ObjectMeta{ + Annotations: map[string]string{ + scope.KubeconfigReadyAnnotation: "true", // skip call to workload cluster to prove working control plane node + }, + } cs.AWSCluster.Status.Network.APIServerELB.DNSName = DNSName cs.AWSCluster.Spec.ControlPlaneLoadBalancer = &infrav1.AWSLoadBalancerSpec{ LoadBalancerType: infrav1.LoadBalancerTypeClassic, @@ -411,7 +430,8 @@ func getMachineScope(cs *scope.ClusterScope, awsMachine *infrav1.AWSMachine) (*s Client: testEnv, Cluster: &clusterv1.Cluster{ ObjectMeta: metav1.ObjectMeta{ - Name: "test", + Name: "test", + Namespace: awsMachine.Namespace, }, Status: clusterv1.ClusterStatus{ InfrastructureReady: true, @@ -423,7 +443,7 @@ func getMachineScope(cs *scope.ClusterScope, awsMachine *infrav1.AWSMachine) (*s }, Spec: clusterv1.MachineSpec{ Bootstrap: clusterv1.Bootstrap{ - DataSecretName: pointer.String("bootstrap-data"), + DataSecretName: aws.String("bootstrap-data"), }, }, }, diff --git a/controllers/awsmachine_controller_unit_test.go b/controllers/awsmachine_controller_unit_test.go index d0505df8b6..9f362f1516 100644 --- a/controllers/awsmachine_controller_unit_test.go +++ b/controllers/awsmachine_controller_unit_test.go @@ -105,13 +105,24 @@ func TestAWSMachineReconciler(t *testing.T) { }, } - client := fake.NewClientBuilder().WithObjects(awsMachine, secret, secretIgnition).WithStatusSubresource(awsMachine).Build() + kubeconfig := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster-kubeconfig", + Namespace: "namespace", + }, + Data: map[string][]byte{ + "value": []byte(fakeKubeconfig), + }, + } + + client := fake.NewClientBuilder().WithObjects(awsMachine, secret, secretIgnition, kubeconfig).WithStatusSubresource(awsMachine).Build() ms, err = scope.NewMachineScope( scope.MachineScopeParams{ Client: client, Cluster: &clusterv1.Cluster{ ObjectMeta: metav1.ObjectMeta{ - Name: "test", + Name: "test-cluster", + Namespace: "namespace", }, Status: clusterv1.ClusterStatus{ InfrastructureReady: true, @@ -124,7 +135,7 @@ func TestAWSMachineReconciler(t *testing.T) { Spec: clusterv1.MachineSpec{ ClusterName: "capi-test", Bootstrap: clusterv1.Bootstrap{ - DataSecretName: pointer.String("bootstrap-data"), + DataSecretName: aws.String("bootstrap-data"), }, }, }, @@ -136,13 +147,23 @@ func TestAWSMachineReconciler(t *testing.T) { cs, err = scope.NewClusterScope( scope.ClusterScopeParams{ - Client: fake.NewClientBuilder().WithObjects(awsMachine, secret).WithStatusSubresource(awsMachine).Build(), - Cluster: &clusterv1.Cluster{}, + Client: fake.NewClientBuilder().WithObjects(awsMachine, secret, kubeconfig).WithStatusSubresource(awsMachine).Build(), + Cluster: &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: "namespace", + }, + }, AWSCluster: &infrav1.AWSCluster{ObjectMeta: metav1.ObjectMeta{Name: "test"}}, }, ) g.Expect(err).To(BeNil()) cs.AWSCluster = &infrav1.AWSCluster{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + scope.KubeconfigReadyAnnotation: "true", // skip call to workload cluster to prove working control plane node + }, + }, Spec: infrav1.AWSClusterSpec{ ControlPlaneLoadBalancer: &infrav1.AWSLoadBalancerSpec{ LoadBalancerType: infrav1.LoadBalancerTypeClassic, @@ -153,6 +174,10 @@ func TestAWSMachineReconciler(t *testing.T) { scope.MachineScopeParams{ Client: client, Cluster: &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "namespace", + }, Status: clusterv1.ClusterStatus{ InfrastructureReady: true, }, @@ -390,7 +415,7 @@ func TestAWSMachineReconciler(t *testing.T) { g.Expect(ms.AWSMachine.Status.InstanceState).To(PointTo(Equal(infrav1.InstanceStatePending))) g.Expect(ms.AWSMachine.Status.Ready).To(BeFalse()) - g.Expect(buf.String()).To(ContainSubstring(("EC2 instance state changed"))) + g.Expect(buf.String()).To(ContainSubstring("EC2 instance state changed")) expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityWarning, infrav1.InstanceNotReadyReason}}) }) @@ -410,7 +435,7 @@ func TestAWSMachineReconciler(t *testing.T) { g.Expect(ms.AWSMachine.Status.InstanceState).To(PointTo(Equal(infrav1.InstanceStateRunning))) g.Expect(ms.AWSMachine.Status.Ready).To(BeTrue()) - g.Expect(buf.String()).To(ContainSubstring(("EC2 instance state changed"))) + g.Expect(buf.String()).To(ContainSubstring("EC2 instance state changed")) expectConditions(g, ms.AWSMachine, []conditionAssertion{ {conditionType: infrav1.InstanceReadyCondition, status: corev1.ConditionTrue}, }) @@ -431,7 +456,7 @@ func TestAWSMachineReconciler(t *testing.T) { secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return("test", int32(1), nil).Times(1) _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) g.Expect(ms.AWSMachine.Status.Ready).To(BeFalse()) - g.Expect(buf.String()).To(ContainSubstring(("EC2 instance state is undefined"))) + g.Expect(buf.String()).To(ContainSubstring("EC2 instance state is undefined")) g.Eventually(recorder.Events).Should(Receive(ContainSubstring("InstanceUnhandledState"))) g.Expect(ms.AWSMachine.Status.FailureMessage).To(PointTo(Equal("EC2 instance state \"NewAWSMachineState\" is undefined"))) expectConditions(g, ms.AWSMachine, []conditionAssertion{{conditionType: infrav1.InstanceReadyCondition, status: corev1.ConditionUnknown}}) @@ -572,7 +597,7 @@ func TestAWSMachineReconciler(t *testing.T) { _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) g.Expect(ms.AWSMachine.Status.InstanceState).To(PointTo(Equal(infrav1.InstanceStateStopping))) g.Expect(ms.AWSMachine.Status.Ready).To(BeFalse()) - g.Expect(buf.String()).To(ContainSubstring(("EC2 instance state changed"))) + g.Expect(buf.String()).To(ContainSubstring("EC2 instance state changed")) expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityError, infrav1.InstanceStoppedReason}}) }) @@ -588,7 +613,7 @@ func TestAWSMachineReconciler(t *testing.T) { _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) g.Expect(ms.AWSMachine.Status.InstanceState).To(PointTo(Equal(infrav1.InstanceStateStopped))) g.Expect(ms.AWSMachine.Status.Ready).To(BeFalse()) - g.Expect(buf.String()).To(ContainSubstring(("EC2 instance state changed"))) + g.Expect(buf.String()).To(ContainSubstring("EC2 instance state changed")) expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityError, infrav1.InstanceStoppedReason}}) }) @@ -604,7 +629,7 @@ func TestAWSMachineReconciler(t *testing.T) { _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) g.Expect(ms.AWSMachine.Status.InstanceState).To(PointTo(Equal(infrav1.InstanceStateRunning))) g.Expect(ms.AWSMachine.Status.Ready).To(BeTrue()) - g.Expect(buf.String()).To(ContainSubstring(("EC2 instance state changed"))) + g.Expect(buf.String()).To(ContainSubstring("EC2 instance state changed")) }) }) t.Run("deleting the AWSMachine manually", func(t *testing.T) { @@ -629,7 +654,7 @@ func TestAWSMachineReconciler(t *testing.T) { instance.State = infrav1.InstanceStateShuttingDown _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) g.Expect(ms.AWSMachine.Status.Ready).To(BeFalse()) - g.Expect(buf.String()).To(ContainSubstring(("Unexpected EC2 instance termination"))) + g.Expect(buf.String()).To(ContainSubstring("Unexpected EC2 instance termination")) g.Eventually(recorder.Events).Should(Receive(ContainSubstring("UnexpectedTermination"))) }) @@ -644,7 +669,7 @@ func TestAWSMachineReconciler(t *testing.T) { instance.State = infrav1.InstanceStateTerminated _, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs) g.Expect(ms.AWSMachine.Status.Ready).To(BeFalse()) - g.Expect(buf.String()).To(ContainSubstring(("Unexpected EC2 instance termination"))) + g.Expect(buf.String()).To(ContainSubstring("Unexpected EC2 instance termination")) g.Eventually(recorder.Events).Should(Receive(ContainSubstring("UnexpectedTermination"))) g.Expect(ms.AWSMachine.Status.FailureMessage).To(PointTo(Equal("EC2 instance state \"terminated\" is unexpected"))) expectConditions(g, ms.AWSMachine, []conditionAssertion{{infrav1.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityError, infrav1.InstanceTerminatedReason}}) @@ -2440,7 +2465,10 @@ func TestAWSMachineReconcilerReconcileDefaultsToLoadBalancerTypeClassic(t *testi ns := "testns" ownerCluster := &clusterv1.Cluster{ - ObjectMeta: metav1.ObjectMeta{Name: "capi-test-1", Namespace: ns}, + ObjectMeta: metav1.ObjectMeta{ + Name: "capi-test-1", + Namespace: ns, + }, Spec: clusterv1.ClusterSpec{ InfrastructureRef: &corev1.ObjectReference{ Kind: "AWSCluster", @@ -2458,6 +2486,9 @@ func TestAWSMachineReconcilerReconcileDefaultsToLoadBalancerTypeClassic(t *testi ObjectMeta: metav1.ObjectMeta{ Name: "capi-test-1", Namespace: ns, + Annotations: map[string]string{ + scope.KubeconfigReadyAnnotation: "true", // skip call to workload cluster to prove working control plane node + }, OwnerReferences: []metav1.OwnerReference{ { APIVersion: clusterv1.GroupVersion.String(), @@ -2567,7 +2598,17 @@ func TestAWSMachineReconcilerReconcileDefaultsToLoadBalancerTypeClassic(t *testi }, } - fakeClient := fake.NewClientBuilder().WithObjects(ownerCluster, awsCluster, ownerMachine, awsMachine, controllerIdentity, secret).WithStatusSubresource(awsCluster, awsMachine).Build() + kubeconfig := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "capi-test-1-kubeconfig", + Namespace: ns, + }, + Data: map[string][]byte{ + "value": []byte(fakeKubeconfig), + }, + } + + fakeClient := fake.NewClientBuilder().WithObjects(ownerCluster, awsCluster, ownerMachine, awsMachine, controllerIdentity, secret, kubeconfig).WithStatusSubresource(awsCluster, awsMachine).Build() recorder := record.NewFakeRecorder(10) reconciler := &AWSMachineReconciler{ diff --git a/controllers/helpers_test.go b/controllers/helpers_test.go index e998ff06e5..a2e927e88f 100644 --- a/controllers/helpers_test.go +++ b/controllers/helpers_test.go @@ -114,6 +114,39 @@ var ( Value: aws.String("apiserver"), }, } + fakeKubeconfig = `{ + "apiVersion": "v1", + "clusters": [ + { + "cluster": { + "certificate-authority-data": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMvakNDQWVhZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJek1ESXlNVEU0TURZeE5sb1hEVE16TURJeE9ERTRNRFl4Tmxvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBSnNqCk0yWHJJSXFUaE9ibDA0UG04TmZhRzBmd3NpOHZVUEZhaTJydGNFUCtpVVZPWDVKSXBxZzBjSklCQU4rUU9RZjAKbWR4bjhNNStzWXVuN3BDTThSWnp4Rzd1OWk4U3FtemhlSHZVU2t0QjNWSDNyY0lqUGlpdW5XQWZWR2VJaC9aZQpvck10RE9oVndwbXFiU2k5NlJaSFhHanV4aVBreXd4UUowTFlNNG02d0ZhNTFFMzJvSU5ZUXJ3ZnBJMWgvZXp4ClZhVkhPTU1aeng0Z1RyNWFWYVFrdk0raFRWYVpUT0p4QWdoek8wNmc1anRhTjlVYy94a3dBRUI3dFczWWtURHUKdjJKUVdiWHhucDRDZ2luWWlTbU51aDBwQlVLQTFhbDA2ejhkbnhnUEFRRkhVRUlqTy9CZ3IyTFB2SmpPNHlDYwpYbjd6ZUF4U1BCREo3ZGlQaTFzQ0F3RUFBYU5aTUZjd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0hRWURWUjBPQkJZRUZCaVVMditWa0NtRjd0b2lJSE00Z2tOWWQyZWdNQlVHQTFVZEVRUU8KTUF5Q0NtdDFZbVZ5Ym1WMFpYTXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBQ0J3RXNpR3l1OW96VHJXVUp4bAphMlJmenArQU5MWG1YcWJzb09YLzIvVGVoUlB1Y1VnQml4aXQvdDNud3E2MFNsOWJVaHd4S2VSd29FRjlFY2xGCmNuQUZySytjM1FDSVR6ek15aW9MZEdVYUNGdEVlWnVld3p4T2dpTm42SDR0b1lVR2pDTTlhdDY2OG53RHBQc3oKcWRFSjdndnhNcjB3cUhNT2Q5SE55eUlJUFdjZzJXRThjdzQrQzllTUNVWWI2Y2lHMHl6VmViSXExc2tNT0hFRQpFTGtCNytRd25Gcjd1Y1huZ29BUHpVMzg2VW9pWkpNbHFrN1djUEUzdk1SZmhOOW5hTmExZjk4T3NMRFgvdG1OCjBzaWpFZSt4RCtvR0s4ZzhsNXlLbUlad1BkNElCUWlqSjBGcFZaOFJmNE5hSEd1QTYxb2hzZkU5VTJkVVM1aEMKQ0F3PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==", + "server": "https://127.0.0.1:12345" + }, + "name": "capa" + } + ], + "contexts": [ + { + "context": { + "cluster": "capa", + "user": "capa" + }, + "name": "capa" + } + ], + "current-context": "capa", + "kind": "Config", + "preferences": {}, + "users": [ + { + "name": "capa", + "user": { + "client-certificate-data": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ0F3SUJBZ0lJWTFTa3dwcTFwQm93RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TXpBeU1qRXhPREEyTVRaYUZ3MHlOREF5TWpFeE9EQTJNVGhhTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXJBT24zTEdFSVE2aFB2b3YKbFlMNTlEWmFVTkhXT1hYdkNJWlIzVmRSSVFVSjJEQTFjaEpyazNEbVNZN016dmdYRjBRNktETFNZOE5veEVlSQpoZ0NOZlZtN1pnaVlUNk9zUXpTNzVIdDdONWpReHp5ZkdsTFZOdjFRenM1M01DTUUrMjI4czRPdzdWUkpySVJNClErRzl0L1dveG8rNjhlRTZOMmlZSzdJUE9peU85VzFVQ3FPc285dVM1dHBzeWtZYkRQYlI4aG1NZGdRWFpLZUQKVzAzRHRuekRKOFVjN3crQXRTT0pPSHRMbmlONG5WelJ6cUdqWHlDZXQ5RGhFWHQyNkFKejJMRzZXbDEwZElYdApBTEh5Y1BkdWtGUXBxT0czQjU0VXVXbWJDcTlGZ0pKQ0cyT1dsL2NpdlhoUWw2UEpRR1prK1RRVkl0eS95OWhNCkgxai9QUUlEQVFBQm8xWXdWREFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RBWURWUjBUQVFIL0JBSXdBREFmQmdOVkhTTUVHREFXZ0JRWWxDNy9sWkFwaGU3YUlpQnpPSUpEV0hkbgpvREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBTlNPbHRpT2JFb2lKTFk5c1hvOHZZT05HYUYvdFNiV1BOU1RnCkEvTmJzUFAzSjJnVmpHa1pUL3d6TCtXd3FmL2FXa2xnQjZIL2pUTjBpbnp3UXZFQ2RhQ054U0gyUXZoTUx1Rm4KeVBHdHdOKzFFQ1VoSnMvcUJlM0F2elRkVlorbWw3SDEraW9oQ0k0T2JNakZJNkhiN0tEL1RTbXE5cEhhdGxFbApmeDRPamlSU0krU09uR2QwU01zZUNVS0loaUNzcVdHWU5wcnRuZDNBM0E5bmxiZVBVZ3BPN0tnMDR2SUZnenVuCmRDTzFpaytOcjhJR0JaM1RKTThGc09LZmlVMjNacGRMbU1TWmZXaUU5U1I1WmdUMVcySEpPZS9UWW04TzJIdU8KcTI5a2VrK3VBL3JYMml3MTh6d0JkbGlUZEtTVnN6V2pHYUUxY09laktJZTdlaWtKU1E9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==", + "client-key-data": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBckFPbjNMR0VJUTZoUHZvdmxZTDU5RFphVU5IV09YWHZDSVpSM1ZkUklRVUoyREExCmNoSnJrM0RtU1k3TXp2Z1hGMFE2S0RMU1k4Tm94RWVJaGdDTmZWbTdaZ2lZVDZPc1F6Uzc1SHQ3TjVqUXh6eWYKR2xMVk52MVF6czUzTUNNRSsyMjhzNE93N1ZSSnJJUk1RK0c5dC9Xb3hvKzY4ZUU2TjJpWUs3SVBPaXlPOVcxVQpDcU9zbzl1UzV0cHN5a1liRFBiUjhobU1kZ1FYWktlRFcwM0R0bnpESjhVYzd3K0F0U09KT0h0TG5pTjRuVnpSCnpxR2pYeUNldDlEaEVYdDI2QUp6MkxHNldsMTBkSVh0QUxIeWNQZHVrRlFwcU9HM0I1NFV1V21iQ3E5RmdKSkMKRzJPV2wvY2l2WGhRbDZQSlFHWmsrVFFWSXR5L3k5aE1IMWovUFFJREFRQUJBb0lCQUZDY3Nma3FNK3F1Q1lFVApER083NnRtNDh5QjNCamNOZnNUMjlieU9KQlllWDF1eVZBbXVlajJ4cGlxSXdwQ29FV0kwOWpCS2lQQjU3a28xCjM5UTB4Qm9maUVRcitQMHhqaFE4eldVcFBUaHo1RHZLdkNGQSttejN6L3ZySkU0cHl6YnRXWWFqUzdJZFV4MkgKTHBxTld6U3Y2cldMUENnSXpoaVRhdVRuWUYxNUtaZFhNY2FHRzg3Sk5OTW5hQnNWY2pMOWhvS3JhZjltK3UwYgpPL2VBZmJXY3F1b1daUzVuQll1THdjWVBxcDVaQkhWSFB0OXFjczZnSGMwYitTRlNrL1ZmdGkzemw2OTVNMWNvCkR6NmxnamlXRFEzOUNPVjVDRTZYSGxoMnlIU3UxeFp2ZmZBV2dDSGxMZmtEaGJvYm44VVNwVlZPcWxiZDg5M1kKQVF1VWJjRUNnWUVBelljUm9YUUd3enpjZkxPTjNLbk9LVU1xNFpWbFk0aElTdjdma3lBaHZxZVRPdkNsdENJZwpXOGhjRGFWSjZOZWhhdzRBUFIramFEQ2VXVnI2QmpSNHpiVWdoS1Iyc0hDZ2ZTWGxiWHRxRG1JRmRBb3d5Q0NOCmkvMDM3R2laRlZ2TDFnSklBa3hOS1ZjRUNKQkJBeFdHWFJ4bWgwQVpGTS9qZlFRZ29zU1JHTkVDZ1lFQTFrR3oKSm1BWitxZzZWQllqOTk4Uzg5T21zNFdKNXl4c1RYV29oYldGWVVsbjM0ZG5zdGdWd3dYYmdGUGJyNGF3TEdYYwo3T295RU80RkVudUY3Umt0TVhydlg5L0p6SjE5Qnl5b1ZIUjJYb1RSMzlGTDExYW13V1VLaVlJS2xncVU4L0FZCkwwckErTjJWTEhJaXpOMklaL204ZzVnMjg4WndjTE5kSm5IbUdxMENnWUJRUFRWQzVUdG1xYklpOVM2alFaLzkKTi8zYnlDbW5MQ09kTTlneFpsQUdVUUlIOXIrYWplQTROUWJMUlFhUDR1OWdEajFGbjc1NkJORXRiWGxEUnVVSwoyblh4a3d2TFlvMGxqcy85YUR0Rmxqc3V6SE96RGhKMDNzMGdmTTJYS3hsdldjQ25OUWJDNXZmcUovZFpydU9nCkltQmMyVWR4ZXFBRHhNTEJDU0Rrb1FLQmdGNnVQOTNJZ0JKOWZ2RWpxNWRnMDc0K0hKK2VkbmRhOFMwMXZsZ0EKQkVZZXF6RmpZOWJybUlwTEwxbkJOUWFYRFlsQkptVG5oV0puM0lQelpCYUhscW9UK200eXRibWZLdDRkeFBFMQpXZzJnd2lJWEdsMjVwQTA0ZW5TVHE5dnNKekM1TytiQ01RNkkxT0FFUEE2dUl4WlhqUS9XRndxWStaMUVGZmprClFsd3RBb0dBU3FnNGNEd2Q4d2QwOFd2aEtaYkNoeXh4cVYvczdkT2YrZWhFaWtqdGlMc0pMc3IxdUEzZ0oxQS8KY1p5QWZVaURVZlE1cHVUQWVzUXo3b0VsYU9YQjlTRTdCQlhQZmswdnJBOVRyRmJ1UytwVjlkUjc3WkhMMGlnUgoxQXg1UE1YLzdGY1ZPRWZselR5V2U2TFkvYVU5MFJkVEtGQ1NKa0VzaTZPNzl4eUVnbDQ9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==" + } + } + ] +}` ) func expectAWSClusterConditions(g *WithT, m *infrav1.AWSCluster, expected []conditionAssertion) { diff --git a/controlplane/eks/api/v1beta1/conversion.go b/controlplane/eks/api/v1beta1/conversion.go index 57284afd25..1e4114fc1d 100644 --- a/controlplane/eks/api/v1beta1/conversion.go +++ b/controlplane/eks/api/v1beta1/conversion.go @@ -116,3 +116,15 @@ func Convert_v1beta2_VpcCni_To_v1beta1_VpcCni(in *ekscontrolplanev1.VpcCni, out func Convert_v1beta2_AWSManagedControlPlaneSpec_To_v1beta1_AWSManagedControlPlaneSpec(in *ekscontrolplanev1.AWSManagedControlPlaneSpec, out *AWSManagedControlPlaneSpec, scope apiconversion.Scope) error { return autoConvert_v1beta2_AWSManagedControlPlaneSpec_To_v1beta1_AWSManagedControlPlaneSpec(in, out, scope) } + +func Convert_v1beta1_OIDCProviderStatus_To_v1beta2_OIDCProviderStatus(in *OIDCProviderStatus, out *infrav1beta2.OIDCProviderStatus, _ apiconversion.Scope) error { + out.ARN = in.ARN + out.TrustPolicy = in.TrustPolicy + return nil +} + +func Convert_v1beta2_OIDCProviderStatus_To_v1beta1_OIDCProviderStatus(in *infrav1beta2.OIDCProviderStatus, out *OIDCProviderStatus, scope apiconversion.Scope) error { + out.ARN = in.ARN + out.TrustPolicy = in.TrustPolicy + return nil +} diff --git a/controlplane/eks/api/v1beta1/zz_generated.conversion.go b/controlplane/eks/api/v1beta1/zz_generated.conversion.go index ecc37543d6..ba71b04ae2 100644 --- a/controlplane/eks/api/v1beta1/zz_generated.conversion.go +++ b/controlplane/eks/api/v1beta1/zz_generated.conversion.go @@ -180,16 +180,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*OIDCProviderStatus)(nil), (*v1beta2.OIDCProviderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_OIDCProviderStatus_To_v1beta2_OIDCProviderStatus(a.(*OIDCProviderStatus), b.(*v1beta2.OIDCProviderStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*v1beta2.OIDCProviderStatus)(nil), (*OIDCProviderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta2_OIDCProviderStatus_To_v1beta1_OIDCProviderStatus(a.(*v1beta2.OIDCProviderStatus), b.(*OIDCProviderStatus), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*RoleMapping)(nil), (*v1beta2.RoleMapping)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_RoleMapping_To_v1beta2_RoleMapping(a.(*RoleMapping), b.(*v1beta2.RoleMapping), scope) }); err != nil { @@ -235,6 +225,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*OIDCProviderStatus)(nil), (*apiv1beta2.OIDCProviderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_OIDCProviderStatus_To_v1beta2_OIDCProviderStatus(a.(*OIDCProviderStatus), b.(*apiv1beta2.OIDCProviderStatus), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta2.AWSManagedControlPlaneSpec)(nil), (*AWSManagedControlPlaneSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta2_AWSManagedControlPlaneSpec_To_v1beta1_AWSManagedControlPlaneSpec(a.(*v1beta2.AWSManagedControlPlaneSpec), b.(*AWSManagedControlPlaneSpec), scope) }); err != nil { @@ -245,6 +240,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*apiv1beta2.OIDCProviderStatus)(nil), (*OIDCProviderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta2_OIDCProviderStatus_To_v1beta1_OIDCProviderStatus(a.(*apiv1beta2.OIDCProviderStatus), b.(*OIDCProviderStatus), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta2.VpcCni)(nil), (*VpcCni)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta2_VpcCni_To_v1beta1_VpcCni(a.(*v1beta2.VpcCni), b.(*VpcCni), scope) }); err != nil { @@ -729,28 +729,6 @@ func Convert_v1beta2_OIDCIdentityProviderConfig_To_v1beta1_OIDCIdentityProviderC return autoConvert_v1beta2_OIDCIdentityProviderConfig_To_v1beta1_OIDCIdentityProviderConfig(in, out, s) } -func autoConvert_v1beta1_OIDCProviderStatus_To_v1beta2_OIDCProviderStatus(in *OIDCProviderStatus, out *v1beta2.OIDCProviderStatus, s conversion.Scope) error { - out.ARN = in.ARN - out.TrustPolicy = in.TrustPolicy - return nil -} - -// Convert_v1beta1_OIDCProviderStatus_To_v1beta2_OIDCProviderStatus is an autogenerated conversion function. -func Convert_v1beta1_OIDCProviderStatus_To_v1beta2_OIDCProviderStatus(in *OIDCProviderStatus, out *v1beta2.OIDCProviderStatus, s conversion.Scope) error { - return autoConvert_v1beta1_OIDCProviderStatus_To_v1beta2_OIDCProviderStatus(in, out, s) -} - -func autoConvert_v1beta2_OIDCProviderStatus_To_v1beta1_OIDCProviderStatus(in *v1beta2.OIDCProviderStatus, out *OIDCProviderStatus, s conversion.Scope) error { - out.ARN = in.ARN - out.TrustPolicy = in.TrustPolicy - return nil -} - -// Convert_v1beta2_OIDCProviderStatus_To_v1beta1_OIDCProviderStatus is an autogenerated conversion function. -func Convert_v1beta2_OIDCProviderStatus_To_v1beta1_OIDCProviderStatus(in *v1beta2.OIDCProviderStatus, out *OIDCProviderStatus, s conversion.Scope) error { - return autoConvert_v1beta2_OIDCProviderStatus_To_v1beta1_OIDCProviderStatus(in, out, s) -} - func autoConvert_v1beta1_RoleMapping_To_v1beta2_RoleMapping(in *RoleMapping, out *v1beta2.RoleMapping, s conversion.Scope) error { out.RoleARN = in.RoleARN if err := Convert_v1beta1_KubernetesMapping_To_v1beta2_KubernetesMapping(&in.KubernetesMapping, &out.KubernetesMapping, s); err != nil { diff --git a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go index 3ca8ded16f..0fbb0570eb 100644 --- a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go +++ b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go @@ -221,14 +221,6 @@ type EncryptionConfig struct { Resources []*string `json:"resources,omitempty"` } -// OIDCProviderStatus holds the status of the AWS OIDC identity provider. -type OIDCProviderStatus struct { - // ARN holds the ARN of the provider - ARN string `json:"arn,omitempty"` - // TrustPolicy contains the boilerplate IAM trust policy to use for IRSA - TrustPolicy string `json:"trustPolicy,omitempty"` -} - type IdentityProviderStatus struct { // ARN holds the ARN of associated identity provider ARN string `json:"arn,omitempty"` @@ -250,7 +242,7 @@ type AWSManagedControlPlaneStatus struct { Bastion *infrav1.Instance `json:"bastion,omitempty"` // OIDCProvider holds the status of the identity provider for this cluster // +optional - OIDCProvider OIDCProviderStatus `json:"oidcProvider,omitempty"` + OIDCProvider infrav1.OIDCProviderStatus `json:"oidcProvider,omitempty"` // ExternalManagedControlPlane indicates to cluster-api that the control plane // is managed by an external service such as AKS, EKS, GKE, etc. // +kubebuilder:default=true diff --git a/controlplane/eks/api/v1beta2/zz_generated.deepcopy.go b/controlplane/eks/api/v1beta2/zz_generated.deepcopy.go index 456c99394c..fa8db74002 100644 --- a/controlplane/eks/api/v1beta2/zz_generated.deepcopy.go +++ b/controlplane/eks/api/v1beta2/zz_generated.deepcopy.go @@ -538,21 +538,6 @@ func (in *OIDCIdentityProviderConfig) DeepCopy() *OIDCIdentityProviderConfig { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OIDCProviderStatus) DeepCopyInto(out *OIDCProviderStatus) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OIDCProviderStatus. -func (in *OIDCProviderStatus) DeepCopy() *OIDCProviderStatus { - if in == nil { - return nil - } - out := new(OIDCProviderStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RoleMapping) DeepCopyInto(out *RoleMapping) { *out = *in diff --git a/go.mod b/go.mod index b51263995d..0c6b102a03 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/aws/aws-sdk-go v1.44.298 github.com/awslabs/goformation/v4 v4.19.5 github.com/blang/semver v3.5.1+incompatible + github.com/cert-manager/cert-manager v1.8.2 github.com/coreos/ignition v0.35.0 github.com/coreos/ignition/v2 v2.16.2 github.com/go-logr/logr v1.2.4 @@ -139,7 +140,6 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/onsi/ginkgo v1.16.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect github.com/pelletier/go-toml v1.9.5 // indirect @@ -187,6 +187,7 @@ require ( k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect k8s.io/kubectl v0.27.3 // indirect k8s.io/metrics v0.27.3 // indirect + sigs.k8s.io/gateway-api v0.4.1 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kind v0.20.0 // indirect sigs.k8s.io/kustomize/kustomize/v5 v5.0.1 // indirect diff --git a/go.sum b/go.sum index f139ee5b5c..61fe89dfc1 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,9 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -37,8 +40,19 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= @@ -56,9 +70,14 @@ github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= +github.com/ahmetb/gen-crd-api-reference-docs v0.3.0/go.mod h1:TdjdkYhlOifCQWPs1UdTma97kQQMozf5h26hTuG70u8= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -76,6 +95,7 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/aws/amazon-vpc-cni-k8s v1.15.0 h1:de/KJJ93G2TUpnNlJowsNPE/uDfmk7LKMLAc//ZKqdg= @@ -96,6 +116,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -104,6 +125,8 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2 github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cert-manager/cert-manager v1.8.2 h1:cqPar+74IDp5dQxT9Icp1eThfca19Ml3RfNh8pDY7dI= +github.com/cert-manager/cert-manager v1.8.2/go.mod h1:95Ds29nFWH6YqEgLiQ9WTtsDnTcxrkUPRNfYaKVOzeM= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -120,6 +143,7 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/coredns/caddy v1.1.0 h1:ezvsPrT/tA/7pYDBZxu0cT0VmWk75AfIaf6GSYCNMf0= github.com/coredns/caddy v1.1.0/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4= github.com/coredns/corefile-migration v1.0.21 h1:W/DCETrHDiFo0Wj03EyMkaQ9fwsmSgqTCQDHpceaSsE= @@ -127,19 +151,24 @@ github.com/coredns/corefile-migration v1.0.21/go.mod h1:XnhgULOEouimnzgn0t4WPuFD github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-json v0.0.0-20230131223807-18775e0fb4fb h1:rmqyI19j3Z/74bIRhuC59RB442rXUazKNueVpfJPxg4= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/ignition v0.35.0 h1:UFodoYq1mOPrbEjtxIsZbThcDyQwAI1owczRDqWmKkQ= github.com/coreos/ignition v0.35.0/go.mod h1:WJQapxzEn9DE0ryxsGvm8QnBajm/XsS/PkrDqSpz+bA= github.com/coreos/ignition/v2 v2.16.2 h1:wPpxTovdzCLJISYmNiM5Cpw4qCPc3/P2ibruPyS46eA= github.com/coreos/ignition/v2 v2.16.2/go.mod h1:Y1BKC60VSNgA5oWNoLIHXigpFX1FFn4CVeimmsI+Bhg= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/vcontext v0.0.0-20230201181013-d72178a18687 h1:uSmlDgJGbUB0bwQBcZomBTottKwEDF5fF8UjSwKSzWM= github.com/coreos/vcontext v0.0.0-20230201181013-d72178a18687/go.mod h1:Salmysdw7DAVuobBW/LwsKKgpyCPHUhjyJoMJD+ZJiI= @@ -149,6 +178,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHH github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -168,6 +198,11 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/drone/envsubst/v2 v2.0.0-20210730161058-179042472c46 h1:7QPwrLT79GlD5sizHf27aoY2RTvw62mO6x7mxkScNk0= github.com/drone/envsubst/v2 v2.0.0-20210730161058-179042472c46/go.mod h1:esf2rsHFNlZlxsqsZDojNBcnNs5REqIvRrWRHqX0vEU= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful/v3 v3.10.2 h1:hIovbnmBTLjHXkqEBUz3HGpXZdM7ZrE9fJIZIqlJLqE= github.com/emicklei/go-restful/v3 v3.10.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -175,8 +210,12 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= @@ -186,10 +225,13 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZM github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -209,21 +251,37 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gobuffalo/flect v0.2.3/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc= github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -241,6 +299,7 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -254,6 +313,7 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -271,6 +331,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= @@ -295,8 +356,10 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v48 v48.2.0 h1:68puzySE6WqUY9KWmpOsDEQfDZsso98rT6pZcz9HqcE= @@ -306,6 +369,7 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17 github.com/google/goexpect v0.0.0-20210430020637-ab937bf7fd6f h1:7MmqygqdeJtziBUpm4Z9ThROFZUaVGaePMfcDnluf1E= github.com/google/goexpect v0.0.0-20210430020637-ab937bf7fd6f/go.mod h1:n1ej5+FqyEytMt/mugVDZLIiqTMO+vsrgY+kM6ohzN0= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4= @@ -323,6 +387,8 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -331,22 +397,30 @@ github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 h1:SJ+NtwL6QaZ21U+ github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2/go.mod h1:Tv1PlzqC9t8wNnpPdctvtSUOPUUg4SHeE6vR1Ir2hmg= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc h1:f8eY6cV/x1x+HLjOp4r72s/31/V2aTUtg5oKRRPf8/Q= github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= @@ -363,6 +437,7 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= @@ -375,6 +450,7 @@ github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= @@ -446,6 +522,7 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -468,6 +545,7 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -482,16 +560,23 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9 github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= @@ -499,10 +584,12 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/microcosm-cc/bluemonday v1.0.18 h1:6HcxvXDAi3ARt3slx6nTesbvorIc3QeTzBNRvWktHBo= @@ -521,6 +608,7 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -528,6 +616,7 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -540,6 +629,7 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -551,21 +641,27 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.12.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= @@ -578,6 +674,7 @@ github.com/openshift-online/ocm-sdk-go v0.1.374 h1:9xyBKzyepFn9oaA10nhn67NodNFBL github.com/openshift-online/ocm-sdk-go v0.1.374/go.mod h1:KYOw8kAKAHyPrJcQoVR82CneQ4ofC02Na4cXXaTq4Nw= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= @@ -589,10 +686,12 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -619,6 +718,7 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= @@ -661,24 +761,32 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace h1:9PNP1jnUjRhfmGMlkXHjYPishpcw4jpSt/V/xYY3FMA= github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= @@ -703,7 +811,9 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI= @@ -726,12 +836,19 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/ziutek/telnet v0.0.0-20180329124119-c3b780dc415b/go.mod h1:IZpXDfkJ6tWD3PhBK5YzgQT+xJWh7OsdwiG8hA2MkO4= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= @@ -742,6 +859,7 @@ go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -754,6 +872,8 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -762,11 +882,14 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -799,6 +922,7 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -807,6 +931,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -832,6 +957,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -849,11 +975,16 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -875,6 +1006,9 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= @@ -906,9 +1040,11 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -930,10 +1066,12 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -942,10 +1080,16 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -967,6 +1111,7 @@ golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -984,9 +1129,12 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1002,7 +1150,9 @@ golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1010,6 +1160,7 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1031,6 +1182,7 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -1048,6 +1200,7 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -1059,6 +1212,7 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc= gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -1080,6 +1234,9 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1119,12 +1276,20 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM= google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= @@ -1148,6 +1313,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ= google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= @@ -1170,18 +1337,23 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= @@ -1200,6 +1372,8 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1208,45 +1382,76 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.21.3/go.mod h1:hUgeYHUbBp23Ue4qdX9tR8/ANi/g3ehylAqDn9NWVOg= +k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY= k8s.io/api v0.27.3 h1:yR6oQXXnUEBWEWcvPWS0jQL575KoAboQPfJAuKNrw5Y= k8s.io/api v0.27.3/go.mod h1:C4BNvZnQOF7JA/0Xed2S+aUyJSfTGkGFxLXz9MnpIpg= +k8s.io/apiextensions-apiserver v0.21.3/go.mod h1:kl6dap3Gd45+21Jnh6utCx8Z2xxLm8LGDkprcd+KbsE= k8s.io/apiextensions-apiserver v0.27.3 h1:xAwC1iYabi+TDfpRhxh4Eapl14Hs2OftM2DN5MpgKX4= k8s.io/apiextensions-apiserver v0.27.3/go.mod h1:BH3wJ5NsB9XE1w+R6SSVpKmYNyIiyIz9xAmBl8Mb+84= +k8s.io/apimachinery v0.21.3/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI= +k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= k8s.io/apimachinery v0.27.3 h1:Ubye8oBufD04l9QnNtW05idcOe9Z3GQN8+7PqmuVcUM= k8s.io/apimachinery v0.27.3/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= +k8s.io/apiserver v0.21.3/go.mod h1:eDPWlZG6/cCCMj/JBcEpDoK+I+6i3r9GsChYBHSbAzU= k8s.io/apiserver v0.27.3 h1:AxLvq9JYtveYWK+D/Dz/uoPCfz8JC9asR5z7+I/bbQ4= k8s.io/apiserver v0.27.3/go.mod h1:Y61+EaBMVWUBJtxD5//cZ48cHZbQD+yIyV/4iEBhhNA= k8s.io/cli-runtime v0.27.3 h1:h592I+2eJfXj/4jVYM+tu9Rv8FEc/dyCoD80UJlMW2Y= k8s.io/cli-runtime v0.27.3/go.mod h1:LzXud3vFFuDFXn2LIrWnscPgUiEj7gQQcYZE2UPn9Kw= +k8s.io/client-go v0.21.3/go.mod h1:+VPhCgTsaFmGILxR/7E1N0S+ryO010QBeNCv5JwRGYU= +k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk= k8s.io/client-go v0.27.3 h1:7dnEGHZEJld3lYwxvLl7WoehK6lAq7GvgjxpA3nv1E8= k8s.io/client-go v0.27.3/go.mod h1:2MBEKuTo6V1lbKy3z1euEGnhPfGZLKTS9tiJ2xodM48= k8s.io/cluster-bootstrap v0.27.2 h1:OL3onrOwrUD7NQxBUqQwTl1Uu2GQKCkw9BMHpc4PbiA= k8s.io/cluster-bootstrap v0.27.2/go.mod h1:b++PF0mjUOiTKdPQFlDw7p4V2VquANZ8SfhAwzxZJFM= +k8s.io/code-generator v0.21.3/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo= +k8s.io/code-generator v0.22.0/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= +k8s.io/component-base v0.21.3/go.mod h1:kkuhtfEHeZM6LkX0saqSK8PbdO7A0HigUngmhhrwfGQ= k8s.io/component-base v0.27.3 h1:g078YmdcdTfrCE4fFobt7qmVXwS8J/3cI1XxRi/2+6k= k8s.io/component-base v0.27.3/go.mod h1:JNiKYcGImpQ44iwSYs6dysxzR9SxIIgQalk4HaCNVUY= k8s.io/component-helpers v0.27.3 h1:oK7+AlwBKsSUIIRC5Vv8/4HEtmgzXNQD+zLbsOUwVso= k8s.io/component-helpers v0.27.3/go.mod h1:uxhXqoWHh4eBVcPj+LKWjtQq0V/vP5ihn4xmf5xNZso= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20201203183100-97869a43a9d9/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.10.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= k8s.io/kubectl v0.27.3 h1:HyC4o+8rCYheGDWrkcOQHGwDmyLKR5bxXFgpvF82BOw= k8s.io/kubectl v0.27.3/go.mod h1:g9OQNCC2zxT+LT3FS09ZYqnDhlvsKAfFq76oyarBcq4= k8s.io/metrics v0.27.3 h1:pBVKgQjfui8xzfTidIxiOmLHwcCk3KbeuWowo/Oh0t0= k8s.io/metrics v0.27.3/go.mod h1:pXj63OTdOjpYgSc95p+88fB3t4krLybM7MOeqIksI6o= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.19/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/aws-iam-authenticator v0.6.12 h1:XlW7djx+FqvyIysdBN60AIKBvHMr+MWVT87U0cG+Qlk= sigs.k8s.io/aws-iam-authenticator v0.6.12/go.mod h1:uh/d/yhtzJXBdevntKgTKRaabgD2T/JI/YdV+9IaycE= sigs.k8s.io/cluster-api v1.5.2 h1:pCsyEHwTBb7n+U5Z2OA5STxdJ1EuSpJv8FLBx4lii3s= sigs.k8s.io/cluster-api v1.5.2/go.mod h1:EGJUNpFWi7dF426tO8MG/jE+w7T0UO5KyMnOwQ5riUY= sigs.k8s.io/cluster-api/test v1.5.2 h1:Hlorgn4ebGdSxP4IZAT7eMweAowh4n0/vdhc4HAPNF8= sigs.k8s.io/cluster-api/test v1.5.2/go.mod h1:mFlsY1y0lApBgQyXbmVprdzCK+9MQNp1C38K+aZdn5A= +sigs.k8s.io/controller-runtime v0.9.6/go.mod h1:q6PpkM5vqQubEKUKOM6qr06oXGzOBcCby1DA9FbyZeA= sigs.k8s.io/controller-runtime v0.15.1 h1:9UvgKD4ZJGcj24vefUFgZFP3xej/3igL9BsOUTb/+4c= sigs.k8s.io/controller-runtime v0.15.1/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= +sigs.k8s.io/controller-tools v0.6.2/go.mod h1:oaeGpjXn6+ZSEIQkUe/+3I40PNiDYp9aeawbt3xTgJ8= +sigs.k8s.io/gateway-api v0.4.1 h1:Tof9/PNSZXyfDuTTe1XFvaTlvBRE6bKq1kmV6jj6rQE= +sigs.k8s.io/gateway-api v0.4.1/go.mod h1:r3eiNP+0el+NTLwaTfOrCNXy8TukC+dIM3ggc+fbNWk= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kind v0.20.0 h1:f0sc3v9mQbGnjBUaqSFST1dwIuiikKVGgoTwpoP33a8= @@ -1257,7 +1462,11 @@ sigs.k8s.io/kustomize/kustomize/v5 v5.0.1 h1:HWXbyKDNwGqol+s/sMNr/vnfNME/EoMdEra sigs.k8s.io/kustomize/kustomize/v5 v5.0.1/go.mod h1:Q8o+soB41Pn1y26eXzG9cniuECDpTJe2eKOA1fENCU8= sigs.k8s.io/kustomize/kyaml v0.14.2 h1:9WSwztbzwGszG1bZTziQUmVMrJccnyrLb5ZMKpJGvXw= sigs.k8s.io/kustomize/kyaml v0.14.2/go.mod h1:AN1/IpawKilWD7V+YvQwRGUvuUOOWpjsHu6uHwonSF4= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/main.go b/main.go index d61f1c37e8..db97f0177a 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,7 @@ import ( "os" "time" + v1certmanager "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/runtime" cgscheme "k8s.io/client-go/kubernetes/scheme" @@ -63,6 +64,8 @@ import ( "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/record" "sigs.k8s.io/cluster-api-provider-aws/v2/version" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + bootstrapv1beta1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" + controlplanev1beta1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" expclusterv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" ) @@ -84,6 +87,9 @@ func init() { _ = infrav1beta1.AddToScheme(scheme) _ = expinfrav1beta1.AddToScheme(scheme) _ = expinfrav1.AddToScheme(scheme) + _ = v1certmanager.AddToScheme(scheme) + _ = controlplanev1beta1.AddToScheme(scheme) + _ = bootstrapv1beta1.AddToScheme(scheme) // +kubebuilder:scaffold:scheme } diff --git a/pkg/cloud/scope/cluster.go b/pkg/cloud/scope/cluster.go index 1f939fdd0f..ae9b204440 100644 --- a/pkg/cloud/scope/cluster.go +++ b/pkg/cloud/scope/cluster.go @@ -31,10 +31,19 @@ import ( "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/logger" "sigs.k8s.io/cluster-api-provider-aws/v2/util/system" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + "sigs.k8s.io/cluster-api/controllers/remote" "sigs.k8s.io/cluster-api/util/conditions" "sigs.k8s.io/cluster-api/util/patch" ) +const ( + // KubeconfigReadyAnnotation is the key for the cluster object annotation + // which tracks if the Kubeconfig is available to communicate with the workload cluster + // See https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + // for annotation formatting rules. + KubeconfigReadyAnnotation = "sigs.k8s.io/cluster-api-provider-aws-kubeconfig-ready" +) + // ClusterScopeParams defines the input parameters used to create a new Scope. type ClusterScopeParams struct { Client client.Client @@ -64,7 +73,7 @@ func NewClusterScope(params ClusterScopeParams) (*ClusterScope, error) { clusterScope := &ClusterScope{ Logger: *params.Logger, - client: params.Client, + Client: params.Client, Cluster: params.Cluster, AWSCluster: params.AWSCluster, controllerName: params.ControllerName, @@ -91,7 +100,7 @@ func NewClusterScope(params ClusterScopeParams) (*ClusterScope, error) { // ClusterScope defines the basic context for an actuator to operate upon. type ClusterScope struct { logger.Logger - client client.Client + Client client.Client patchHelper *patch.Helper Cluster *clusterv1.Cluster @@ -104,6 +113,27 @@ type ClusterScope struct { tagUnmanagedNetworkResources bool } +// RemoteClient returns the Kubernetes client for connecting to the workload cluster. +func (s *ClusterScope) RemoteClient() (client.Client, error) { + clusterKey := client.ObjectKey{ + Name: s.Name(), + Namespace: s.Namespace(), + } + + restConfig, err := remote.RESTConfig(context.Background(), s.Cluster.Name, s.Client, clusterKey) + if err != nil { + return nil, fmt.Errorf("getting remote rest config for %s/%s: %w", s.Namespace(), s.Name(), err) + } + restConfig.Timeout = DefaultKubeClientTimeout + + return client.New(restConfig, client.Options{Scheme: scheme}) +} + +// ManagementClient returns the Kubernetes Client for the management cluster. +func (s *ClusterScope) ManagementClient() client.Client { + return s.Client +} + // Network returns the cluster network object. func (s *ClusterScope) Network() *infrav1.NetworkStatus { return &s.AWSCluster.Status.Network @@ -342,6 +372,20 @@ func (s *ClusterScope) TagUnmanagedNetworkResources() bool { return s.tagUnmanagedNetworkResources } +// S3Bucket returns the s3 bucket details. +func (s *ClusterScope) S3Bucket() *infrav1.S3Bucket { + return s.AWSCluster.Spec.S3Bucket +} + +// AssociateOIDCProvider returns if the cluster should have an OIDC Provider Associated. +func (s *ClusterScope) AssociateOIDCProvider() bool { + return s.AWSCluster.Spec.AssociateOIDCProvider +} + +func (s *ClusterScope) OIDCProviderStatus() *infrav1.OIDCProviderStatus { + return &s.AWSCluster.Status.OIDCProvider +} + // SetBastionInstance sets the bastion instance in the status of the cluster. func (s *ClusterScope) SetBastionInstance(instance *infrav1.Instance) { s.AWSCluster.Status.Bastion = instance diff --git a/pkg/cloud/scope/ec2.go b/pkg/cloud/scope/ec2.go index 2a4707cdc1..dfce10a187 100644 --- a/pkg/cloud/scope/ec2.go +++ b/pkg/cloud/scope/ec2.go @@ -17,6 +17,8 @@ limitations under the License. package scope import ( + "sigs.k8s.io/controller-runtime/pkg/client" + infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud" ) @@ -25,6 +27,12 @@ import ( type EC2Scope interface { cloud.ClusterScoper + // RemoteClient returns the Kubernetes Client for connecting to the workload cluster. + RemoteClient() (client.Client, error) + + // ManagementClient returns the Kubernetes Client for the management cluster. + ManagementClient() client.Client + // VPC returns the cluster VPC. VPC() *infrav1.VPCSpec @@ -40,6 +48,15 @@ type EC2Scope interface { // Bastion returns the bastion details for the cluster. Bastion() *infrav1.Bastion + // Bucket returns the s3 bucket details for the cluster. + Bucket() *infrav1.S3Bucket + + // AssociateOIDCProvider returns if the cluster should have an OIDC Provider Associated. + AssociateOIDCProvider() bool + + // OIDCProviderStatus returns the status of the OIDC provider associated to the cluster for IRSA + OIDCProviderStatus() *infrav1.OIDCProviderStatus + // SetBastionInstance sets the bastion instance in the status of the cluster. SetBastionInstance(instance *infrav1.Instance) diff --git a/pkg/cloud/scope/iam.go b/pkg/cloud/scope/iam.go new file mode 100644 index 0000000000..08caf127ac --- /dev/null +++ b/pkg/cloud/scope/iam.go @@ -0,0 +1,10 @@ +package scope + +import ( + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud" +) + +// IAMScope is the interface for the scope to be used with the IAM service. +type IAMScope interface { + cloud.ClusterScoper +} diff --git a/pkg/cloud/scope/machine.go b/pkg/cloud/scope/machine.go index 208895d8e3..b624306ec3 100644 --- a/pkg/cloud/scope/machine.go +++ b/pkg/cloud/scope/machine.go @@ -20,6 +20,7 @@ import ( "context" "encoding/base64" "fmt" + "path" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" @@ -254,6 +255,11 @@ func (m *MachineScope) SetAddresses(addrs []clusterv1.MachineAddress) { m.AWSMachine.Status.Addresses = addrs } +// GetBootstrapDataKey returns the bootstrap data key to use in the s3 bucket when storing ignition bootstrap data. +func (m *MachineScope) GetBootstrapDataKey() string { + return path.Join(m.Role(), m.Name()) +} + // GetBootstrapData returns the bootstrap data from the secret in the Machine's bootstrap.dataSecretName as base64. func (m *MachineScope) GetBootstrapData() (string, error) { value, err := m.GetRawBootstrapData() diff --git a/pkg/cloud/scope/managedcontrolplane.go b/pkg/cloud/scope/managedcontrolplane.go index 63b5f895d6..6c402a5902 100644 --- a/pkg/cloud/scope/managedcontrolplane.go +++ b/pkg/cloud/scope/managedcontrolplane.go @@ -19,11 +19,11 @@ package scope import ( "context" "fmt" - "time" amazoncni "github.com/aws/amazon-vpc-cni-k8s/pkg/apis/crd/v1alpha1" awsclient "github.com/aws/aws-sdk-go/aws/client" "github.com/pkg/errors" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -51,6 +51,7 @@ func init() { _ = appsv1.AddToScheme(scheme) _ = corev1.AddToScheme(scheme) _ = rbacv1.AddToScheme(scheme) + _ = admissionregistrationv1.AddToScheme(scheme) } // ManagedControlPlaneScopeParams defines the input parameters used to create a new Scope. @@ -141,11 +142,16 @@ func (s *ManagedControlPlaneScope) RemoteClient() (client.Client, error) { if err != nil { return nil, fmt.Errorf("getting remote rest config for %s/%s: %w", s.Namespace(), s.Name(), err) } - restConfig.Timeout = 1 * time.Minute + restConfig.Timeout = DefaultKubeClientTimeout return client.New(restConfig, client.Options{Scheme: scheme}) } +// ManagementClient returns the Kubernetes Client for the management cluster. +func (s *ManagedControlPlaneScope) ManagementClient() client.Client { + return s.Client +} + // Network returns the control plane network object. func (s *ManagedControlPlaneScope) Network() *infrav1.NetworkStatus { return &s.ControlPlane.Status.Network @@ -312,6 +318,16 @@ func (s *ManagedControlPlaneScope) TagUnmanagedNetworkResources() bool { return s.tagUnmanagedNetworkResources } +// Bucket returns the s3 bucket details. +func (s *ManagedControlPlaneScope) Bucket() *infrav1.S3Bucket { + return nil // no s3 bucket for managed clusters +} + +// AssociateOIDCProvider returns if the cluster should have an OIDC Provider Associated. +func (s *ManagedControlPlaneScope) AssociateOIDCProvider() bool { + return s.ControlPlane.Spec.AssociateOIDCProvider +} + // SetBastionInstance sets the bastion instance in the status of the cluster. func (s *ManagedControlPlaneScope) SetBastionInstance(instance *infrav1.Instance) { s.ControlPlane.Status.Bastion = instance @@ -403,6 +419,10 @@ func (s *ManagedControlPlaneScope) OIDCIdentityProviderConfig() *ekscontrolplane return s.ControlPlane.Spec.OIDCIdentityProviderConfig } +func (s *ManagedControlPlaneScope) OIDCProviderStatus() *infrav1.OIDCProviderStatus { + return &s.ControlPlane.Status.OIDCProvider +} + // ServiceCidrs returns the CIDR blocks used for services. func (s *ManagedControlPlaneScope) ServiceCidrs() *clusterv1.NetworkRanges { if s.Cluster.Spec.ClusterNetwork != nil { diff --git a/pkg/cloud/scope/shared.go b/pkg/cloud/scope/shared.go index 76e1ec91d8..879280f212 100644 --- a/pkg/cloud/scope/shared.go +++ b/pkg/cloud/scope/shared.go @@ -18,6 +18,7 @@ package scope import ( "fmt" + "time" "github.com/pkg/errors" @@ -34,6 +35,8 @@ var ( ErrLoggerRequired = errors.New("logger is required") // ErrNotPlaced is an error if there is no placement determined. ErrNotPlaced = errors.New("placement not determined") + + DefaultKubeClientTimeout = 1 * time.Minute ) type placementInput struct { diff --git a/pkg/cloud/services/eks/oidc.go b/pkg/cloud/services/eks/oidc.go index aa4ef6ec26..83893845ab 100644 --- a/pkg/cloud/services/eks/oidc.go +++ b/pkg/cloud/services/eks/oidc.go @@ -19,31 +19,23 @@ package eks import ( "context" "fmt" - "regexp" "strings" "github.com/aws/aws-sdk-go/service/eks" - "github.com/aws/aws-sdk-go/service/iam" + awsiam "github.com/aws/aws-sdk-go/service/iam" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/cluster-api-provider-aws/v2/cmd/clusterawsadm/converters" iamv1 "sigs.k8s.io/cluster-api-provider-aws/v2/iam/api/v1beta1" tagConverter "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/converters" + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/iam" "sigs.k8s.io/cluster-api/controllers/remote" ) -var ( - trustPolicyConfigMapName = "boilerplate-oidc-trust-policy" - trustPolicyConfigMapNamespace = metav1.NamespaceDefault - - whitespaceRe = regexp.MustCompile(`(?m)[\t\n]`) -) - func (s *Service) reconcileOIDCProvider(cluster *eks.Cluster) error { if !s.scope.ControlPlane.Spec.AssociateOIDCProvider || s.scope.ControlPlane.Status.OIDCProvider.ARN != "" { return nil @@ -72,12 +64,12 @@ func (s *Service) reconcileOIDCProvider(cluster *eks.Cluster) error { if err != nil { return errors.Wrap(err, "failed to parse IAM policy") } - s.scope.ControlPlane.Status.OIDCProvider.TrustPolicy = whitespaceRe.ReplaceAllString(policy, "") + s.scope.ControlPlane.Status.OIDCProvider.TrustPolicy = iam.WhitespaceRegex.ReplaceAllString(policy, "") if err := s.scope.PatchObject(); err != nil { return errors.Wrap(err, "failed to update control plane with OIDC provider ARN") } // tagging the OIDC provider with the same tags of cluster - inputForTags := iam.TagOpenIDConnectProviderInput{ + inputForTags := awsiam.TagOpenIDConnectProviderInput{ OpenIDConnectProviderArn: &s.scope.ControlPlane.Status.OIDCProvider.ARN, Tags: tagConverter.MapToIAMTags(tagConverter.MapPtrToMap(cluster.Tags)), } @@ -111,15 +103,15 @@ func (s *Service) reconcileTrustPolicy() error { } configMapRef := types.NamespacedName{ - Name: trustPolicyConfigMapName, - Namespace: trustPolicyConfigMapNamespace, + Name: iam.TrustPolicyConfigMapName, + Namespace: iam.TrustPolicyConfigMapNamespace, } trustPolicyConfigMap := &corev1.ConfigMap{} err = remoteClient.Get(ctx, configMapRef, trustPolicyConfigMap) if err != nil && !apierrors.IsNotFound(err) { - return fmt.Errorf("getting %s/%s config map: %w", trustPolicyConfigMapNamespace, trustPolicyConfigMapName, err) + return fmt.Errorf("getting %s/%s config map: %w", iam.TrustPolicyConfigMapNamespace, iam.TrustPolicyConfigMapName, err) } policy, err := converters.IAMPolicyDocumentToJSON(s.buildOIDCTrustPolicy()) @@ -128,17 +120,17 @@ func (s *Service) reconcileTrustPolicy() error { } trustPolicyConfigMap.Data = map[string]string{ - "trust-policy.json": policy, + iam.TrustPolicyJSON: policy, } if trustPolicyConfigMap.UID == "" { - trustPolicyConfigMap.Name = trustPolicyConfigMapName - trustPolicyConfigMap.Namespace = trustPolicyConfigMapNamespace - s.Debug("Creating new Trust Policy ConfigMap", "cluster", s.scope.Name(), "configmap", trustPolicyConfigMapName) + trustPolicyConfigMap.Name = iam.TrustPolicyConfigMapName + trustPolicyConfigMap.Namespace = iam.TrustPolicyConfigMapNamespace + s.Debug("Creating new Trust Policy ConfigMap", "cluster", s.scope.Name(), "configmap", iam.TrustPolicyConfigMapName) return remoteClient.Create(ctx, trustPolicyConfigMap) } - s.Debug("Updating existing Trust Policy ConfigMap", "cluster", s.scope.Name(), "configmap", trustPolicyConfigMapName) + s.Debug("Updating existing Trust Policy ConfigMap", "cluster", s.scope.Name(), "configmap", iam.TrustPolicyConfigMapName) return remoteClient.Update(ctx, trustPolicyConfigMap) } diff --git a/pkg/cloud/services/iam/iam.go b/pkg/cloud/services/iam/iam.go new file mode 100644 index 0000000000..bc3fd17079 --- /dev/null +++ b/pkg/cloud/services/iam/iam.go @@ -0,0 +1,133 @@ +package iam + +import ( + "context" + "errors" + "regexp" + + "github.com/aws/aws-sdk-go/service/iam/iamiface" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/scope" + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/s3" +) + +type Service struct { + scope scope.EC2Scope + IAMClient iamiface.IAMAPI +} + +// NewService returns a new service given the api clients. +func NewService(clusterScope scope.EC2Scope) *Service { + iamClient := scope.NewIAMClient(clusterScope, clusterScope, clusterScope, clusterScope.InfraCluster()) + + return &Service{ + scope: clusterScope, + IAMClient: iamClient, + } +} + +const ( + // TrustPolicyJSON is the data key for the configmap containing the oidc trust policy. + TrustPolicyJSON = "trust-policy.json" + + // PodIdentityWebhookCertificateFormat the format for the cert name used for the pod-identity-webhook. + PodIdentityWebhookCertificateFormat = "%s-pod-id-wh" + + // SelfsignedIssuerFormat format for the self signed issuer used for the cluster to make the pod-identity-webhook cert. + SelfsignedIssuerFormat = "%s-selfsigned-issuer" + + // S3HostFormat format for the host format for s3 for the oidc provider. + S3HostFormat = "s3.%s.amazonaws.com" + + // STSAWSAudience security token service url. + STSAWSAudience = "sts.amazonaws.com" + + // TrustPolicyConfigMapName name of the configmap that contains the trust policy template. + TrustPolicyConfigMapName = "boilerplate-oidc-trust-policy" + + // TrustPolicyConfigMapNamespace namespace the trust policy template is put into. + TrustPolicyConfigMapNamespace = metav1.NamespaceDefault +) + +var ( + WhitespaceRegex = regexp.MustCompile(`(?m)[\t\n]`) +) + +// ReconcileOIDCProvider replicates functionality already built into managed clusters by auto-deploying the +// modifying kube-apiserver args, deploying the pod identity webhook and setting/configuring an oidc provider +// for more details see: https://github.com/aws/amazon-eks-pod-identity-webhook/blob/master/SELF_HOSTED_SETUP.md +// 1. create a self signed issuer for the mutating webhook +// 2. add create a json patch for kube-apiserver and use capi config to add to the kubeadm.yml +// 3. create an oidc provider in aws which points to the s3 bucket +// 4. pause until kubeconfig and cluster acccess is ready +// 5. move openid config and jwks to the s3 bucket +// 6. add the pod identity webhook to the workload cluster +// 7. add the configmap to the workload cluster. +func (s *Service) ReconcileOIDCProvider(ctx context.Context) error { + if !s.scope.AssociateOIDCProvider() { + return nil + } + + log := s.scope.GetLogger() + log.Info("Associating OIDC Provider") + + if s.scope.Bucket() == nil { + return errors.New("s3 bucket configuration required to associate oidc provider") + } + + if err := s.reconcileSelfsignedIssuer(ctx); err != nil { + return err + } + + if err := s.reconcileKubeAPIParameters(ctx); err != nil { + return err + } + + if err := s.reconcileIdentityProvider(ctx); err != nil { + return err + } + + // the following can only run with a working workload cluster, return nil until then + _, ok := s.scope.InfraCluster().GetAnnotations()[scope.KubeconfigReadyAnnotation] + if !ok { + log.Info("Associating OIDC Provider paused, kubeconfig and workload cluster API access is not ready") + return nil + } + + log.Info("Associating OIDC Provider continuing, kubeconfig for the workload cluster is available") + if err := s.reconcileBucketContents(ctx); err != nil { + return err + } + + if err := s.reconcilePodIdentityWebhook(ctx); err != nil { + return err + } + + return s.reconcileTrustPolicyConfigMap(ctx) +} + +// DeleteOIDCProvider will delete the iam resources note that the bucket is cleaned up in the s3 service +// 1. delete oidc provider +// 2. delete mwh certificate +// 3. delete cert-manager issuer. +func (s *Service) DeleteOIDCProvider(ctx context.Context) error { + if !s.scope.AssociateOIDCProvider() { + return nil + } + + log := s.scope.GetLogger() + log.Info("Deleting OIDC Provider") + + if s.scope.Bucket() != nil { + if err := s.deleteBucketContents(s3.NewService(s.scope)); err != nil { + return err + } + } + + if err := deleteCertificatesAndIssuer(ctx, s.scope.Name(), s.scope.Namespace(), s.scope.ManagementClient()); err != nil { + return err + } + + return deleteOIDCProvider(s.scope.OIDCProviderStatus().ARN, s.IAMClient) +} diff --git a/pkg/cloud/services/iam/oidc.go b/pkg/cloud/services/iam/oidc.go new file mode 100644 index 0000000000..9dde8cf002 --- /dev/null +++ b/pkg/cloud/services/iam/oidc.go @@ -0,0 +1,375 @@ +package iam + +import ( + "bytes" + "context" + "crypto/sha1" + "crypto/tls" + "fmt" + "path" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/iam" + "github.com/aws/aws-sdk-go/service/iam/iamiface" + v1certmanager "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + v1certmanagermeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client" + + iamv1 "sigs.k8s.io/cluster-api-provider-aws/v2/iam/api/v1beta1" + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/scope" + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/s3" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + v1beta12 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" + "sigs.k8s.io/cluster-api/controllers/remote" + v1beta13 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" +) + +const ( + jwksKey = "/openid/v1/jwks" + opendIDConfigurationKey = "/.well-known/openid-configuration" +) + +func certificateSecret(ctx context.Context, name, namespace, issuer string, dnsNames []string, client client.Client) (*corev1.Secret, error) { + // check if the secret was already created + certSecret := &corev1.Secret{} + if err := client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, certSecret); err != nil && !apierrors.IsNotFound(err) { + return nil, err + } + + if certSecret.UID != "" { + return certSecret, nil + } + + // cert does not exist, create the request + cert := &v1certmanager.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + clusterv1.ProviderNameLabel: "infrastructure-aws", + }, + Name: name, + Namespace: namespace, + }, + Spec: v1certmanager.CertificateSpec{ + SecretName: name, + IsCA: true, + PrivateKey: &v1certmanager.CertificatePrivateKey{ + Algorithm: v1certmanager.RSAKeyAlgorithm, + Size: 2048, + }, + IssuerRef: v1certmanagermeta.ObjectReference{ + Kind: "Issuer", + Name: issuer, + }, + DNSNames: dnsNames, + }, + } + + // check if cert already exists + if err := client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, cert); err != nil && !apierrors.IsNotFound(err) { + return nil, err + } + + if cert.UID == "" { + if err := client.Create(ctx, cert); err != nil { + return nil, err + } + } + + // check if the secret was created by cert-manager, return all errors until cert manager is done + return certSecret, client.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, certSecret) +} + +func (s *Service) deleteBucketContents(s3 *s3.Service) error { + if err := s3.Delete("/" + path.Join(s.scope.Name(), jwksKey)); err != nil { + return err + } + + return s3.Delete("/" + path.Join(s.scope.Name(), opendIDConfigurationKey)) +} + +func deleteCertificatesAndIssuer(ctx context.Context, name, namespace string, client client.Client) error { + certs := []string{ + fmt.Sprintf(PodIdentityWebhookCertificateFormat, name), + } + + for _, c := range certs { + cert := &v1certmanager.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: c, + Namespace: namespace, + }, + } + if err := client.Delete(ctx, cert); err != nil && !apierrors.IsNotFound(err) { + return err + } + } + + if err := client.Delete(ctx, &v1certmanager.Issuer{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(SelfsignedIssuerFormat, name), + Namespace: namespace, + }, + }); err != nil && !apierrors.IsNotFound(err) { + return err + } + + return nil +} + +func (s *Service) reconcileBucketContents(ctx context.Context) error { + clusterKey := client.ObjectKey{ + Name: s.scope.Name(), + Namespace: s.scope.Namespace(), + } + + // get remote config from management cluster + remoteRestConfig, err := remote.RESTConfig(context.Background(), s.scope.Name(), s.scope.ManagementClient(), clusterKey) + if err != nil { + return fmt.Errorf("getting remote rest config for %s/%s: %w", s.scope.Namespace(), s.scope.Name(), err) + } + remoteRestConfig.Timeout = scope.DefaultKubeClientTimeout + + // make a client set for the workload cluster + clientSet, err := kubernetes.NewForConfig(remoteRestConfig) + if err != nil { + return err + } + + s3scope := s3.NewService(s.scope) + conf, err := get(ctx, clientSet, opendIDConfigurationKey) + if err != nil { + return err + } + + if _, err := s3scope.CreatePublic("/"+path.Join(s.scope.Name(), opendIDConfigurationKey), []byte(conf)); err != nil { + return err + } + + jwks, err := get(ctx, clientSet, jwksKey) + if err != nil { + return err + } + + if _, err := s3scope.CreatePublic("/"+path.Join(s.scope.Name(), jwksKey), []byte(jwks)); err != nil { + return err + } + + return nil +} + +func get(ctx context.Context, clientSet *kubernetes.Clientset, uri string) (ret string, err error) { + request := clientSet.RESTClient().Get().RequestURI(uri) + stream, err := request.Stream(ctx) + if err != nil { + return + } + defer func() { + err = stream.Close() + }() + + buf := new(bytes.Buffer) + _, err = buf.ReadFrom(stream) + if err != nil { + return + } + + if err = stream.Close(); err != nil { + return + } + + ret = buf.String() + return +} + +func buildOIDCTrustPolicy(arn string) iamv1.PolicyDocument { + conditionValue := arn[strings.Index(arn, "/")+1:] + ":sub" + + return iamv1.PolicyDocument{ + Version: "2012-10-17", + Statement: iamv1.Statements{ + iamv1.StatementEntry{ + Sid: "", + Effect: "Allow", + Principal: iamv1.Principals{ + iamv1.PrincipalFederated: iamv1.PrincipalID{arn}, + }, + Action: iamv1.Actions{"sts:AssumeRoleWithWebIdentity"}, + Condition: iamv1.Conditions{ + "ForAnyValue:StringLike": map[string][]string{ + conditionValue: {"system:serviceaccount:${SERVICE_ACCOUNT_NAMESPACE}:${SERVICE_ACCOUNT_NAME}"}, + }, + }, + }, + }, + } +} + +// FindAndVerifyOIDCProvider will try to find an OIDC provider. It will return an error if the found provider does not +// match the cluster spec. +func findAndVerifyOIDCProvider(issuerURL, thumbprint string, iamClient iamiface.IAMAPI) (string, error) { + output, err := iamClient.ListOpenIDConnectProviders(&iam.ListOpenIDConnectProvidersInput{}) + if err != nil { + return "", errors.Wrap(err, "error listing providers") + } + for _, r := range output.OpenIDConnectProviderList { + provider, err := iamClient.GetOpenIDConnectProvider(&iam.GetOpenIDConnectProviderInput{OpenIDConnectProviderArn: r.Arn}) + if err != nil { + return "", errors.Wrap(err, "error getting provider") + } + // URL should always contain `https`. + if "https://"+aws.StringValue(provider.Url) != issuerURL { + continue + } + if len(provider.ThumbprintList) != 1 || aws.StringValue(provider.ThumbprintList[0]) != thumbprint { + return "", errors.Wrap(err, "found provider with matching issuerURL but with non-matching thumbprint") + } + if len(provider.ClientIDList) != 1 || aws.StringValue(provider.ClientIDList[0]) != STSAWSAudience { + return "", errors.Wrap(err, "found provider with matching issuerURL but with non-matching clientID") + } + return aws.StringValue(r.Arn), nil + } + return "", nil +} + +func fetchRootCAThumbprint(url string, port int) (ret string, err error) { + // Parse cmdline arguments using flag package + conn, err := tls.Dial("tcp", fmt.Sprintf("%s:%d", url, port), &tls.Config{ + MinVersion: tls.VersionTLS12, + }) + if err != nil { + return + } + defer func() { + err = conn.Close() + }() + + // Get the ConnectionState struct as that's the one which gives us x509.Certificate struct + cert := conn.ConnectionState().PeerCertificates[0] + fingerprint := sha1.Sum(cert.Raw) //nolint:gosec // this is not used for real security + var buf bytes.Buffer + for _, f := range fingerprint { + if _, err = fmt.Fprintf(&buf, "%02X", f); err != nil { + return + } + } + ret = strings.ToLower(buf.String()) + return +} + +// DeleteOIDCProvider will delete an OIDC provider. +func deleteOIDCProvider(arn string, iamClient iamiface.IAMAPI) error { + if arn == "" { + return nil + } + + input := iam.DeleteOpenIDConnectProviderInput{ + OpenIDConnectProviderArn: aws.String(arn), + } + + _, err := iamClient.DeleteOpenIDConnectProvider(&input) + if err != nil { + return errors.Wrap(err, "error deleting provider") + } + return nil +} + +// reconcileKubeAPIParameters +// 1. find kubeadmcontrolplane +// 2. use name/namespace to pull kubeadmconfig +// 3. update files/params. +func (s *Service) reconcileKubeAPIParameters(ctx context.Context) error { + managementClient := s.scope.ManagementClient() + name := s.scope.Name() + namespace := s.scope.Namespace() + + s3Host := fmt.Sprintf(S3HostFormat, s.scope.Region()) + accountIssuer := "https://" + path.Join(s3Host, s.scope.Bucket().Name, s.scope.Name()) + + listOptions := []client.ListOption{ + client.InNamespace(namespace), + client.MatchingLabels(map[string]string{clusterv1.ClusterNameLabel: name}), + } + + controlPlanes := &v1beta13.KubeadmControlPlaneList{} + if err := managementClient.List(ctx, controlPlanes, listOptions...); err != nil { + return fmt.Errorf("failed to list kubeadm control planes for cluster %s/%s: %w", namespace, name, err) + } + + patchContent := `[ + { + "op": "add", + "path": "/spec/containers/0/command/1", + "value": "--api-audiences=https://kubernetes.default.svc.cluster.local" + }, + { + "op": "add", + "path": "/spec/containers/0/command/1", + "value": "--api-audiences=` + STSAWSAudience + `" + }, + { + "op": "add", + "path": "/spec/containers/0/command/1", + "value": "--service-account-issuer=` + accountIssuer + `" + }, + { + "op": "add", + "path": "/spec/containers/0/command/1", + "value": "--service-account-jwks-uri=` + accountIssuer + jwksKey + `" + } + ]` + + for i := range controlPlanes.Items { + // files have to be unique so rebuild and toss the ones we're going to add + var files []v1beta12.File + for _, file := range controlPlanes.Items[i].Spec.KubeadmConfigSpec.Files { + if file.Path != "/etc/kubernetes/patches/kube-apiserver0+json.json" { + files = append(files, file) + } else if file.Content == patchContent { + return nil // nothing to reconcile + } + } + + controlPlanes.Items[i].Spec.KubeadmConfigSpec.Files = append(files, + // command starts with 0 == kube-apiserver, json patch add will insert at the position and shift the array + v1beta12.File{ + Path: "/etc/kubernetes/patches/kube-apiserver0+json.json", + Content: patchContent, + }) + + // panic checks to be safe + if controlPlanes.Items[i].Spec.KubeadmConfigSpec.InitConfiguration == nil { + controlPlanes.Items[i].Spec.KubeadmConfigSpec.InitConfiguration = &v1beta12.InitConfiguration{ + Patches: &v1beta12.Patches{}, + } + } + + if controlPlanes.Items[i].Spec.KubeadmConfigSpec.InitConfiguration.Patches == nil { + controlPlanes.Items[i].Spec.KubeadmConfigSpec.InitConfiguration.Patches = &v1beta12.Patches{} + } + + // set the patch directory for kubeadmn init to apply before booting apiserver + controlPlanes.Items[i].Spec.KubeadmConfigSpec.InitConfiguration.Patches.Directory = "/etc/kubernetes/patches" + + if err := managementClient.Update(ctx, &controlPlanes.Items[i]); err != nil { + return err + } + } + + return nil +} diff --git a/pkg/cloud/services/iam/podidentitywebhook.go b/pkg/cloud/services/iam/podidentitywebhook.go new file mode 100644 index 0000000000..bc5299018c --- /dev/null +++ b/pkg/cloud/services/iam/podidentitywebhook.go @@ -0,0 +1,340 @@ +package iam + +import ( + "context" + "errors" + + v14 "k8s.io/api/admissionregistration/v1" + v13 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + v12 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/client" + + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" +) + +const ( + podIdentityWebhookName = "pod-identity-webhook" + podIdentityWebhookImage = "amazon/amazon-eks-pod-identity-webhook:v0.4.0" +) + +func reconcileServiceAccount(ctx context.Context, ns string, remoteClient client.Client) error { + check := &corev1.ServiceAccount{} + if err := remoteClient.Get(ctx, types.NamespacedName{ + Name: podIdentityWebhookName, + Namespace: ns, + }, check); err != nil && !apierrors.IsNotFound(err) { + return err + } + + if check.UID != "" { + return nil + } + + sa := &corev1.ServiceAccount{ + ObjectMeta: objectMeta(podIdentityWebhookName, ns), + } + + return remoteClient.Create(ctx, sa) +} + +func reconcileClusterRole(ctx context.Context, ns string, remoteClient client.Client) error { + check := &v12.ClusterRole{} + if err := remoteClient.Get(ctx, types.NamespacedName{ + Name: podIdentityWebhookName, + Namespace: ns, + }, check); err != nil && !apierrors.IsNotFound(err) { + return err + } + + if check.UID != "" { + return nil + } + + cr := &v12.ClusterRole{ + ObjectMeta: objectMeta(podIdentityWebhookName, ns), + Rules: []v12.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"create"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"get", "update", "patch"}, + ResourceNames: []string{podIdentityWebhookName}, + }, + { + APIGroups: []string{""}, + Resources: []string{"serviceaccounts"}, + Verbs: []string{"get", "watch", "list"}, + }, + { + APIGroups: []string{"certificates.k8s.io"}, + Resources: []string{"certificatesigningrequests"}, + Verbs: []string{"create", "get", "list", "watch"}, + }, + }, + } + + return remoteClient.Create(ctx, cr) +} + +func reconcileClusterRoleBinding(ctx context.Context, ns string, remoteClient client.Client) error { + check := &v12.ClusterRoleBinding{} + if err := remoteClient.Get(ctx, types.NamespacedName{ + Name: podIdentityWebhookName, + }, check); err != nil && !apierrors.IsNotFound(err) { + return err + } + + if check.UID != "" { + return nil + } + + crb := &v12.ClusterRoleBinding{ + ObjectMeta: objectMeta(podIdentityWebhookName, ""), + RoleRef: v12.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: podIdentityWebhookName, + }, + Subjects: []v12.Subject{ + { + Kind: "ServiceAccount", + Name: podIdentityWebhookName, + Namespace: ns, + }, + }, + } + + return remoteClient.Create(ctx, crb) +} + +func reconcileService(ctx context.Context, ns string, remoteClient client.Client) error { + check := &corev1.Service{} + if err := remoteClient.Get(ctx, types.NamespacedName{ + Name: podIdentityWebhookName, + Namespace: ns, + }, check); err != nil && !apierrors.IsNotFound(err) { + return err + } + + if check.UID != "" { + return nil + } + service := &corev1.Service{ + ObjectMeta: objectMeta(podIdentityWebhookName, ns), + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Port: 443, + TargetPort: intstr.FromInt(443), + }, + }, + Selector: map[string]string{ + "app": podIdentityWebhookName, + }, + }, + } + + return remoteClient.Create(ctx, service) +} + +func reconcileDeployment(ctx context.Context, ns string, secret *corev1.Secret, remoteClient client.Client) error { + check := &v13.Deployment{} + if err := remoteClient.Get(ctx, types.NamespacedName{ + Name: podIdentityWebhookName, + Namespace: ns, + }, check); err != nil && !apierrors.IsNotFound(err) { + return err + } + + if check.UID != "" { + return nil + } + + replicas := int32(1) + deployment := &v13.Deployment{ + ObjectMeta: objectMeta(podIdentityWebhookName, ns), + Spec: v13.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": podIdentityWebhookName, + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app": podIdentityWebhookName, + }, + }, + Spec: corev1.PodSpec{ + ServiceAccountName: podIdentityWebhookName, + Containers: []corev1.Container{ + { + Name: podIdentityWebhookName, + Image: podIdentityWebhookImage, + ImagePullPolicy: corev1.PullIfNotPresent, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "webhook-certs", + MountPath: "/etc/webhook/certs/", + ReadOnly: false, + }, + }, + Command: []string{ + "/webhook", + "--in-cluster=false", + "--namespace=" + ns, + "--service-name=" + podIdentityWebhookName, + "--annotation-prefix=eks.amazonaws.com", + "--token-audience=sts.amazonaws.com", + "--logtostderr", + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "webhook-certs", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: secret.Name, + }, + }, + }, + }, + }, + }, + }, + } + + return remoteClient.Create(ctx, deployment) +} + +func reconcileMutatingWebHook(ctx context.Context, ns string, secret *corev1.Secret, remoteClient client.Client) error { + check := &v14.MutatingWebhookConfiguration{} + if err := remoteClient.Get(ctx, types.NamespacedName{ + Name: podIdentityWebhookName, + Namespace: ns, + }, check); err != nil && !apierrors.IsNotFound(err) { + return err + } + + if check.UID != "" { + return nil + } + + caBundle, ok := secret.Data["ca.crt"] + if !ok { + return errors.New("no CA certificate for the pod identity webhook certificate") + } + + mwhMeta := objectMeta(podIdentityWebhookName, ns) + fail := v14.Ignore + none := v14.SideEffectClassNone + mutate := "/mutate" + mwh := &v14.MutatingWebhookConfiguration{ + ObjectMeta: mwhMeta, + Webhooks: []v14.MutatingWebhook{ + { + Name: podIdentityWebhookName + ".amazonaws.com", + FailurePolicy: &fail, + ClientConfig: v14.WebhookClientConfig{ + Service: &v14.ServiceReference{ + Name: podIdentityWebhookName, + Namespace: ns, + Path: &mutate, + }, + CABundle: caBundle, + }, + Rules: []v14.RuleWithOperations{ + { + Operations: []v14.OperationType{v14.Create}, + Rule: v14.Rule{ + APIGroups: []string{""}, + APIVersions: []string{"v1"}, + Resources: []string{"pods"}, + }, + }, + }, + SideEffects: &none, + AdmissionReviewVersions: []string{"v1beta1"}, + }, + }, + } + + return remoteClient.Create(ctx, mwh) +} + +// reconcilePodIdentityWebhookComponents will create sa, cr, crb, service, deployment and a mutating webhook in kube-system. The +// only difference between this and upstream is we are using cert-manager in the management cluster to create a cert +// instead installing it in the work load cluster. +// https://github.com/aws/amazon-eks-pod-identity-webhook/tree/master/deploy +func reconcilePodIdentityWebhookComponents(ctx context.Context, ns string, secret *corev1.Secret, remoteClient client.Client) error { + // TODO only creates the object if they don't exist, could create some comparison logic for updates + if err := reconcileServiceAccount(ctx, ns, remoteClient); err != nil { + return err + } + + if err := reconcileClusterRole(ctx, ns, remoteClient); err != nil { + return err + } + + if err := reconcileClusterRoleBinding(ctx, ns, remoteClient); err != nil { + return err + } + + if err := reconcileService(ctx, ns, remoteClient); err != nil { + return err + } + + if err := reconcileDeployment(ctx, ns, secret, remoteClient); err != nil { + return err + } + + if err := reconcileMutatingWebHook(ctx, ns, secret, remoteClient); err != nil { + return err + } + + return nil +} + +func objectMeta(name, namespace string) metav1.ObjectMeta { + meta := metav1.ObjectMeta{ + Labels: map[string]string{ + clusterv1.ProviderNameLabel: "infrastructure-aws", + }, + Name: name, + } + if namespace != "" { + meta.Namespace = namespace + } + return meta +} + +// reconcileCertifcateSecret takes a secret and moves it to the workload cluster. +func reconcileCertifcateSecret(ctx context.Context, cert *corev1.Secret, remoteClient client.Client) error { + // check if the secret was created by cert-manager + certCheck := &corev1.Secret{} + if err := remoteClient.Get(ctx, types.NamespacedName{ + Name: cert.Name, + Namespace: cert.Namespace, + }, certCheck); err != nil && !apierrors.IsNotFound(err) { + // will return not found if waiting for cert-manager and will reconcile again later due to error + return err + } + + if certCheck.UID == "" { + cert.ResourceVersion = "" + return remoteClient.Create(ctx, cert) + } + + return nil +} diff --git a/pkg/cloud/services/iam/reconcilers.go b/pkg/cloud/services/iam/reconcilers.go new file mode 100644 index 0000000000..ffaa6aee01 --- /dev/null +++ b/pkg/cloud/services/iam/reconcilers.go @@ -0,0 +1,180 @@ +package iam + +import ( + "context" + "fmt" + "path" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/iam" + v1certmanager "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" + "sigs.k8s.io/cluster-api-provider-aws/v2/cmd/clusterawsadm/converters" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" +) + +// reconcilePodIdentityWebhook generates certs and starts the webhook in the workload cluster +// https://github.com/aws/amazon-eks-pod-identity-webhook +// 1. generate webhook certs via cert-manager in the management cluster +// 2. push cert secret down to the workload cluster +// 3. deploy pod identity webhook components with mounted certs (rbac,deployment,mwh,service). +func (s *Service) reconcilePodIdentityWebhook(ctx context.Context) error { + certName := fmt.Sprintf(PodIdentityWebhookCertificateFormat, s.scope.Name()) + certSecret, err := certificateSecret(ctx, + certName, s.scope.Namespace(), + fmt.Sprintf(SelfsignedIssuerFormat, s.scope.Name()), []string{ + fmt.Sprintf("%s.%s.svc", podIdentityWebhookName, s.scope.Namespace()), + fmt.Sprintf("%s.%s.svc.cluster.local", podIdentityWebhookName, s.scope.Namespace()), + }, s.scope.ManagementClient()) + + if err != nil { + return err + } + + remoteClient, err := s.scope.RemoteClient() + if err != nil { + return err + } + + // switch it to kube-system and move it to the remote cluster + if err := reconcileCertifcateSecret(ctx, certSecret, remoteClient); err != nil { + return err + } + + if err := reconcilePodIdentityWebhookComponents(ctx, s.scope.Namespace(), certSecret, remoteClient); err != nil { + return err + } + + return nil +} + +// reconcileSelfsignedIssuer create a selfsigned issuer at the cluster level. +func (s *Service) reconcileSelfsignedIssuer(ctx context.Context) error { + mgmtClient := s.scope.ManagementClient() + issuerName := fmt.Sprintf(SelfsignedIssuerFormat, s.scope.Name()) + issuer := &v1certmanager.Issuer{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + clusterv1.ProviderNameLabel: "infrastructure-aws", + }, + Name: issuerName, + Namespace: s.scope.Namespace(), + }, + Spec: v1certmanager.IssuerSpec{ + IssuerConfig: v1certmanager.IssuerConfig{ + SelfSigned: &v1certmanager.SelfSignedIssuer{}, + }, + }, + } + + if err := mgmtClient.Get(ctx, types.NamespacedName{ + Name: issuerName, + Namespace: s.scope.Namespace(), + }, issuer); err != nil && !apierrors.IsNotFound(err) { + return err + } + + if issuer.UID != "" { + return nil + } + + return mgmtClient.Create(ctx, issuer) +} + +// CreateOIDCProvider will create an OIDC provider in IAM and store the arn/trustpolicy on the cluster status. +func (s *Service) reconcileIdentityProvider(_ context.Context) error { + s3Host := fmt.Sprintf(S3HostFormat, s.scope.Region()) + thumbprint, err := fetchRootCAThumbprint(s3Host, 443) + if err != nil { + return err + } + + // e.g. s3-us-west-2.amazonaws.com// + oidcURL := "https://" + path.Join(s3Host, s.scope.Bucket().Name, s.scope.Name()) + arn, err := findAndVerifyOIDCProvider(oidcURL, thumbprint, s.IAMClient) + if err != nil { + return err + } + + // find and verify confirms it's in IAM but if the status is not set we still want update + providerStatus := s.scope.OIDCProviderStatus() + if providerStatus.ARN != "" && providerStatus.ARN == arn { + return nil + } + + if arn == "" { + var tags []*iam.Tag + tags = append(tags, &iam.Tag{ + Key: aws.String(v1beta2.ClusterAWSCloudProviderTagKey(s.scope.Name())), + Value: aws.String(string(v1beta2.ResourceLifecycleOwned)), + }) + + input := iam.CreateOpenIDConnectProviderInput{ + ClientIDList: aws.StringSlice([]string{STSAWSAudience}), + ThumbprintList: aws.StringSlice([]string{thumbprint}), + Url: aws.String(oidcURL), + Tags: tags, + } + provider, err := s.IAMClient.CreateOpenIDConnectProvider(&input) + if err != nil { + return errors.Wrap(err, "error creating provider") + } + arn = aws.StringValue(provider.OpenIDConnectProviderArn) + } + + providerStatus.ARN = arn + oidcTrustPolicy := buildOIDCTrustPolicy(providerStatus.ARN) + policy, err := converters.IAMPolicyDocumentToJSON(oidcTrustPolicy) + if err != nil { + return errors.Wrap(err, "failed to parse IAM policy") + } + providerStatus.TrustPolicy = WhitespaceRegex.ReplaceAllString(policy, "") + return s.scope.PatchObject() +} + +// reconcileTrustPolicyConfigMap make sure the remote cluster has the config map of the trust policy, this enables +// the remote cluster to have everything it needs to create roles for services accounts. +func (s *Service) reconcileTrustPolicyConfigMap(ctx context.Context) error { + remoteClient, err := s.scope.RemoteClient() + if err != nil { + return err + } + + configMapRef := types.NamespacedName{ + Name: TrustPolicyConfigMapName, + Namespace: TrustPolicyConfigMapNamespace, + } + + trustPolicyConfigMap := &corev1.ConfigMap{} + err = remoteClient.Get(ctx, configMapRef, trustPolicyConfigMap) + if err != nil && !apierrors.IsNotFound(err) { + return fmt.Errorf("getting %s/%s config map: %w", TrustPolicyConfigMapNamespace, TrustPolicyConfigMapName, err) + } + + policy, err := converters.IAMPolicyDocumentToJSON(buildOIDCTrustPolicy(s.scope.OIDCProviderStatus().ARN)) + if err != nil { + return errors.Wrap(err, "failed to parse IAM policy") + } + + if tp, ok := trustPolicyConfigMap.Data[TrustPolicyJSON]; ok && tp == policy { + return nil // trust policy in the kube is the same as generated, don't update + } + + trustPolicyConfigMap.Data = map[string]string{ + TrustPolicyJSON: policy, + } + + if trustPolicyConfigMap.UID == "" { + trustPolicyConfigMap.Name = TrustPolicyConfigMapName + trustPolicyConfigMap.Namespace = TrustPolicyConfigMapNamespace + return remoteClient.Create(ctx, trustPolicyConfigMap) + } + + return remoteClient.Update(ctx, trustPolicyConfigMap) +} diff --git a/pkg/cloud/services/interfaces.go b/pkg/cloud/services/interfaces.go index df6b4c179e..b2d04c469d 100644 --- a/pkg/cloud/services/interfaces.go +++ b/pkg/cloud/services/interfaces.go @@ -17,6 +17,8 @@ limitations under the License. package services import ( + "context" + infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/scope" @@ -109,6 +111,11 @@ type NetworkInterface interface { ReconcileNetwork() error } +type IAMInterface interface { + ReconcileOIDCProvider(ctx context.Context) error + DeleteOIDCProvider(ctx context.Context) error +} + // SecurityGroupInterface encapsulates the methods exposed to the cluster // controller. type SecurityGroupInterface interface { @@ -120,6 +127,7 @@ type SecurityGroupInterface interface { type ObjectStoreInterface interface { DeleteBucket() error ReconcileBucket() error - Delete(m *scope.MachineScope) error - Create(m *scope.MachineScope, data []byte) (objectURL string, err error) + Delete(key string) error + Create(key string, data []byte) (objectURL string, err error) + CreatePublic(key string, data []byte) (objectURL string, err error) } diff --git a/pkg/cloud/services/mock_services/objectstore_machine_interface_mock.go b/pkg/cloud/services/mock_services/objectstore_machine_interface_mock.go index 559f356f3a..bbe33f6b68 100644 --- a/pkg/cloud/services/mock_services/objectstore_machine_interface_mock.go +++ b/pkg/cloud/services/mock_services/objectstore_machine_interface_mock.go @@ -24,7 +24,6 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - scope "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/scope" ) // MockObjectStoreInterface is a mock of ObjectStoreInterface interface. @@ -51,7 +50,7 @@ func (m *MockObjectStoreInterface) EXPECT() *MockObjectStoreInterfaceMockRecorde } // Create mocks base method. -func (m *MockObjectStoreInterface) Create(arg0 *scope.MachineScope, arg1 []byte) (string, error) { +func (m *MockObjectStoreInterface) Create(arg0 string, arg1 []byte) (string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Create", arg0, arg1) ret0, _ := ret[0].(string) @@ -65,8 +64,23 @@ func (mr *MockObjectStoreInterfaceMockRecorder) Create(arg0, arg1 interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockObjectStoreInterface)(nil).Create), arg0, arg1) } +// CreatePublic mocks base method. +func (m *MockObjectStoreInterface) CreatePublic(arg0 string, arg1 []byte) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreatePublic", arg0, arg1) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreatePublic indicates an expected call of CreatePublic. +func (mr *MockObjectStoreInterfaceMockRecorder) CreatePublic(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePublic", reflect.TypeOf((*MockObjectStoreInterface)(nil).CreatePublic), arg0, arg1) +} + // Delete mocks base method. -func (m *MockObjectStoreInterface) Delete(arg0 *scope.MachineScope) error { +func (m *MockObjectStoreInterface) Delete(arg0 string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Delete", arg0) ret0, _ := ret[0].(error) diff --git a/pkg/cloud/services/s3/s3.go b/pkg/cloud/services/s3/s3.go index b485b43c95..8c59e39a31 100644 --- a/pkg/cloud/services/s3/s3.go +++ b/pkg/cloud/services/s3/s3.go @@ -21,7 +21,6 @@ import ( "encoding/json" "fmt" "net/url" - "path" "sort" "github.com/aws/aws-sdk-go/aws" @@ -47,6 +46,24 @@ type Service struct { STSClient stsiface.STSAPI } +var DisabledError = errors.New("s3 management disabled") + +func IsDisabledError(err error) bool { + return err == DisabledError +} + +var EmptyBucketError = errors.New("empty bucket name") + +func IsEmptyBucketError(err error) bool { + return err == EmptyBucketError +} + +var EmptyKeyError = errors.New("empty key") + +func IsEmptyKeyError(err error) bool { + return err == EmptyKeyError +} + // NewService returns a new service given the api clients. func NewService(s3Scope scope.S3Scope) *Service { s3Client := scope.NewS3Client(s3Scope, s3Scope, s3Scope, s3Scope.InfraCluster()) @@ -74,6 +91,10 @@ func (s *Service) ReconcileBucket() error { return errors.Wrap(err, "tagging bucket") } + if err := s.ensureBucketAccess(bucketName); err != nil { + return errors.Wrap(err, "ensuring bucket ACL ") + } + if err := s.ensureBucketPolicy(bucketName); err != nil { return errors.Wrap(err, "ensuring bucket policy") } @@ -87,6 +108,9 @@ func (s *Service) DeleteBucket() error { } bucketName := s.bucketName() + if bucketName == "" { + return EmptyBucketError + } log := s.scope.WithValues("name", bucketName) @@ -116,30 +140,48 @@ func (s *Service) DeleteBucket() error { return nil } -func (s *Service) Create(m *scope.MachineScope, data []byte) (string, error) { +// Create will add a file to the s3 bucket which is private and server side encrypted. +func (s *Service) Create(key string, data []byte) (string, error) { if !s.bucketManagementEnabled() { - return "", errors.New("requested object creation but bucket management is not enabled") + return "", DisabledError } - if m == nil { - return "", errors.New("machine scope can't be nil") + // server side encryption, acl defaults to private + return s.create(&s3.PutObjectInput{ + Body: aws.ReadSeekCloser(bytes.NewReader(data)), + Bucket: aws.String(s.scope.Bucket().Name), + Key: aws.String(key), + ServerSideEncryption: aws.String("aws:kms"), + }) +} + +// CreatePublic will add file to the s3 bucket which is public and open to the world to access. +func (s *Service) CreatePublic(key string, data []byte) (string, error) { + // acl public-read + if !s.bucketManagementEnabled() { + return "", DisabledError } - if len(data) == 0 { - return "", errors.New("got empty data") + return s.create(&s3.PutObjectInput{ + Body: aws.ReadSeekCloser(bytes.NewReader(data)), + Bucket: aws.String(s.scope.Bucket().Name), + Key: aws.String(key), + ACL: aws.String("public-read"), + }) +} + +func (s *Service) create(putInput *s3.PutObjectInput) (string, error) { + if aws.StringValue(putInput.Bucket) == "" { + return "", EmptyBucketError } - bucket := s.bucketName() - key := s.bootstrapDataKey(m) + if aws.StringValue(putInput.Key) == "" { + return "", EmptyKeyError + } - s.scope.Info("Creating object", "bucket_name", bucket, "key", key) + s.scope.Info("Creating public object", "bucket_name", aws.StringValue(putInput.Bucket), "key", aws.StringValue(putInput.Key)) - if _, err := s.S3Client.PutObject(&s3.PutObjectInput{ - Body: aws.ReadSeekCloser(bytes.NewReader(data)), - Bucket: aws.String(bucket), - Key: aws.String(key), - ServerSideEncryption: aws.String("aws:kms"), - }); err != nil { + if _, err := s.S3Client.PutObject(putInput); err != nil { return "", errors.Wrap(err, "putting object") } @@ -154,29 +196,32 @@ func (s *Service) Create(m *scope.MachineScope, data []byte) (string, error) { objectURL := &url.URL{ Scheme: "s3", - Host: bucket, - Path: key, + Host: aws.StringValue(putInput.Bucket), + Path: aws.StringValue(putInput.Key), } return objectURL.String(), nil } -func (s *Service) Delete(m *scope.MachineScope) error { +// Delete takes a key which is a s3 path to an object e.g. /path/file.ext. +func (s *Service) Delete(key string) error { if !s.bucketManagementEnabled() { - return errors.New("requested object creation but bucket management is not enabled") + return DisabledError } - if m == nil { - return errors.New("machine scope can't be nil") + if key == "" { + return EmptyKeyError } - bucket := s.bucketName() - key := s.bootstrapDataKey(m) + bucketName := s.bucketName() + if bucketName == "" { + return EmptyBucketError + } - s.scope.Info("Deleting object", "bucket_name", bucket, "key", key) + s.scope.Info("Deleting object", "bucket_name", bucketName, "key", key) _, err := s.S3Client.DeleteObject(&s3.DeleteObjectInput{ - Bucket: aws.String(bucket), + Bucket: aws.String(bucketName), Key: aws.String(key), }) if err == nil { @@ -199,7 +244,8 @@ func (s *Service) Delete(m *scope.MachineScope) error { func (s *Service) createBucketIfNotExist(bucketName string) error { input := &s3.CreateBucketInput{ - Bucket: aws.String(bucketName), + Bucket: aws.String(bucketName), + ObjectOwnership: aws.String(s3.ObjectOwnershipBucketOwnerPreferred), } _, err := s.S3Client.CreateBucket(input) @@ -225,6 +271,24 @@ func (s *Service) createBucketIfNotExist(bucketName string) error { } } +func (s *Service) ensureBucketAccess(bucketName string) error { + f := false + input := &s3.PutPublicAccessBlockInput{ + Bucket: aws.String(bucketName), + PublicAccessBlockConfiguration: &s3.PublicAccessBlockConfiguration{ + BlockPublicAcls: aws.Bool(f), + }, + } + + if _, err := s.S3Client.PutPublicAccessBlock(input); err != nil { + return errors.Wrap(err, "enabling bucket public access") + } + + s.scope.GetLogger().Info("Updated bucket ACL to allow public access", "bucket_name", bucketName) + + return nil +} + func (s *Service) ensureBucketPolicy(bucketName string) error { if s.scope.Bucket().PresignedURLDuration != nil { // If presigned URL is enabled, we don't need to set bucket policy. @@ -287,6 +351,10 @@ func (s *Service) tagBucket(bucketName string) error { return nil } +// bucketPolicy grants access to get/put objects the cluster needs including a per cluster subdir in case two clusters share the same bucket. +// / contains cluster wide object e.g. oidc configs for irsa. +// /control-plane contains ignite configs for control-plane nodes stored per node id. +// /node contains ignite configs for worker nodes stored per node id. func (s *Service) bucketPolicy(bucketName string) (string, error) { accountID, err := s.STSClient.GetCallerIdentity(&sts.GetCallerIdentityInput{}) if err != nil { @@ -297,6 +365,16 @@ func (s *Service) bucketPolicy(bucketName string) (string, error) { partition := system.GetPartitionFromRegion(s.scope.Region()) statements := []iam.StatementEntry{ + { + // grant access to the / folder to the control plane nodes + Sid: s.scope.Name(), + Effect: iam.EffectAllow, + Principal: map[iam.PrincipalType]iam.PrincipalID{ + iam.PrincipalAWS: []string{fmt.Sprintf("arn:aws:iam::%s:role/%s", *accountID.Account, bucket.ControlPlaneIAMInstanceProfile)}, + }, + Action: []string{"s3:GetObject", "s3:PutObject"}, + Resource: []string{fmt.Sprintf("arn:aws:s3:::%s/%s/*", bucketName, s.scope.Name())}, + }, { Sid: "control-plane", Effect: iam.EffectAllow, @@ -340,8 +418,3 @@ func (s *Service) bucketManagementEnabled() bool { func (s *Service) bucketName() string { return s.scope.Bucket().Name } - -func (s *Service) bootstrapDataKey(m *scope.MachineScope) string { - // Use machine name as object key. - return path.Join(m.Role(), m.Name()) -} diff --git a/pkg/cloud/services/s3/s3_test.go b/pkg/cloud/services/s3/s3_test.go index f3753b24ad..e903f36c0e 100644 --- a/pkg/cloud/services/s3/s3_test.go +++ b/pkg/cloud/services/s3/s3_test.go @@ -71,7 +71,8 @@ func TestReconcileBucket(t *testing.T) { }) input := &s3svc.CreateBucketInput{ - Bucket: aws.String(expectedBucketName), + Bucket: aws.String(expectedBucketName), + ObjectOwnership: aws.String(s3svc.ObjectOwnershipBucketOwnerPreferred), } s3Mock.EXPECT().CreateBucket(gomock.Eq(input)).Return(nil, nil).Times(1) @@ -95,6 +96,7 @@ func TestReconcileBucket(t *testing.T) { s3Mock.EXPECT().PutBucketTagging(gomock.Eq(taggingInput)).Return(nil, nil).Times(1) s3Mock.EXPECT().PutBucketPolicy(gomock.Any()).Return(nil, nil).Times(1) + s3Mock.EXPECT().PutPublicAccessBlock(gomock.Any()).Return(nil, nil).Times(1) if err := svc.ReconcileBucket(); err != nil { t.Fatalf("Unexpected error: %v", err) @@ -116,7 +118,7 @@ func TestReconcileBucket(t *testing.T) { client := fake.NewClientBuilder().WithScheme(scheme).Build() longName := strings.Repeat("a", 40) - scope, err := scope.NewClusterScope(scope.ClusterScopeParams{ + clusterScope, err := scope.NewClusterScope(scope.ClusterScopeParams{ Client: client, Cluster: &clusterv1.Cluster{ ObjectMeta: metav1.ObjectMeta{ @@ -134,7 +136,7 @@ func TestReconcileBucket(t *testing.T) { t.Fatalf("Failed to create test context: %v", err) } - svc := s3.NewService(scope) + svc := s3.NewService(clusterScope) svc.S3Client = s3Mock svc.STSClient = stsMock @@ -150,6 +152,7 @@ func TestReconcileBucket(t *testing.T) { s3Mock.EXPECT().PutBucketTagging(gomock.Any()).Return(nil, nil).Times(1) s3Mock.EXPECT().PutBucketPolicy(gomock.Any()).Return(nil, nil).Times(1) + s3Mock.EXPECT().PutPublicAccessBlock(gomock.Any()).Return(nil, nil).Times(1) if err := svc.ReconcileBucket(); err != nil { t.Fatalf("Unexpected error: %v", err) @@ -171,6 +174,7 @@ func TestReconcileBucket(t *testing.T) { s3Mock.EXPECT().CreateBucket(gomock.Any()).Return(nil, nil).Times(1) s3Mock.EXPECT().PutBucketTagging(gomock.Any()).Return(nil, nil).Times(1) + s3Mock.EXPECT().PutPublicAccessBlock(gomock.Any()).Return(nil, nil).Times(1) s3Mock.EXPECT().PutBucketPolicy(gomock.Any()).Do(func(input *s3svc.PutBucketPolicyInput) { if input.Policy == nil { t.Fatalf("Policy must be defined") @@ -207,11 +211,14 @@ func TestReconcileBucket(t *testing.T) { t.Run("is_idempotent", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &infrav1.S3Bucket{ + Name: "doesnt-exist", + }) s3Mock.EXPECT().CreateBucket(gomock.Any()).Return(nil, nil).Times(2) s3Mock.EXPECT().PutBucketTagging(gomock.Any()).Return(nil, nil).Times(2) s3Mock.EXPECT().PutBucketPolicy(gomock.Any()).Return(nil, nil).Times(2) + s3Mock.EXPECT().PutPublicAccessBlock(gomock.Any()).Return(nil, nil).Times(2) if err := svc.ReconcileBucket(); err != nil { t.Fatalf("Unexpected error: %v", err) @@ -232,6 +239,7 @@ func TestReconcileBucket(t *testing.T) { s3Mock.EXPECT().CreateBucket(gomock.Any()).Return(nil, err).Times(1) s3Mock.EXPECT().PutBucketTagging(gomock.Any()).Return(nil, nil).Times(1) s3Mock.EXPECT().PutBucketPolicy(gomock.Any()).Return(nil, nil).Times(1) + s3Mock.EXPECT().PutPublicAccessBlock(gomock.Any()).Return(nil, nil).Times(1) if err := svc.ReconcileBucket(); err != nil { t.Fatalf("Unexpected error, got: %v", err) @@ -276,6 +284,7 @@ func TestReconcileBucket(t *testing.T) { mockCtrl := gomock.NewController(t) stsMock := mock_stsiface.NewMockSTSAPI(mockCtrl) stsMock.EXPECT().GetCallerIdentity(gomock.Any()).Return(nil, fmt.Errorf(t.Name())).AnyTimes() + s3Mock.EXPECT().PutPublicAccessBlock(gomock.Any()).Return(nil, errors.New("error")).Times(1) svc.STSClient = stsMock if err := svc.ReconcileBucket(); err == nil { @@ -291,6 +300,7 @@ func TestReconcileBucket(t *testing.T) { s3Mock.EXPECT().CreateBucket(gomock.Any()).Return(nil, nil).Times(1) s3Mock.EXPECT().PutBucketTagging(gomock.Any()).Return(nil, nil).Times(1) s3Mock.EXPECT().PutBucketPolicy(gomock.Any()).Return(nil, errors.New("error")).Times(1) + s3Mock.EXPECT().PutPublicAccessBlock(gomock.Any()).Return(nil, errors.New("error")).Times(1) if err := svc.ReconcileBucket(); err == nil { t.Fatalf("Expected error") @@ -337,7 +347,9 @@ func TestDeleteBucket(t *testing.T) { t.Run("unexpected_error", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &infrav1.S3Bucket{ + Name: "doesnt-exist", + }) s3Mock.EXPECT().DeleteBucket(gomock.Any()).Return(nil, errors.New("err")).Times(1) @@ -349,7 +361,9 @@ func TestDeleteBucket(t *testing.T) { t.Run("unexpected_AWS_error", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &infrav1.S3Bucket{ + Name: "doesnt-exist", + }) s3Mock.EXPECT().DeleteBucket(gomock.Any()).Return(nil, awserr.New("foo", "", nil)).Times(1) @@ -362,7 +376,9 @@ func TestDeleteBucket(t *testing.T) { t.Run("ignores_when_bucket_has_already_been_removed", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &infrav1.S3Bucket{ + Name: "doesnt-exist", + }) s3Mock.EXPECT().DeleteBucket(gomock.Any()).Return(nil, awserr.New(s3svc.ErrCodeNoSuchBucket, "", nil)).Times(1) @@ -374,7 +390,9 @@ func TestDeleteBucket(t *testing.T) { t.Run("skips_bucket_removal_when_bucket_is_not_empty", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &infrav1.S3Bucket{ + Name: "doesnt-exist", + }) s3Mock.EXPECT().DeleteBucket(gomock.Any()).Return(nil, awserr.New("BucketNotEmpty", "", nil)).Times(1) @@ -445,7 +463,7 @@ func TestCreateObject(t *testing.T) { }) }).Return(nil, nil).Times(1) - bootstrapDataURL, err := svc.Create(machineScope, bootstrapData) + bootstrapDataURL, err := svc.Create(machineScope.GetBootstrapDataKey(), bootstrapData) if err != nil { t.Fatalf("Unexpected error, got: %v", err) } @@ -476,7 +494,9 @@ func TestCreateObject(t *testing.T) { t.Run("is_idempotent", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &infrav1.S3Bucket{ + Name: "doesnt-exist", + }) machineScope := &scope.MachineScope{ Machine: &clusterv1.Machine{}, @@ -491,10 +511,10 @@ func TestCreateObject(t *testing.T) { boostrapData := []byte("foo") - if _, err := svc.Create(machineScope, boostrapData); err != nil { + if _, err := svc.Create(machineScope.GetBootstrapDataKey(), boostrapData); err != nil { t.Fatalf("Unexpected error: %v", err) } - if _, err := svc.Create(machineScope, boostrapData); err != nil { + if _, err := svc.Create(machineScope.GetBootstrapDataKey(), boostrapData); err != nil { t.Fatalf("Unexpected error: %v", err) } }) @@ -505,7 +525,9 @@ func TestCreateObject(t *testing.T) { t.Run("object_creation_fails", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &infrav1.S3Bucket{ + Name: "doesnt-exist", + }) machineScope := &scope.MachineScope{ Machine: &clusterv1.Machine{}, @@ -518,7 +540,7 @@ func TestCreateObject(t *testing.T) { s3Mock.EXPECT().PutObject(gomock.Any()).Return(nil, errors.New("foo")).Times(1) - bootstrapDataURL, err := svc.Create(machineScope, []byte("foo")) + bootstrapDataURL, err := svc.Create(machineScope.GetBootstrapDataKey(), []byte("foo")) if err == nil { t.Fatalf("Expected error") } @@ -533,7 +555,7 @@ func TestCreateObject(t *testing.T) { svc, _ := testService(t, &infrav1.S3Bucket{}) - bootstrapDataURL, err := svc.Create(nil, []byte("foo")) + bootstrapDataURL, err := svc.Create("", []byte("foo")) if err == nil { t.Fatalf("Expected error") } @@ -544,7 +566,7 @@ func TestCreateObject(t *testing.T) { }) // If one tries to put empty bootstrap data into S3, most likely something is wrong. - t.Run("given_empty_bootstrap_data", func(t *testing.T) { + t.Run("given_empty_bucket_name", func(t *testing.T) { t.Parallel() svc, _ := testService(t, &infrav1.S3Bucket{}) @@ -558,9 +580,9 @@ func TestCreateObject(t *testing.T) { }, } - bootstrapDataURL, err := svc.Create(machineScope, []byte{}) - if err == nil { - t.Fatalf("Expected error") + bootstrapDataURL, err := svc.Create(machineScope.GetBootstrapDataKey(), []byte{}) + if !s3.IsEmptyBucketError(err) { + t.Fatalf("Unexpected error: %v", err) } if bootstrapDataURL != "" { @@ -582,9 +604,9 @@ func TestCreateObject(t *testing.T) { }, } - bootstrapDataURL, err := svc.Create(machineScope, []byte("foo")) - if err == nil { - t.Fatalf("Expected error") + bootstrapDataURL, err := svc.Create(machineScope.GetBootstrapDataKey(), []byte("foo")) + if !s3.IsDisabledError(err) { + t.Fatalf("Unexpected error: %v", err) } if bootstrapDataURL != "" { @@ -645,7 +667,7 @@ func TestDeleteObject(t *testing.T) { }) }).Return(nil, nil).Times(1) - if err := svc.Delete(machineScope); err != nil { + if err := svc.Delete(machineScope.GetBootstrapDataKey()); err != nil { t.Fatalf("Unexpected error, got: %v", err) } }) @@ -653,7 +675,9 @@ func TestDeleteObject(t *testing.T) { t.Run("succeeds_when_bucket_has_already_been_removed", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &infrav1.S3Bucket{ + Name: "doesnt-exist", + }) machineScope := &scope.MachineScope{ Machine: &clusterv1.Machine{}, @@ -666,7 +690,7 @@ func TestDeleteObject(t *testing.T) { s3Mock.EXPECT().DeleteObject(gomock.Any()).Return(nil, awserr.New(s3svc.ErrCodeNoSuchBucket, "", nil)).Times(1) - if err := svc.Delete(machineScope); err != nil { + if err := svc.Delete(machineScope.GetBootstrapDataKey()); err != nil { t.Fatalf("Unexpected error, got: %v", err) } }) @@ -677,7 +701,9 @@ func TestDeleteObject(t *testing.T) { t.Run("object_deletion_fails", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &infrav1.S3Bucket{ + Name: "doesnt-exist", + }) machineScope := &scope.MachineScope{ Machine: &clusterv1.Machine{}, @@ -690,7 +716,7 @@ func TestDeleteObject(t *testing.T) { s3Mock.EXPECT().DeleteObject(gomock.Any()).Return(nil, errors.New("foo")).Times(1) - if err := svc.Delete(machineScope); err == nil { + if err := svc.Delete(machineScope.GetBootstrapDataKey()); err == nil { t.Fatalf("Expected error") } }) @@ -700,7 +726,7 @@ func TestDeleteObject(t *testing.T) { svc, _ := testService(t, &infrav1.S3Bucket{}) - if err := svc.Delete(nil); err == nil { + if err := svc.Delete(""); err == nil { t.Fatalf("Expected error") } }) @@ -719,8 +745,8 @@ func TestDeleteObject(t *testing.T) { }, } - if err := svc.Delete(machineScope); err == nil { - t.Fatalf("Expected error") + if err := svc.Delete(machineScope.GetBootstrapDataKey()); !s3.IsDisabledError(err) { + t.Fatalf("Unexpected error, got: %v", err) } }) }) @@ -728,7 +754,9 @@ func TestDeleteObject(t *testing.T) { t.Run("is_idempotent", func(t *testing.T) { t.Parallel() - svc, s3Mock := testService(t, &infrav1.S3Bucket{}) + svc, s3Mock := testService(t, &infrav1.S3Bucket{ + Name: "doesnt-exist", + }) machineScope := &scope.MachineScope{ Machine: &clusterv1.Machine{}, @@ -741,11 +769,11 @@ func TestDeleteObject(t *testing.T) { s3Mock.EXPECT().DeleteObject(gomock.Any()).Return(nil, nil).Times(2) - if err := svc.Delete(machineScope); err != nil { + if err := svc.Delete(machineScope.GetBootstrapDataKey()); err != nil { t.Fatalf("Unexpected error: %v", err) } - if err := svc.Delete(machineScope); err != nil { + if err := svc.Delete(machineScope.GetBootstrapDataKey()); err != nil { t.Fatalf("Unexpected error: %v", err) } }) @@ -765,7 +793,7 @@ func testService(t *testing.T, bucket *infrav1.S3Bucket) (*s3.Service, *mock_s3i _ = infrav1.AddToScheme(scheme) client := fake.NewClientBuilder().WithScheme(scheme).Build() - scope, err := scope.NewClusterScope(scope.ClusterScopeParams{ + clusterScope, err := scope.NewClusterScope(scope.ClusterScopeParams{ Client: client, Cluster: &clusterv1.Cluster{ ObjectMeta: metav1.ObjectMeta{ @@ -783,7 +811,7 @@ func testService(t *testing.T, bucket *infrav1.S3Bucket) (*s3.Service, *mock_s3i t.Fatalf("Failed to create test context: %v", err) } - svc := s3.NewService(scope) + svc := s3.NewService(clusterScope) svc.S3Client = s3Mock svc.STSClient = stsMock From ea2869e5823cea87b65eab4360af0d2d052863a9 Mon Sep 17 00:00:00 2001 From: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> Date: Sat, 7 Oct 2023 18:48:21 +0200 Subject: [PATCH 2/2] applied a small number of refactorings as I read through the code --- api/v1beta1/zz_generated.conversion.go | 4 -- api/v1beta2/conditions_consts.go | 8 ++++ controllers/awscluster_controller.go | 1 + controllers/awsmachine_controller.go | 43 +++++++++++--------- pkg/cloud/services/iam/iam.go | 17 ++++++-- pkg/cloud/services/iam/oidc.go | 19 +++++---- pkg/cloud/services/iam/podidentitywebhook.go | 4 +- pkg/cloud/services/iam/reconcilers.go | 2 +- pkg/cloud/services/s3/s3.go | 24 ++++++----- pkg/cloud/services/s3/s3_test.go | 1 - 10 files changed, 73 insertions(+), 50 deletions(-) diff --git a/api/v1beta1/zz_generated.conversion.go b/api/v1beta1/zz_generated.conversion.go index 0ad6c9919b..318fe42575 100644 --- a/api/v1beta1/zz_generated.conversion.go +++ b/api/v1beta1/zz_generated.conversion.go @@ -939,7 +939,6 @@ func autoConvert_v1beta2_AWSClusterSpec_To_v1beta1_AWSClusterSpec(in *v1beta2.AW return err } out.IdentityRef = (*AWSIdentityReference)(unsafe.Pointer(in.IdentityRef)) -<<<<<<< HEAD if in.S3Bucket != nil { in, out := &in.S3Bucket, &out.S3Bucket *out = new(S3Bucket) @@ -949,10 +948,7 @@ func autoConvert_v1beta2_AWSClusterSpec_To_v1beta1_AWSClusterSpec(in *v1beta2.AW } else { out.S3Bucket = nil } -======= - out.S3Bucket = (*S3Bucket)(unsafe.Pointer(in.S3Bucket)) // WARNING: in.AssociateOIDCProvider requires manual conversion: does not exist in peer-type ->>>>>>> 17f26c99d (adding IRSA functionality through an OIDC provider using an s3 bucket) return nil } diff --git a/api/v1beta2/conditions_consts.go b/api/v1beta2/conditions_consts.go index c76dd13d5b..b66d48357f 100644 --- a/api/v1beta2/conditions_consts.go +++ b/api/v1beta2/conditions_consts.go @@ -173,3 +173,11 @@ const ( // S3BucketFailedReason is used when any errors occur during reconciliation of an S3 bucket. S3BucketFailedReason = "S3BucketCreationFailed" ) + +const ( + // OIDCProviderReadyCondition indicates that the OIDC provider has been created successfully. + OIDCProviderReadyCondition = "OIDCProviderCreated" + + // OIDCProviderReconciliationFailedReason is used if we can't reconcile the OIDC provider. + OIDCProviderReconciliationFailedReason = "OIDCProviderReconciliationFailed" +) diff --git a/controllers/awscluster_controller.go b/controllers/awscluster_controller.go index bbd4b32c59..4aa694cb75 100644 --- a/controllers/awscluster_controller.go +++ b/controllers/awscluster_controller.go @@ -337,6 +337,7 @@ func (r *AWSClusterReconciler) reconcileNormal(ctx context.Context, clusterScope } if err := iamService.ReconcileOIDCProvider(ctx); err != nil { + conditions.MarkFalse(awsCluster, infrav1.OIDCProviderReadyCondition, infrav1.OIDCProviderReconciliationFailedReason, clusterv1.ConditionSeverityError, err.Error()) clusterScope.Error(err, "failed to reconcile OIDC provider") return reconcile.Result{RequeueAfter: 15 * time.Second}, nil } diff --git a/controllers/awsmachine_controller.go b/controllers/awsmachine_controller.go index afb4ec1e12..67335ab94e 100644 --- a/controllers/awsmachine_controller.go +++ b/controllers/awsmachine_controller.go @@ -309,7 +309,7 @@ func (r *AWSMachineReconciler) reconcileDelete(machineScope *scope.MachineScope, } instance, err := r.findInstance(machineScope, ec2Service) - if err != nil && err != ec2.ErrInstanceNotFoundByID { + if err != nil && !errors.Is(err, ec2.ErrInstanceNotFoundByID) { machineScope.Error(err, "query to find instance failed") return ctrl.Result{}, err } @@ -651,8 +651,7 @@ func (r *AWSMachineReconciler) reconcileOperationalState(ctx context.Context, ec } // check if the remote kubeconfig works and annotate the cluster - _, ok := machineScope.InfraCluster.InfraCluster().GetAnnotations()[scope.KubeconfigReadyAnnotation] - if !ok && machineScope.IsControlPlane() { + if _, ok := machineScope.InfraCluster.InfraCluster().GetAnnotations()[scope.KubeconfigReadyAnnotation]; !ok && machineScope.IsControlPlane() { // if a control plane node is operational check for a kubeconfig and a working control plane node // and set the annotation so any reconciliation which requires workload api access can complete remoteClient, err := machineScope.InfraCluster.RemoteClient() @@ -669,23 +668,29 @@ func (r *AWSMachineReconciler) reconcileOperationalState(ctx context.Context, ec for i := range nodes.Items { if util.IsNodeReady(&nodes.Items[i]) { oneReady = true // if one control plane is ready return true + break } } - if oneReady { - awsCluster := &infrav1.AWSCluster{} - key := types.NamespacedName{Namespace: machineScope.InfraCluster.Namespace(), Name: machineScope.InfraCluster.Name()} - if err := r.Client.Get(ctx, key, awsCluster); err != nil { - return err - } - anno := awsCluster.GetAnnotations() - anno[scope.KubeconfigReadyAnnotation] = "true" - awsCluster.SetAnnotations(anno) - if err := r.Client.Update(ctx, awsCluster); err != nil { - return err - } - } else { + if !oneReady { r.Log.Info("waiting for a control plane node to be ready before annotating the cluster, do you need to deploy a CNI?") + + return nil + } + + awsCluster := &infrav1.AWSCluster{} + key := types.NamespacedName{ + Namespace: machineScope.InfraCluster.Namespace(), + Name: machineScope.InfraCluster.Name(), + } + if err := r.Client.Get(ctx, key, awsCluster); err != nil { + return fmt.Errorf("failed to get aws cluster: %w", err) + } + + awsCluster.Annotations[scope.KubeconfigReadyAnnotation] = "true" + + if err := r.Client.Update(ctx, awsCluster); err != nil { + return fmt.Errorf("failed to update aws cluster with new annotation: %w", err) } } @@ -1206,19 +1211,19 @@ func (r *AWSMachineReconciler) ensureStorageTags(ec2svc services.EC2Interface, i if err != nil { r.Log.Error(err, "Failed to fetch the changed volume tags in EC2 instance") } - prevAnnotations[volumeID] = newAnnotation + annotations[volumeID] = newAnnotation } else { newAnnotation, err := r.ensureVolumeTags(ec2svc, aws.String(volumeID), make(map[string]interface{}), additionalTags) if err != nil { r.Log.Error(err, "Failed to fetch the changed volume tags in EC2 instance") } - prevAnnotations[volumeID] = newAnnotation + annotations[volumeID] = newAnnotation } } if !cmp.Equal(prevAnnotations, annotations, cmpopts.EquateEmpty()) { // We also need to update the annotation if anything changed. - err = r.updateMachineAnnotationJSON(machine, VolumeTagsLastAppliedAnnotation, prevAnnotations) + err = r.updateMachineAnnotationJSON(machine, VolumeTagsLastAppliedAnnotation, annotations) if err != nil { r.Log.Error(err, "Failed to fetch the changed volume tags in EC2 instance") } diff --git a/pkg/cloud/services/iam/iam.go b/pkg/cloud/services/iam/iam.go index bc3fd17079..cc206e94ea 100644 --- a/pkg/cloud/services/iam/iam.go +++ b/pkg/cloud/services/iam/iam.go @@ -3,13 +3,16 @@ package iam import ( "context" "errors" + "fmt" "regexp" "github.com/aws/aws-sdk-go/service/iam/iamiface" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/scope" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/s3" + "sigs.k8s.io/cluster-api/util/conditions" ) type Service struct { @@ -57,11 +60,11 @@ var ( // ReconcileOIDCProvider replicates functionality already built into managed clusters by auto-deploying the // modifying kube-apiserver args, deploying the pod identity webhook and setting/configuring an oidc provider // for more details see: https://github.com/aws/amazon-eks-pod-identity-webhook/blob/master/SELF_HOSTED_SETUP.md -// 1. create a self signed issuer for the mutating webhook +// 1. create a self-signed issuer for the mutating webhook // 2. add create a json patch for kube-apiserver and use capi config to add to the kubeadm.yml // 3. create an oidc provider in aws which points to the s3 bucket -// 4. pause until kubeconfig and cluster acccess is ready -// 5. move openid config and jwks to the s3 bucket +// 4. pause until kubeconfig and cluster access is ready +// 5. move openid config and JWKs to the s3 bucket // 6. add the pod identity webhook to the workload cluster // 7. add the configmap to the workload cluster. func (s *Service) ReconcileOIDCProvider(ctx context.Context) error { @@ -104,7 +107,13 @@ func (s *Service) ReconcileOIDCProvider(ctx context.Context) error { return err } - return s.reconcileTrustPolicyConfigMap(ctx) + if err := s.reconcileTrustPolicyConfigMap(ctx); err != nil { + return fmt.Errorf("failed to reconcile trust policy config map: %w", err) + } + + conditions.MarkTrue(s.scope.InfraCluster(), infrav1.OIDCProviderReadyCondition) + + return nil } // DeleteOIDCProvider will delete the iam resources note that the bucket is cleaned up in the s3 service diff --git a/pkg/cloud/services/iam/oidc.go b/pkg/cloud/services/iam/oidc.go index 9dde8cf002..67598221b7 100644 --- a/pkg/cloud/services/iam/oidc.go +++ b/pkg/cloud/services/iam/oidc.go @@ -5,6 +5,7 @@ import ( "context" "crypto/sha1" "crypto/tls" + stderr "errors" "fmt" "path" "strings" @@ -247,29 +248,31 @@ func findAndVerifyOIDCProvider(issuerURL, thumbprint string, iamClient iamiface. return "", nil } -func fetchRootCAThumbprint(url string, port int) (ret string, err error) { +func fetchRootCAThumbprint(url string, port int) (_ string, err error) { // Parse cmdline arguments using flag package conn, err := tls.Dial("tcp", fmt.Sprintf("%s:%d", url, port), &tls.Config{ MinVersion: tls.VersionTLS12, }) if err != nil { - return + return "", err } + defer func() { - err = conn.Close() + if cerr := conn.Close(); cerr != nil { + err = stderr.Join(err, cerr) + } }() - // Get the ConnectionState struct as that's the one which gives us x509.Certificate struct cert := conn.ConnectionState().PeerCertificates[0] fingerprint := sha1.Sum(cert.Raw) //nolint:gosec // this is not used for real security var buf bytes.Buffer for _, f := range fingerprint { - if _, err = fmt.Fprintf(&buf, "%02X", f); err != nil { - return + if _, err := fmt.Fprintf(&buf, "%02X", f); err != nil { + return "", err } } - ret = strings.ToLower(buf.String()) - return + + return strings.ToLower(buf.String()), nil } // DeleteOIDCProvider will delete an OIDC provider. diff --git a/pkg/cloud/services/iam/podidentitywebhook.go b/pkg/cloud/services/iam/podidentitywebhook.go index bc5299018c..0f0651b9ba 100644 --- a/pkg/cloud/services/iam/podidentitywebhook.go +++ b/pkg/cloud/services/iam/podidentitywebhook.go @@ -319,8 +319,8 @@ func objectMeta(name, namespace string) metav1.ObjectMeta { return meta } -// reconcileCertifcateSecret takes a secret and moves it to the workload cluster. -func reconcileCertifcateSecret(ctx context.Context, cert *corev1.Secret, remoteClient client.Client) error { +// reconcileCertificateSecret takes a secret and moves it to the workload cluster. +func reconcileCertificateSecret(ctx context.Context, cert *corev1.Secret, remoteClient client.Client) error { // check if the secret was created by cert-manager certCheck := &corev1.Secret{} if err := remoteClient.Get(ctx, types.NamespacedName{ diff --git a/pkg/cloud/services/iam/reconcilers.go b/pkg/cloud/services/iam/reconcilers.go index ffaa6aee01..945e81f3c6 100644 --- a/pkg/cloud/services/iam/reconcilers.go +++ b/pkg/cloud/services/iam/reconcilers.go @@ -43,7 +43,7 @@ func (s *Service) reconcilePodIdentityWebhook(ctx context.Context) error { } // switch it to kube-system and move it to the remote cluster - if err := reconcileCertifcateSecret(ctx, certSecret, remoteClient); err != nil { + if err := reconcileCertificateSecret(ctx, certSecret, remoteClient); err != nil { return err } diff --git a/pkg/cloud/services/s3/s3.go b/pkg/cloud/services/s3/s3.go index 8c59e39a31..3a5b053192 100644 --- a/pkg/cloud/services/s3/s3.go +++ b/pkg/cloud/services/s3/s3.go @@ -49,19 +49,19 @@ type Service struct { var DisabledError = errors.New("s3 management disabled") func IsDisabledError(err error) bool { - return err == DisabledError + return errors.Is(err, DisabledError) } var EmptyBucketError = errors.New("empty bucket name") func IsEmptyBucketError(err error) bool { - return err == EmptyBucketError + return errors.Is(err, EmptyBucketError) } var EmptyKeyError = errors.New("empty key") func IsEmptyKeyError(err error) bool { - return err == EmptyKeyError + return errors.Is(err, EmptyKeyError) } // NewService returns a new service given the api clients. @@ -123,7 +123,8 @@ func (s *Service) DeleteBucket() error { return nil } - aerr, ok := err.(awserr.Error) + var aerr awserr.Error + ok := errors.As(err, &aerr) if !ok { return errors.Wrap(err, "deleting S3 bucket") } @@ -186,10 +187,10 @@ func (s *Service) create(putInput *s3.PutObjectInput) (string, error) { } if exp := s.scope.Bucket().PresignedURLDuration; exp != nil { - s.scope.Info("Generating presigned URL", "bucket_name", bucket, "key", key) + s.scope.Info("Generating presigned URL", "bucket_name", aws.StringValue(putInput.Bucket), "key", aws.StringValue(putInput.Key)) req, _ := s.S3Client.GetObjectRequest(&s3.GetObjectInput{ - Bucket: aws.String(bucket), - Key: aws.String(key), + Bucket: putInput.Bucket, + Key: putInput.Key, }) return req.Presign(exp.Duration) } @@ -228,7 +229,8 @@ func (s *Service) Delete(key string) error { return nil } - aerr, ok := err.(awserr.Error) + var aerr awserr.Error + ok := errors.As(err, &aerr) if !ok { return errors.Wrap(err, "deleting S3 object") } @@ -255,7 +257,8 @@ func (s *Service) createBucketIfNotExist(bucketName string) error { return nil } - aerr, ok := err.(awserr.Error) + var aerr awserr.Error + ok := errors.As(err, &aerr) if !ok { return errors.Wrap(err, "creating S3 bucket") } @@ -272,11 +275,10 @@ func (s *Service) createBucketIfNotExist(bucketName string) error { } func (s *Service) ensureBucketAccess(bucketName string) error { - f := false input := &s3.PutPublicAccessBlockInput{ Bucket: aws.String(bucketName), PublicAccessBlockConfiguration: &s3.PublicAccessBlockConfiguration{ - BlockPublicAcls: aws.Bool(f), + BlockPublicAcls: aws.Bool(false), }, } diff --git a/pkg/cloud/services/s3/s3_test.go b/pkg/cloud/services/s3/s3_test.go index e903f36c0e..f80c4e9069 100644 --- a/pkg/cloud/services/s3/s3_test.go +++ b/pkg/cloud/services/s3/s3_test.go @@ -299,7 +299,6 @@ func TestReconcileBucket(t *testing.T) { s3Mock.EXPECT().CreateBucket(gomock.Any()).Return(nil, nil).Times(1) s3Mock.EXPECT().PutBucketTagging(gomock.Any()).Return(nil, nil).Times(1) - s3Mock.EXPECT().PutBucketPolicy(gomock.Any()).Return(nil, errors.New("error")).Times(1) s3Mock.EXPECT().PutPublicAccessBlock(gomock.Any()).Return(nil, errors.New("error")).Times(1) if err := svc.ReconcileBucket(); err == nil {