diff --git a/api/v1/resourcesetinputprovider_types.go b/api/v1/resourcesetinputprovider_types.go index 8a709cdf..742a7409 100644 --- a/api/v1/resourcesetinputprovider_types.go +++ b/api/v1/resourcesetinputprovider_types.go @@ -48,12 +48,34 @@ const ( // +kubebuilder:validation:XValidation:rule="!has(self.serviceAccountName) || self.type.startsWith('AzureDevOps') || self.type.endsWith('ArtifactTag')", message="cannot specify spec.serviceAccountName when spec.type is not one of AzureDevOps* or *ArtifactTag" // +kubebuilder:validation:XValidation:rule="!has(self.certSecretRef) || !(self.url == 'Static' || self.type.startsWith('AzureDevOps') || (self.type.endsWith('ArtifactTag') && self.type != 'OCIArtifactTag'))", message="cannot specify spec.certSecretRef when spec.type is one of Static, AzureDevOps*, ACRArtifactTag, ECRArtifactTag or GARArtifactTag" // +kubebuilder:validation:XValidation:rule="!has(self.secretRef) || !(self.url == 'Static' || (self.type.endsWith('ArtifactTag') && self.type != 'OCIArtifactTag'))", message="cannot specify spec.secretRef when spec.type is one of Static, ACRArtifactTag, ECRArtifactTag or GARArtifactTag" +// +kubebuilder:validation:XValidation:rule="!has(self.credential) || (self.credential == 'ServiceAccountToken' && self.type == 'OCIArtifactTag')", message="spec.credential can be set to 'ServiceAccountToken' only when spec.type is 'OCIArtifactTag'" +// +kubebuilder:validation:XValidation:rule="!has(self.audiences) || size(self.audiences) == 0 || (has(self.credential) && self.credential == 'ServiceAccountToken')", message="spec.audiences can be set only when spec.credential is set to 'ServiceAccountToken'" +// +kubebuilder:validation:XValidation:rule="!has(self.credential) || self.credential != 'ServiceAccountToken' || (has(self.audiences) && size(self.audiences) > 0)", message="spec.audiences must be set when spec.credential is set to 'ServiceAccountToken'" type ResourceSetInputProviderSpec struct { // Type specifies the type of the input provider. // +kubebuilder:validation:Enum=Static;GitHubBranch;GitHubTag;GitHubPullRequest;GitLabBranch;GitLabTag;GitLabMergeRequest;GitLabEnvironment;AzureDevOpsBranch;AzureDevOpsTag;AzureDevOpsPullRequest;OCIArtifactTag;ACRArtifactTag;ECRArtifactTag;GARArtifactTag // +required Type string `json:"type"` + // Credential specifies the type of credential that will be sent to the input provider. + // Supported values are: + // + // - ServiceAccountToken: The operator will generate a Kubernetes ServiceAccount + // token and send it as a bearer token to the provider. Currently supported only + // for the OCIArtifactTag type when connecting to OCI registries that support + // workload identity federation. If ServiceAccountName is not specified, the + // ServiceAccount of the operator will be used to generate the token. + // + // +kubebuilder:validation:Enum=ServiceAccountToken + // +optional + Credential string `json:"credential,omitempty"` + + // Audiences specifies the audience claim to be set in JWT credentials, + // like the ServiceAccountToken credential. Required when using JWT + // credentials. + // +optional + Audiences []string `json:"audiences,omitempty"` + // URL specifies the HTTP/S or OCI address of the input provider API. // When connecting to a Git provider, the URL should point to the repository address. // When connecting to an OCI provider, the URL should point to the OCI repository address. @@ -62,10 +84,13 @@ type ResourceSetInputProviderSpec struct { URL string `json:"url,omitempty"` // ServiceAccountName specifies the name of the Kubernetes ServiceAccount - // used for authentication with AWS, Azure or GCP services through - // workload identity federation features. If not specified, the - // authentication for these cloud providers will use the ServiceAccount - // of the operator (or any other environment authentication configuration). + // in the same namespace as the ResourceSetInputProvider used for + // authentication with workload identity federation features. If + // not specified, the authentication will use the ServiceAccount of the + // operator (or any other environment authentication configuration, e.g. + // for cloud providers). If Type is set to OCIArtifactTag and Credential + // is not set, the image pull secrets of the ServiceAccount are used to + // authenticate to the OCI registry. // +optional ServiceAccountName string `json:"serviceAccountName,omitempty"` diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index ba90bdac..159ac2a1 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -778,6 +778,11 @@ func (in *ResourceSetInputProviderList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResourceSetInputProviderSpec) DeepCopyInto(out *ResourceSetInputProviderSpec) { *out = *in + if in.Audiences != nil { + in, out := &in.Audiences, &out.Audiences + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.SecretRef != nil { in, out := &in.SecretRef, &out.SecretRef *out = new(meta.LocalObjectReference) diff --git a/config/crd/bases/fluxcd.controlplane.io_resourcesetinputproviders.yaml b/config/crd/bases/fluxcd.controlplane.io_resourcesetinputproviders.yaml index 918884f8..1eb5eeef 100644 --- a/config/crd/bases/fluxcd.controlplane.io_resourcesetinputproviders.yaml +++ b/config/crd/bases/fluxcd.controlplane.io_resourcesetinputproviders.yaml @@ -53,6 +53,14 @@ spec: description: ResourceSetInputProviderSpec defines the desired state of ResourceSetInputProvider properties: + audiences: + description: |- + Audiences specifies the audience claim to be set in JWT credentials, + like the ServiceAccountToken credential. Required when using JWT + credentials. + items: + type: string + type: array certSecretRef: description: |- CertSecretRef specifies the Kubernetes Secret containing either or both of @@ -71,6 +79,19 @@ spec: required: - name type: object + credential: + description: |- + Credential specifies the type of credential that will be sent to the input provider. + Supported values are: + + - ServiceAccountToken: The operator will generate a Kubernetes ServiceAccount + token and send it as a bearer token to the provider. Currently supported only + for the OCIArtifactTag type when connecting to OCI registries that support + workload identity federation. If ServiceAccountName is not specified, the + ServiceAccount of the operator will be used to generate the token. + enum: + - ServiceAccountToken + type: string defaultValues: additionalProperties: x-kubernetes-preserve-unknown-fields: true @@ -177,10 +198,13 @@ spec: serviceAccountName: description: |- ServiceAccountName specifies the name of the Kubernetes ServiceAccount - used for authentication with AWS, Azure or GCP services through - workload identity federation features. If not specified, the - authentication for these cloud providers will use the ServiceAccount - of the operator (or any other environment authentication configuration). + in the same namespace as the ResourceSetInputProvider used for + authentication with workload identity federation features. If + not specified, the authentication will use the ServiceAccount of the + operator (or any other environment authentication configuration, e.g. + for cloud providers). If Type is set to OCIArtifactTag and Credential + is not set, the image pull secrets of the ServiceAccount are used to + authenticate to the OCI registry. type: string skip: description: Skip defines whether we need to skip input provider response @@ -249,6 +273,17 @@ spec: ACRArtifactTag, ECRArtifactTag or GARArtifactTag rule: '!has(self.secretRef) || !(self.url == ''Static'' || (self.type.endsWith(''ArtifactTag'') && self.type != ''OCIArtifactTag''))' + - message: spec.credential can be set to 'ServiceAccountToken' only when + spec.type is 'OCIArtifactTag' + rule: '!has(self.credential) || (self.credential == ''ServiceAccountToken'' + && self.type == ''OCIArtifactTag'')' + - message: spec.audiences can be set only when spec.credential is set + to 'ServiceAccountToken' + rule: '!has(self.audiences) || size(self.audiences) == 0 || (has(self.credential) + && self.credential == ''ServiceAccountToken'')' + - message: spec.audiences must be set when spec.credential is set to 'ServiceAccountToken' + rule: '!has(self.credential) || self.credential != ''ServiceAccountToken'' + || (has(self.audiences) && size(self.audiences) > 0)' status: description: ResourceSetInputProviderStatus defines the observed state of ResourceSetInputProvider. diff --git a/docs/api/v1/resourcesetinputprovider.md b/docs/api/v1/resourcesetinputprovider.md index 775270ea..eb1bd02b 100644 --- a/docs/api/v1/resourcesetinputprovider.md +++ b/docs/api/v1/resourcesetinputprovider.md @@ -350,6 +350,7 @@ The `.spec.serviceAccountName` field can only be used with the following provide - `AzureDevOpsPullRequest` - `AzureDevOpsBranch` - `AzureDevOpsTag` +- `OCIArtifactTag` (when `.spec.credential` is set to `ServiceAccountToken`) - `ACRArtifactTag` - `ECRArtifactTag` - `GARArtifactTag` @@ -359,6 +360,41 @@ DevOps, [`.spec.secretRef`](#secret-based) is also not specified), the operator to authenticate using the environment credentials, i.e. either the identity of the node or the operator ServiceAccount. This is called *controller-level workload identity*. +##### ServiceAccountToken Credential for OCI Registries + +For the `OCIArtifactTag` provider [type](#type), the `.spec.credential` field can be set +to `ServiceAccountToken` to enable authentication using a Kubernetes ServiceAccount token. +When this credential type is used, the operator generates a ServiceAccount token and sends +it as a bearer token to the OCI registry. This is useful for connecting to OCI registries +that support workload identity federation. + +When using `ServiceAccountToken` credential, the `.spec.audiences` field is required and +specifies the audience claims to be included in the generated JWT token. The audience +values depend on the OCI registry provider being used. + +If `.spec.serviceAccountName` is specified, the token is generated for that ServiceAccount. +Otherwise, the operator's own ServiceAccount is used to generate the token. + +Example: + +```yaml +apiVersion: fluxcd.controlplane.io/v1 +kind: ResourceSetInputProvider +metadata: + name: oci-provider + namespace: default +spec: + type: OCIArtifactTag + url: oci://registry.example.com/my-repo + credential: ServiceAccountToken + audiences: + - "https://registry.example.com" + serviceAccountName: my-service-account # optional + filter: + semver: ">=1.0.0" + limit: 1 +``` + For configuring a Kubernetes ServiceAccount with workload identity, see the following documentation: - [Azure](https://fluxcd.io/flux/integrations/azure/#with-workload-identity-federation) diff --git a/go.mod b/go.mod index 2d9bbd15..ccfbf3c7 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/fluxcd/pkg/apis/event v0.21.0 github.com/fluxcd/pkg/apis/kustomize v1.14.0 github.com/fluxcd/pkg/apis/meta v1.24.0 - github.com/fluxcd/pkg/auth v0.34.0 + github.com/fluxcd/pkg/auth v0.34.1-0.20260118212638-6e3e8ddfe8fe github.com/fluxcd/pkg/cache v0.12.0 github.com/fluxcd/pkg/git v0.40.0 github.com/fluxcd/pkg/kustomize v1.24.0 diff --git a/go.sum b/go.sum index 7e91258b..344fd5f3 100644 --- a/go.sum +++ b/go.sum @@ -117,8 +117,8 @@ github.com/fluxcd/pkg/apis/kustomize v1.14.0 h1:PmWqMpRX0v7/aCAUNWfohe4o1qa9G3Cg github.com/fluxcd/pkg/apis/kustomize v1.14.0/go.mod h1:CGRpU9Od4ht5+MHL6QlMfWaW87U9UTfGVM5CM4PZ28I= github.com/fluxcd/pkg/apis/meta v1.24.0 h1:+e33T4OL9oqMWZSltsgImvi+/Punx42X9NqFlPesH6o= github.com/fluxcd/pkg/apis/meta v1.24.0/go.mod h1:UWsIbBPCxYvoVklr2mV2uLFBf/n17dNAmKFjRfApdDo= -github.com/fluxcd/pkg/auth v0.34.0 h1:lJFU7aW46vC6nOjpzQdnrz9XC1xXlaM5Z6d3qlozb8I= -github.com/fluxcd/pkg/auth v0.34.0/go.mod h1:BIz/zxLVz5o8EYQv+2c+ifAeaLq9wr4azXPdWYOU2AY= +github.com/fluxcd/pkg/auth v0.34.1-0.20260118212638-6e3e8ddfe8fe h1:NSz+6rUo31uy9owVgv8NCRbDNh48DQFOPEHVqUZTC5I= +github.com/fluxcd/pkg/auth v0.34.1-0.20260118212638-6e3e8ddfe8fe/go.mod h1:BIz/zxLVz5o8EYQv+2c+ifAeaLq9wr4azXPdWYOU2AY= github.com/fluxcd/pkg/cache v0.12.0 h1:mabABT3jIfuo84VbIW+qvfqMZ7PbM5tXQgQvA2uo2rc= github.com/fluxcd/pkg/cache v0.12.0/go.mod h1:HL/9cgBmwCdKIr3JH57rxrGdb7rOgX5Z1eJlHsaV1vE= github.com/fluxcd/pkg/envsubst v1.5.0 h1:S07mo+MkGhptdHA4pRze5HPKlc8tHxKswNdcMZi1WDY= diff --git a/internal/controller/resourcesetinputprovider_controller.go b/internal/controller/resourcesetinputprovider_controller.go index bf7d254e..a4e527a2 100644 --- a/internal/controller/resourcesetinputprovider_controller.go +++ b/internal/controller/resourcesetinputprovider_controller.go @@ -20,6 +20,7 @@ import ( "github.com/fluxcd/pkg/auth/aws" "github.com/fluxcd/pkg/auth/azure" "github.com/fluxcd/pkg/auth/gcp" + "github.com/fluxcd/pkg/auth/serviceaccounttoken" authutils "github.com/fluxcd/pkg/auth/utils" "github.com/fluxcd/pkg/cache" "github.com/fluxcd/pkg/git/github" @@ -700,7 +701,8 @@ func (r *ResourceSetInputProviderReconciler) callOCIProvider(ctx context.Context return res, nil } -var inputProviderToCloudProvider = map[string]string{ +var ociInputProviderToAuthProvider = map[string]string{ + fluxcdv1.InputProviderOCIArtifactTag: serviceaccounttoken.CredentialName, fluxcdv1.InputProviderACRArtifactTag: azure.ProviderName, fluxcdv1.InputProviderECRArtifactTag: aws.ProviderName, fluxcdv1.InputProviderGARArtifactTag: gcp.ProviderName, @@ -719,12 +721,19 @@ func (r *ResourceSetInputProviderReconciler) buildOCIOptions(ctx context.Context switch { // Configure workload identity for cloud providers. - case obj.Spec.Type != fluxcdv1.InputProviderOCIArtifactTag: + case obj.Spec.Type != fluxcdv1.InputProviderOCIArtifactTag || + obj.Spec.Credential == serviceaccounttoken.CredentialName: + authOpts := []auth.Option{ auth.WithClient(r.Client), auth.WithServiceAccountNamespace(obj.GetNamespace()), } + // Configure audiences. + if a := obj.Spec.Audiences; len(a) > 0 { + authOpts = append(authOpts, auth.WithAudiences(a...)) + } + // Configure service account. if s := obj.Spec.ServiceAccountName; s != "" { authOpts = append(authOpts, auth.WithServiceAccountName(s)) @@ -737,7 +746,7 @@ func (r *ResourceSetInputProviderReconciler) buildOCIOptions(ctx context.Context } // Build authenticator. - provider := inputProviderToCloudProvider[obj.Spec.Type] + provider := ociInputProviderToAuthProvider[obj.Spec.Type] authenticator, err := authutils.GetArtifactRegistryCredentials(ctx, provider, repo, authOpts...) if err != nil { return nil, fmt.Errorf("failed to get artifact registry credentials for '%s', provider '%s': %w", @@ -775,13 +784,13 @@ func (r *ResourceSetInputProviderReconciler) buildOCIOptions(ctx context.Context } opts = append(opts, crane.WithAuthFromKeychain(keychain)) } + } - // Configure TLS settings. - if tlsConfig != nil { - transport := http.DefaultTransport.(*http.Transport).Clone() - transport.TLSClientConfig = tlsConfig - opts = append(opts, crane.WithTransport(transport)) - } + // Configure TLS settings. + if tlsConfig != nil && obj.Spec.Type == fluxcdv1.InputProviderOCIArtifactTag { + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.TLSClientConfig = tlsConfig + opts = append(opts, crane.WithTransport(transport)) } return opts, nil diff --git a/internal/controller/resourcesetinputprovider_controller_oci_test.go b/internal/controller/resourcesetinputprovider_controller_oci_test.go index a18d44d5..61824b19 100644 --- a/internal/controller/resourcesetinputprovider_controller_oci_test.go +++ b/internal/controller/resourcesetinputprovider_controller_oci_test.go @@ -11,6 +11,8 @@ import ( "testing" "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/auth" + "github.com/fluxcd/pkg/auth/serviceaccounttoken" "github.com/fluxcd/pkg/runtime/conditions" kauth "github.com/google/go-containerregistry/pkg/authn/kubernetes" "github.com/google/go-containerregistry/pkg/crane" @@ -259,6 +261,89 @@ func TestResourceSetInputProviderReconciler_buildOCIOptions(t *testing.T) { } } +func TestResourceSetInputProviderReconciler_buildOCIOptions_ServiceAccountToken(t *testing.T) { + g := NewWithT(t) + + r := getResourceSetInputProviderReconciler(t) + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + ns, err := testEnv.CreateNamespace(ctx, "test-build-oci-options-sa-token") + g.Expect(err).NotTo(HaveOccurred()) + + // Create a ServiceAccount for the test. + sa := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-sa", + Namespace: ns.Name, + }, + } + err = testEnv.Create(ctx, sa) + g.Expect(err).NotTo(HaveOccurred()) + + t.Run("uses serviceaccounttoken provider with serviceAccountName", func(t *testing.T) { + g := NewWithT(t) + + // Enable the feature gate for ServiceAccountToken. + auth.EnableObjectLevelWorkloadIdentity() + + obj := &fluxcdv1.ResourceSetInputProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: ns.Name, + }, + Spec: fluxcdv1.ResourceSetInputProviderSpec{ + Type: fluxcdv1.InputProviderOCIArtifactTag, + ServiceAccountName: "test-sa", + Credential: serviceaccounttoken.CredentialName, + Audiences: []string{"test-audience"}, + }, + } + + const repo = "example.com/stefanprodan/podinfo" + + opts, err := r.buildOCIOptions(ctx, obj, repo, nil, nil) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(opts).NotTo(BeNil()) + }) + + t.Run("TLS config is applied with ServiceAccountToken credential", func(t *testing.T) { + g := NewWithT(t) + + // Enable the feature gate for ServiceAccountToken. + auth.EnableObjectLevelWorkloadIdentity() + + obj := &fluxcdv1.ResourceSetInputProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-tls", + Namespace: ns.Name, + }, + Spec: fluxcdv1.ResourceSetInputProviderSpec{ + Type: fluxcdv1.InputProviderOCIArtifactTag, + ServiceAccountName: "test-sa", + Credential: serviceaccounttoken.CredentialName, + Audiences: []string{"test-audience"}, + }, + } + + const repo = "example.com/stefanprodan/podinfo" + + tlsConfig := &tls.Config{ + ServerName: "server.example.com", + } + + opts, err := r.buildOCIOptions(ctx, obj, repo, tlsConfig, nil) + g.Expect(err).NotTo(HaveOccurred()) + + o := crane.GetOptions(opts...) + + // Validate TLS config is set. + g.Expect(o.Transport).NotTo(BeNil()) + g.Expect(o.Transport.(*http.Transport)).NotTo(BeNil()) + g.Expect(o.Transport.(*http.Transport).TLSClientConfig.ServerName).To(Equal("server.example.com")) + }) +} + func TestResourceSetInputProviderReconciler_InvalidOCIURL(t *testing.T) { g := NewWithT(t) diff --git a/internal/controller/resourcesetinputprovider_controller_test.go b/internal/controller/resourcesetinputprovider_controller_test.go index 8c94f299..ad5ccc7d 100644 --- a/internal/controller/resourcesetinputprovider_controller_test.go +++ b/internal/controller/resourcesetinputprovider_controller_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/auth/serviceaccounttoken" "github.com/fluxcd/pkg/runtime/conditions" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" @@ -427,6 +428,102 @@ func TestResourceSetInputProviderReconciler_ProviderAuthAndSecretsCompatiblity(t } } +func TestResourceSetInputProviderReconciler_CredentialAndAudiencesValidation(t *testing.T) { + g := NewWithT(t) + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + ns, err := testEnv.CreateNamespace(ctx, "test-credential-audiences-validation") + g.Expect(err).ToNot(HaveOccurred()) + + t.Run("credential can only be ServiceAccountToken when type is OCIArtifactTag", func(t *testing.T) { + g := NewWithT(t) + + for _, provider := range []string{ + fluxcdv1.InputProviderACRArtifactTag, + fluxcdv1.InputProviderECRArtifactTag, + fluxcdv1.InputProviderGARArtifactTag, + } { + obj := &fluxcdv1.ResourceSetInputProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-credential-" + strings.ToLower(provider), + Namespace: ns.Name, + }, + Spec: fluxcdv1.ResourceSetInputProviderSpec{ + Type: provider, + URL: "oci://example.com/owner/repo", + Credential: serviceaccounttoken.CredentialName, + Audiences: []string{"aud1"}, + }, + } + err := testEnv.Create(ctx, obj) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring( + "spec.credential can be set to 'ServiceAccountToken' only when spec.type is 'OCIArtifactTag'")) + } + }) + + t.Run("audiences can only be set when credential is ServiceAccountToken", func(t *testing.T) { + g := NewWithT(t) + + obj := &fluxcdv1.ResourceSetInputProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-audiences-without-credential", + Namespace: ns.Name, + }, + Spec: fluxcdv1.ResourceSetInputProviderSpec{ + Type: fluxcdv1.InputProviderOCIArtifactTag, + URL: "oci://example.com/owner/repo", + Audiences: []string{"aud1"}, + }, + } + err := testEnv.Create(ctx, obj) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring( + "spec.audiences can be set only when spec.credential is set to 'ServiceAccountToken'")) + }) + + t.Run("audiences must be set when credential is ServiceAccountToken", func(t *testing.T) { + g := NewWithT(t) + + obj := &fluxcdv1.ResourceSetInputProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-credential-without-audiences", + Namespace: ns.Name, + }, + Spec: fluxcdv1.ResourceSetInputProviderSpec{ + Type: fluxcdv1.InputProviderOCIArtifactTag, + URL: "oci://example.com/owner/repo", + Credential: serviceaccounttoken.CredentialName, + }, + } + err := testEnv.Create(ctx, obj) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring( + "spec.audiences must be set when spec.credential is set to 'ServiceAccountToken'")) + }) + + t.Run("valid credential and audiences configuration", func(t *testing.T) { + g := NewWithT(t) + + obj := &fluxcdv1.ResourceSetInputProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-valid-credential-audiences", + Namespace: ns.Name, + }, + Spec: fluxcdv1.ResourceSetInputProviderSpec{ + Type: fluxcdv1.InputProviderOCIArtifactTag, + URL: "oci://example.com/owner/repo", + Credential: serviceaccounttoken.CredentialName, + Audiences: []string{"aud1"}, + }, + } + err := testEnv.Create(ctx, obj, client.DryRunAll, client.FieldOwner(controllerName)) + g.Expect(err).ToNot(HaveOccurred()) + }) +} + func TestResourceSetInputProviderReconciler_makeFilters(t *testing.T) { r := getResourceSetInputProviderReconciler(t)