Merge pull request #321 from Nikhil-Ladha/sync-4.17

Syncing latest commits from devel to release-4.17 branch
This commit is contained in:
openshift-merge-bot[bot] 2024-06-20 06:49:55 +00:00 committed by GitHub
commit 9397576390
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
127 changed files with 1923 additions and 1235 deletions

View File

@ -37,15 +37,12 @@ queue_rules:
- "status-success=ci/centos/k8s-e2e-external-storage/1.27" - "status-success=ci/centos/k8s-e2e-external-storage/1.27"
- "status-success=ci/centos/k8s-e2e-external-storage/1.28" - "status-success=ci/centos/k8s-e2e-external-storage/1.28"
- "status-success=ci/centos/k8s-e2e-external-storage/1.29" - "status-success=ci/centos/k8s-e2e-external-storage/1.29"
- "status-success=ci/centos/k8s-e2e-external-storage/1.30"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.27" - "status-success=ci/centos/mini-e2e-helm/k8s-1.27"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.28" - "status-success=ci/centos/mini-e2e-helm/k8s-1.28"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.29" - "status-success=ci/centos/mini-e2e-helm/k8s-1.29"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.30"
- "status-success=ci/centos/mini-e2e/k8s-1.27" - "status-success=ci/centos/mini-e2e/k8s-1.27"
- "status-success=ci/centos/mini-e2e/k8s-1.28" - "status-success=ci/centos/mini-e2e/k8s-1.28"
- "status-success=ci/centos/mini-e2e/k8s-1.29" - "status-success=ci/centos/mini-e2e/k8s-1.29"
- "status-success=ci/centos/mini-e2e/k8s-1.30"
- "status-success=ci/centos/upgrade-tests-cephfs" - "status-success=ci/centos/upgrade-tests-cephfs"
- "status-success=ci/centos/upgrade-tests-rbd" - "status-success=ci/centos/upgrade-tests-rbd"
- and: - and:
@ -193,15 +190,12 @@ pull_request_rules:
- "status-success=ci/centos/k8s-e2e-external-storage/1.27" - "status-success=ci/centos/k8s-e2e-external-storage/1.27"
- "status-success=ci/centos/k8s-e2e-external-storage/1.28" - "status-success=ci/centos/k8s-e2e-external-storage/1.28"
- "status-success=ci/centos/k8s-e2e-external-storage/1.29" - "status-success=ci/centos/k8s-e2e-external-storage/1.29"
- "status-success=ci/centos/k8s-e2e-external-storage/1.30"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.27" - "status-success=ci/centos/mini-e2e-helm/k8s-1.27"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.28" - "status-success=ci/centos/mini-e2e-helm/k8s-1.28"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.29" - "status-success=ci/centos/mini-e2e-helm/k8s-1.29"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.30"
- "status-success=ci/centos/mini-e2e/k8s-1.27" - "status-success=ci/centos/mini-e2e/k8s-1.27"
- "status-success=ci/centos/mini-e2e/k8s-1.28" - "status-success=ci/centos/mini-e2e/k8s-1.28"
- "status-success=ci/centos/mini-e2e/k8s-1.29" - "status-success=ci/centos/mini-e2e/k8s-1.29"
- "status-success=ci/centos/mini-e2e/k8s-1.30"
- "status-success=ci/centos/upgrade-tests-cephfs" - "status-success=ci/centos/upgrade-tests-cephfs"
- "status-success=ci/centos/upgrade-tests-rbd" - "status-success=ci/centos/upgrade-tests-rbd"
- and: - and:
@ -283,15 +277,12 @@ pull_request_rules:
- "status-success=ci/centos/k8s-e2e-external-storage/1.27" - "status-success=ci/centos/k8s-e2e-external-storage/1.27"
- "status-success=ci/centos/k8s-e2e-external-storage/1.28" - "status-success=ci/centos/k8s-e2e-external-storage/1.28"
- "status-success=ci/centos/k8s-e2e-external-storage/1.29" - "status-success=ci/centos/k8s-e2e-external-storage/1.29"
- "status-success=ci/centos/k8s-e2e-external-storage/1.30"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.27" - "status-success=ci/centos/mini-e2e-helm/k8s-1.27"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.28" - "status-success=ci/centos/mini-e2e-helm/k8s-1.28"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.29" - "status-success=ci/centos/mini-e2e-helm/k8s-1.29"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.30"
- "status-success=ci/centos/mini-e2e/k8s-1.27" - "status-success=ci/centos/mini-e2e/k8s-1.27"
- "status-success=ci/centos/mini-e2e/k8s-1.28" - "status-success=ci/centos/mini-e2e/k8s-1.28"
- "status-success=ci/centos/mini-e2e/k8s-1.29" - "status-success=ci/centos/mini-e2e/k8s-1.29"
- "status-success=ci/centos/mini-e2e/k8s-1.30"
- "status-success=ci/centos/upgrade-tests-cephfs" - "status-success=ci/centos/upgrade-tests-cephfs"
- "status-success=ci/centos/upgrade-tests-rbd" - "status-success=ci/centos/upgrade-tests-rbd"
- "status-success=DCO" - "status-success=DCO"

View File

@ -1,9 +1,10 @@
# v3.11 Pending Release Notes # v3.12 Pending Release Notes
## Breaking Changes ## Breaking Changes
## Features ## Features
- kms: added Azure Key Vault as a supported KMS in [PR](https://github.com/ceph/ceph-csi/pull/4455) - deploy: podSecurityContexts can be configured for ceph-csi-cephfs chart in [PR](https://github.com/ceph/ceph-csi/pull/4664).
- deploy: podSecurityContexts can be configured for ceph-csi-rbd chart in [PR](https://github.com/ceph/ceph-csi/pull/4668)
## NOTE ## NOTE

View File

@ -6,7 +6,7 @@ require (
github.com/ghodss/yaml v1.0.0 github.com/ghodss/yaml v1.0.0
github.com/openshift/api v0.0.0-20240115183315-0793e918179d github.com/openshift/api v0.0.0-20240115183315-0793e918179d
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
k8s.io/api v0.30.0 k8s.io/api v0.30.1
) )
require ( require (
@ -23,7 +23,7 @@ require (
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apimachinery v0.30.0 // indirect k8s.io/apimachinery v0.30.1 // indirect
k8s.io/klog/v2 v2.120.1 // indirect k8s.io/klog/v2 v2.120.1 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect

View File

@ -5,6 +5,8 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@ -79,10 +81,10 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA= k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY=
k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM=
k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA= k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U=
k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=

View File

@ -1986,6 +1986,7 @@ message HTTPHeader {
// pod's hosts file. // pod's hosts file.
message HostAlias { message HostAlias {
// IP address of the host file entry. // IP address of the host file entry.
// +required
optional string ip = 1; optional string ip = 1;
// Hostnames for the above IP address. // Hostnames for the above IP address.
@ -2308,9 +2309,15 @@ message LoadBalancerStatus {
// +structType=atomic // +structType=atomic
message LocalObjectReference { message LocalObjectReference {
// Name of the referent. // Name of the referent.
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names // This field is effectively required, but due to backwards compatibility is
// allowed to be empty. Instances of this type with an empty value here are
// almost certainly wrong.
// TODO: Add other useful fields. apiVersion, kind, uid? // TODO: Add other useful fields. apiVersion, kind, uid?
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
// +optional // +optional
// +default=""
// +kubebuilder:default=""
// TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896.
optional string name = 1; optional string name = 1;
} }
@ -5838,6 +5845,8 @@ message ServiceSpec {
// not set, the implementation will apply its default routing strategy. If set // not set, the implementation will apply its default routing strategy. If set
// to "PreferClose", implementations should prioritize endpoints that are // to "PreferClose", implementations should prioritize endpoints that are
// topologically close (e.g., same zone). // topologically close (e.g., same zone).
// This is an alpha field and requires enabling ServiceTrafficDistribution feature.
// +featureGate=ServiceTrafficDistribution
// +optional // +optional
optional string trafficDistribution = 23; optional string trafficDistribution = 23;
} }

View File

@ -4113,7 +4113,8 @@ const (
// pod's hosts file. // pod's hosts file.
type HostAlias struct { type HostAlias struct {
// IP address of the host file entry. // IP address of the host file entry.
IP string `json:"ip,omitempty" protobuf:"bytes,1,opt,name=ip"` // +required
IP string `json:"ip" protobuf:"bytes,1,opt,name=ip"`
// Hostnames for the above IP address. // Hostnames for the above IP address.
// +listType=atomic // +listType=atomic
Hostnames []string `json:"hostnames,omitempty" protobuf:"bytes,2,rep,name=hostnames"` Hostnames []string `json:"hostnames,omitempty" protobuf:"bytes,2,rep,name=hostnames"`
@ -5366,6 +5367,8 @@ type ServiceSpec struct {
// not set, the implementation will apply its default routing strategy. If set // not set, the implementation will apply its default routing strategy. If set
// to "PreferClose", implementations should prioritize endpoints that are // to "PreferClose", implementations should prioritize endpoints that are
// topologically close (e.g., same zone). // topologically close (e.g., same zone).
// This is an alpha field and requires enabling ServiceTrafficDistribution feature.
// +featureGate=ServiceTrafficDistribution
// +optional // +optional
TrafficDistribution *string `json:"trafficDistribution,omitempty" protobuf:"bytes,23,opt,name=trafficDistribution"` TrafficDistribution *string `json:"trafficDistribution,omitempty" protobuf:"bytes,23,opt,name=trafficDistribution"`
} }
@ -6553,9 +6556,15 @@ type ObjectReference struct {
// +structType=atomic // +structType=atomic
type LocalObjectReference struct { type LocalObjectReference struct {
// Name of the referent. // Name of the referent.
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names // This field is effectively required, but due to backwards compatibility is
// allowed to be empty. Instances of this type with an empty value here are
// almost certainly wrong.
// TODO: Add other useful fields. apiVersion, kind, uid? // TODO: Add other useful fields. apiVersion, kind, uid?
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
// +optional // +optional
// +default=""
// +kubebuilder:default=""
// TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896.
Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
} }

View File

@ -1032,7 +1032,7 @@ func (LoadBalancerStatus) SwaggerDoc() map[string]string {
var map_LocalObjectReference = map[string]string{ var map_LocalObjectReference = map[string]string{
"": "LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace.", "": "LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace.",
"name": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", "name": "Name of the referent. This field is effectively required, but due to backwards compatibility is allowed to be empty. Instances of this type with an empty value here are almost certainly wrong. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names",
} }
func (LocalObjectReference) SwaggerDoc() map[string]string { func (LocalObjectReference) SwaggerDoc() map[string]string {
@ -2420,7 +2420,7 @@ var map_ServiceSpec = map[string]string{
"allocateLoadBalancerNodePorts": "allocateLoadBalancerNodePorts defines if NodePorts will be automatically allocated for services with type LoadBalancer. Default is \"true\". It may be set to \"false\" if the cluster load-balancer does not rely on NodePorts. If the caller requests specific NodePorts (by specifying a value), those requests will be respected, regardless of this field. This field may only be set for services with type LoadBalancer and will be cleared if the type is changed to any other type.", "allocateLoadBalancerNodePorts": "allocateLoadBalancerNodePorts defines if NodePorts will be automatically allocated for services with type LoadBalancer. Default is \"true\". It may be set to \"false\" if the cluster load-balancer does not rely on NodePorts. If the caller requests specific NodePorts (by specifying a value), those requests will be respected, regardless of this field. This field may only be set for services with type LoadBalancer and will be cleared if the type is changed to any other type.",
"loadBalancerClass": "loadBalancerClass is the class of the load balancer implementation this Service belongs to. If specified, the value of this field must be a label-style identifier, with an optional prefix, e.g. \"internal-vip\" or \"example.com/internal-vip\". Unprefixed names are reserved for end-users. This field can only be set when the Service type is 'LoadBalancer'. If not set, the default load balancer implementation is used, today this is typically done through the cloud provider integration, but should apply for any default implementation. If set, it is assumed that a load balancer implementation is watching for Services with a matching class. Any default load balancer implementation (e.g. cloud providers) should ignore Services that set this field. This field can only be set when creating or updating a Service to type 'LoadBalancer'. Once set, it can not be changed. This field will be wiped when a service is updated to a non 'LoadBalancer' type.", "loadBalancerClass": "loadBalancerClass is the class of the load balancer implementation this Service belongs to. If specified, the value of this field must be a label-style identifier, with an optional prefix, e.g. \"internal-vip\" or \"example.com/internal-vip\". Unprefixed names are reserved for end-users. This field can only be set when the Service type is 'LoadBalancer'. If not set, the default load balancer implementation is used, today this is typically done through the cloud provider integration, but should apply for any default implementation. If set, it is assumed that a load balancer implementation is watching for Services with a matching class. Any default load balancer implementation (e.g. cloud providers) should ignore Services that set this field. This field can only be set when creating or updating a Service to type 'LoadBalancer'. Once set, it can not be changed. This field will be wiped when a service is updated to a non 'LoadBalancer' type.",
"internalTrafficPolicy": "InternalTrafficPolicy describes how nodes distribute service traffic they receive on the ClusterIP. If set to \"Local\", the proxy will assume that pods only want to talk to endpoints of the service on the same node as the pod, dropping the traffic if there are no local endpoints. The default value, \"Cluster\", uses the standard behavior of routing to all endpoints evenly (possibly modified by topology and other features).", "internalTrafficPolicy": "InternalTrafficPolicy describes how nodes distribute service traffic they receive on the ClusterIP. If set to \"Local\", the proxy will assume that pods only want to talk to endpoints of the service on the same node as the pod, dropping the traffic if there are no local endpoints. The default value, \"Cluster\", uses the standard behavior of routing to all endpoints evenly (possibly modified by topology and other features).",
"trafficDistribution": "TrafficDistribution offers a way to express preferences for how traffic is distributed to Service endpoints. Implementations can use this field as a hint, but are not required to guarantee strict adherence. If the field is not set, the implementation will apply its default routing strategy. If set to \"PreferClose\", implementations should prioritize endpoints that are topologically close (e.g., same zone).", "trafficDistribution": "TrafficDistribution offers a way to express preferences for how traffic is distributed to Service endpoints. Implementations can use this field as a hint, but are not required to guarantee strict adherence. If the field is not set, the implementation will apply its default routing strategy. If set to \"PreferClose\", implementations should prioritize endpoints that are topologically close (e.g., same zone). This is an alpha field and requires enabling ServiceTrafficDistribution feature.",
} }
func (ServiceSpec) SwaggerDoc() map[string]string { func (ServiceSpec) SwaggerDoc() map[string]string {

View File

@ -55,12 +55,12 @@ gopkg.in/yaml.v2
# gopkg.in/yaml.v3 v3.0.1 # gopkg.in/yaml.v3 v3.0.1
## explicit ## explicit
gopkg.in/yaml.v3 gopkg.in/yaml.v3
# k8s.io/api v0.30.0 # k8s.io/api v0.30.1
## explicit; go 1.22.0 ## explicit; go 1.22.0
k8s.io/api/core/v1 k8s.io/api/core/v1
k8s.io/api/rbac/v1 k8s.io/api/rbac/v1
k8s.io/api/storage/v1 k8s.io/api/storage/v1
# k8s.io/apimachinery v0.30.0 # k8s.io/apimachinery v0.30.1
## explicit; go 1.22.0 ## explicit; go 1.22.0
k8s.io/apimachinery/pkg/api/resource k8s.io/apimachinery/pkg/api/resource
k8s.io/apimachinery/pkg/apis/meta/v1 k8s.io/apimachinery/pkg/apis/meta/v1

View File

@ -30,7 +30,7 @@ GOLANGCI_VERSION=v1.57.2
# external snapshotter version # external snapshotter version
# Refer: https://github.com/kubernetes-csi/external-snapshotter/releases # Refer: https://github.com/kubernetes-csi/external-snapshotter/releases
SNAPSHOT_VERSION=v7.0.2 SNAPSHOT_VERSION=v8.0.1
# "go test" configuration # "go test" configuration
# set to stdout or html to enable coverage reporting, disabled by default # set to stdout or html to enable coverage reporting, disabled by default
@ -54,10 +54,10 @@ ROOK_VERSION=v1.12.1
ROOK_CEPH_CLUSTER_IMAGE=quay.io/ceph/ceph:v18 ROOK_CEPH_CLUSTER_IMAGE=quay.io/ceph/ceph:v18
# CSI sidecar version # CSI sidecar version
CSI_ATTACHER_VERSION=v4.5.1 CSI_ATTACHER_VERSION=v4.6.1
CSI_SNAPSHOTTER_VERSION=v7.0.2 CSI_SNAPSHOTTER_VERSION=v8.0.1
CSI_RESIZER_VERSION=v1.10.1 CSI_RESIZER_VERSION=v1.11.1
CSI_PROVISIONER_VERSION=v4.0.1 CSI_PROVISIONER_VERSION=v5.0.1
CSI_NODE_DRIVER_REGISTRAR_VERSION=v2.10.1 CSI_NODE_DRIVER_REGISTRAR_VERSION=v2.10.1
# e2e settings # e2e settings

View File

@ -129,6 +129,7 @@ charts and their default values.
| `nodeplugin.plugin.image.repository` | Nodeplugin image repository URL | `quay.io/cephcsi/cephcsi` | | `nodeplugin.plugin.image.repository` | Nodeplugin image repository URL | `quay.io/cephcsi/cephcsi` |
| `nodeplugin.plugin.image.tag` | Image tag | `canary` | | `nodeplugin.plugin.image.tag` | Image tag | `canary` |
| `nodeplugin.plugin.image.pullPolicy` | Image pull policy | `IfNotPresent` | | `nodeplugin.plugin.image.pullPolicy` | Image pull policy | `IfNotPresent` |
| `nodeplugin.podSecurityContext` | Specifies pod-level security context. | `{}` |
| `nodeplugin.nodeSelector` | Kubernetes `nodeSelector` to add to the Daemonset | `{}` | | `nodeplugin.nodeSelector` | Kubernetes `nodeSelector` to add to the Daemonset | `{}` |
| `nodeplugin.tolerations` | List of Kubernetes `tolerations` to add to the Daemonset | `{}` | | `nodeplugin.tolerations` | List of Kubernetes `tolerations` to add to the Daemonset | `{}` |
| `nodeplugin.forcecephkernelclient` | Set to true to enable Ceph Kernel clients on kernel < 4.17 which support quotas | `true` | | `nodeplugin.forcecephkernelclient` | Set to true to enable Ceph Kernel clients on kernel < 4.17 which support quotas | `true` |
@ -145,17 +146,17 @@ charts and their default values.
| `provisioner.imagePullSecrets` | Specifies imagePullSecrets for containers | `[]` | | `provisioner.imagePullSecrets` | Specifies imagePullSecrets for containers | `[]` |
| `provisioner.profiling.enabled` | Specifies whether profiling should be enabled | `false` | | `provisioner.profiling.enabled` | Specifies whether profiling should be enabled | `false` |
| `provisioner.provisioner.image.repository` | Specifies the csi-provisioner image repository URL | `registry.k8s.io/sig-storage/csi-provisioner` | | `provisioner.provisioner.image.repository` | Specifies the csi-provisioner image repository URL | `registry.k8s.io/sig-storage/csi-provisioner` |
| `provisioner.provisioner.image.tag` | Specifies image tag | `v4.0.1` | | `provisioner.provisioner.image.tag` | Specifies image tag | `v5.0.1` |
| `provisioner.provisioner.image.pullPolicy` | Specifies pull policy | `IfNotPresent` | | `provisioner.provisioner.image.pullPolicy` | Specifies pull policy | `IfNotPresent` |
| `provisioner.provisioner.image.extraArgs` | Specifies extra arguments for the provisioner sidecar | `[]` | | `provisioner.provisioner.image.extraArgs` | Specifies extra arguments for the provisioner sidecar | `[]` |
| `provisioner.resizer.image.repository` | Specifies the csi-resizer image repository URL | `registry.k8s.io/sig-storage/csi-resizer` | | `provisioner.resizer.image.repository` | Specifies the csi-resizer image repository URL | `registry.k8s.io/sig-storage/csi-resizer` |
| `provisioner.resizer.image.tag` | Specifies image tag | `v1.10.1` | | `provisioner.resizer.image.tag` | Specifies image tag | `v1.11.1` |
| `provisioner.resizer.image.pullPolicy` | Specifies pull policy | `IfNotPresent` | | `provisioner.resizer.image.pullPolicy` | Specifies pull policy | `IfNotPresent` |
| `provisioner.resizer.image.extraArgs` | Specifies extra arguments for the resizer sidecar | `[]` | | `provisioner.resizer.image.extraArgs` | Specifies extra arguments for the resizer sidecar | `[]` |
| `provisioner.resizer.name` | Specifies the name of csi-resizer sidecar | `resizer` | | `provisioner.resizer.name` | Specifies the name of csi-resizer sidecar | `resizer` |
| `provisioner.resizer.enabled` | Specifies whether resizer sidecar is enabled | `true` | | `provisioner.resizer.enabled` | Specifies whether resizer sidecar is enabled | `true` |
| `provisioner.snapshotter.image.repository` | Specifies the csi-snapshotter image repository URL | `registry.k8s.io/sig-storage/csi-snapshotter` | | `provisioner.snapshotter.image.repository` | Specifies the csi-snapshotter image repository URL | `registry.k8s.io/sig-storage/csi-snapshotter` |
| `provisioner.snapshotter.image.tag` | Specifies image tag | `v7.0.2` | | `provisioner.snapshotter.image.tag` | Specifies image tag | `v8.0.1` |
| `provisioner.snapshotter.image.pullPolicy` | Specifies pull policy | `IfNotPresent` | | `provisioner.snapshotter.image.pullPolicy` | Specifies pull policy | `IfNotPresent` |
| `provisioner.snapshotter.image.extraArgs` | Specifies extra arguments for the snapshotter sidecar | `[]` | | `provisioner.snapshotter.image.extraArgs` | Specifies extra arguments for the snapshotter sidecar | `[]` |
| `provisioner.snapshotter.args.enableVolumeGroupSnapshots` | enables the creation of volume group snapshots | `false` | | `provisioner.snapshotter.args.enableVolumeGroupSnapshots` | enables the creation of volume group snapshots | `false` |
@ -163,6 +164,7 @@ charts and their default values.
| `provisioner.tolerations` | Specifies the tolerations for provisioner deployment | `{}` | | `provisioner.tolerations` | Specifies the tolerations for provisioner deployment | `{}` |
| `provisioner.affinity` | Specifies the affinity for provisioner deployment | `{}` | | `provisioner.affinity` | Specifies the affinity for provisioner deployment | `{}` |
| `provisioner.podSecurityPolicy.enabled` | Specifies whether podSecurityPolicy is enabled | `false` | | `provisioner.podSecurityPolicy.enabled` | Specifies whether podSecurityPolicy is enabled | `false` |
| `provisioner.podSecurityContext` | Specifies pod-level security context. | `{}` |
| `provisionerSocketFile` | The filename of the provisioner socket | `csi-provisioner.sock` | | `provisionerSocketFile` | The filename of the provisioner socket | `csi-provisioner.sock` |
| `pluginSocketFile` | The filename of the plugin socket | `csi.sock` | | `pluginSocketFile` | The filename of the plugin socket | `csi.sock` |
| `readAffinity.enabled` | Enable read affinity for CephFS subvolumes. Recommended to set to true if running kernel 5.8 or newer. | `false` | | `readAffinity.enabled` | Enable read affinity for CephFS subvolumes. Recommended to set to true if running kernel 5.8 or newer. | `false` |

View File

@ -28,6 +28,7 @@ spec:
heritage: {{ .Release.Service }} heritage: {{ .Release.Service }}
{{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }} {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}
spec: spec:
securityContext: {{ toYaml .Values.nodeplugin.podSecurityContext | nindent 8 }}
serviceAccountName: {{ include "ceph-csi-cephfs.serviceAccountName.nodeplugin" . }} serviceAccountName: {{ include "ceph-csi-cephfs.serviceAccountName.nodeplugin" . }}
{{- if .Values.nodeplugin.priorityClassName }} {{- if .Values.nodeplugin.priorityClassName }}
priorityClassName: {{ .Values.nodeplugin.priorityClassName }} priorityClassName: {{ .Values.nodeplugin.priorityClassName }}

View File

@ -57,6 +57,7 @@ spec:
{{ toYaml .Values.provisioner.affinity | indent 8 -}} {{ toYaml .Values.provisioner.affinity | indent 8 -}}
{{- end -}} {{- end -}}
{{- end }} {{- end }}
securityContext: {{ toYaml .Values.provisioner.podSecurityContext | nindent 8 }}
serviceAccountName: {{ include "ceph-csi-cephfs.serviceAccountName.provisioner" . }} serviceAccountName: {{ include "ceph-csi-cephfs.serviceAccountName.provisioner" . }}
hostNetwork: {{ .Values.provisioner.enableHostNetwork }} hostNetwork: {{ .Values.provisioner.enableHostNetwork }}
{{- if .Values.provisioner.priorityClassName }} {{- if .Values.provisioner.priorityClassName }}

View File

@ -127,6 +127,8 @@ nodeplugin:
affinity: {} affinity: {}
podSecurityContext: {}
# Set to true to enable Ceph Kernel clients # Set to true to enable Ceph Kernel clients
# on kernel < 4.17 which support quotas # on kernel < 4.17 which support quotas
# forcecephkernelclient: true # forcecephkernelclient: true
@ -202,7 +204,7 @@ provisioner:
provisioner: provisioner:
image: image:
repository: registry.k8s.io/sig-storage/csi-provisioner repository: registry.k8s.io/sig-storage/csi-provisioner
tag: v4.0.1 tag: v5.0.1
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
resources: {} resources: {}
## For further options, check ## For further options, check
@ -217,7 +219,7 @@ provisioner:
enabled: true enabled: true
image: image:
repository: registry.k8s.io/sig-storage/csi-resizer repository: registry.k8s.io/sig-storage/csi-resizer
tag: v1.10.1 tag: v1.11.1
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
resources: {} resources: {}
## For further options, check ## For further options, check
@ -227,7 +229,7 @@ provisioner:
snapshotter: snapshotter:
image: image:
repository: registry.k8s.io/sig-storage/csi-snapshotter repository: registry.k8s.io/sig-storage/csi-snapshotter
tag: v7.0.2 tag: v8.0.1
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
resources: {} resources: {}
## For further options, check ## For further options, check
@ -244,6 +246,8 @@ provisioner:
affinity: {} affinity: {}
podSecurityContext: {}
# readAffinity: # readAffinity:
# Enable read affinity for CephFS subvolumes. Recommended to # Enable read affinity for CephFS subvolumes. Recommended to
# set to true if running kernel 5.8 or newer. # set to true if running kernel 5.8 or newer.

View File

@ -131,6 +131,7 @@ charts and their default values.
| `nodeplugin.plugin.image.repository` | Nodeplugin image repository URL | `quay.io/cephcsi/cephcsi` | | `nodeplugin.plugin.image.repository` | Nodeplugin image repository URL | `quay.io/cephcsi/cephcsi` |
| `nodeplugin.plugin.image.tag` | Image tag | `canary` | | `nodeplugin.plugin.image.tag` | Image tag | `canary` |
| `nodeplugin.plugin.image.pullPolicy` | Image pull policy | `IfNotPresent` | | `nodeplugin.plugin.image.pullPolicy` | Image pull policy | `IfNotPresent` |
| `nodeplugin.podSecurityContext` | Specifies pod-level security context. | `{}` |
| `nodeplugin.nodeSelector` | Kubernetes `nodeSelector` to add to the Daemonset | `{}` | | `nodeplugin.nodeSelector` | Kubernetes `nodeSelector` to add to the Daemonset | `{}` |
| `nodeplugin.tolerations` | List of Kubernetes `tolerations` to add to the Daemonset | `{}` | | `nodeplugin.tolerations` | List of Kubernetes `tolerations` to add to the Daemonset | `{}` |
| `nodeplugin.podSecurityPolicy.enabled` | If true, create & use [Pod Security Policy resources](https://kubernetes.io/docs/concepts/policy/pod-security-policy/). | `false` | | `nodeplugin.podSecurityPolicy.enabled` | If true, create & use [Pod Security Policy resources](https://kubernetes.io/docs/concepts/policy/pod-security-policy/). | `false` |
@ -151,9 +152,10 @@ charts and their default values.
| `provisioner.imagePullSecrets` | Specifies imagePullSecrets for containers | `[]` | | `provisioner.imagePullSecrets` | Specifies imagePullSecrets for containers | `[]` |
| `provisioner.profiling.enabled` | Specifies whether profiling should be enabled | `false` | | `provisioner.profiling.enabled` | Specifies whether profiling should be enabled | `false` |
| `provisioner.provisioner.image.repository` | Specifies the csi-provisioner image repository URL | `registry.k8s.io/sig-storage/csi-provisioner` | | `provisioner.provisioner.image.repository` | Specifies the csi-provisioner image repository URL | `registry.k8s.io/sig-storage/csi-provisioner` |
| `provisioner.provisioner.image.tag` | Specifies image tag | `v4.0.1` | | `provisioner.provisioner.image.tag` | Specifies image tag | `v5.0.1` |
| `provisioner.provisioner.image.pullPolicy` | Specifies pull policy | `IfNotPresent` | | `provisioner.provisioner.image.pullPolicy` | Specifies pull policy | `IfNotPresent` |
| `provisioner.provisioner.image.extraArgs` | Specifies extra arguments for the provisioner sidecar | `[]` | | `provisioner.provisioner.image.extraArgs` | Specifies extra arguments for the provisioner sidecar | `[]` |
| `provisioner.podSecurityContext` | Specifies pod-level security context. | `{}` |
| `provisioner.snapshotter.args.enableVolumeGroupSnapshots` | enables the creation of volume group snapshots | `false` | | `provisioner.snapshotter.args.enableVolumeGroupSnapshots` | enables the creation of volume group snapshots | `false` |
| `provisioner.attacher.image.repository` | Specifies the csi-attacher image repository URL | `registry.k8s.io/sig-storage/csi-attacher` | | `provisioner.attacher.image.repository` | Specifies the csi-attacher image repository URL | `registry.k8s.io/sig-storage/csi-attacher` |
| `provisioner.attacher.image.tag` | Specifies image tag | `v4.5.` | | `provisioner.attacher.image.tag` | Specifies image tag | `v4.5.` |
@ -162,13 +164,13 @@ charts and their default values.
| `provisioner.attacher.name` | Specifies the name of csi-attacher sidecar | `attacher` | | `provisioner.attacher.name` | Specifies the name of csi-attacher sidecar | `attacher` |
| `provisioner.attacher.enabled` | Specifies whether attacher sidecar is enabled | `true` | | `provisioner.attacher.enabled` | Specifies whether attacher sidecar is enabled | `true` |
| `provisioner.resizer.image.repository` | Specifies the csi-resizer image repository URL | `registry.k8s.io/sig-storage/csi-resizer` | | `provisioner.resizer.image.repository` | Specifies the csi-resizer image repository URL | `registry.k8s.io/sig-storage/csi-resizer` |
| `provisioner.resizer.image.tag` | Specifies image tag | `v1.10.1` | | `provisioner.resizer.image.tag` | Specifies image tag | `v1.11.1` |
| `provisioner.resizer.image.pullPolicy` | Specifies pull policy | `IfNotPresent` | | `provisioner.resizer.image.pullPolicy` | Specifies pull policy | `IfNotPresent` |
| `provisioner.resizer.image.extraArgs` | Specifies extra arguments for the resizer sidecar | `[]` | | `provisioner.resizer.image.extraArgs` | Specifies extra arguments for the resizer sidecar | `[]` |
| `provisioner.resizer.name` | Specifies the name of csi-resizer sidecar | `resizer` | | `provisioner.resizer.name` | Specifies the name of csi-resizer sidecar | `resizer` |
| `provisioner.resizer.enabled` | Specifies whether resizer sidecar is enabled | `true` | | `provisioner.resizer.enabled` | Specifies whether resizer sidecar is enabled | `true` |
| `provisioner.snapshotter.image.repository` | Specifies the csi-snapshotter image repository URL | `registry.k8s.io/sig-storage/csi-snapshotter` | | `provisioner.snapshotter.image.repository` | Specifies the csi-snapshotter image repository URL | `registry.k8s.io/sig-storage/csi-snapshotter` |
| `provisioner.snapshotter.image.tag` | Specifies image tag | `v7.0.2` | | `provisioner.snapshotter.image.tag` | Specifies image tag | `v8.0.1` |
| `provisioner.snapshotter.image.pullPolicy` | Specifies pull policy | `IfNotPresent` | | `provisioner.snapshotter.image.pullPolicy` | Specifies pull policy | `IfNotPresent` |
| `provisioner.snapshotter.image.extraArgs` | Specifies extra arguments for the snapshotter sidecar | `[]` | | `provisioner.snapshotter.image.extraArgs` | Specifies extra arguments for the snapshotter sidecar | `[]` |
| `provisioner.nodeSelector` | Specifies the node selector for provisioner deployment | `{}` | | `provisioner.nodeSelector` | Specifies the node selector for provisioner deployment | `{}` |

View File

@ -28,6 +28,7 @@ spec:
heritage: {{ .Release.Service }} heritage: {{ .Release.Service }}
{{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }} {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}
spec: spec:
securityContext: {{ toYaml .Values.nodeplugin.podSecurityContext | nindent 8 }}
serviceAccountName: {{ include "ceph-csi-rbd.serviceAccountName.nodeplugin" . }} serviceAccountName: {{ include "ceph-csi-rbd.serviceAccountName.nodeplugin" . }}
hostNetwork: true hostNetwork: true
hostPID: true hostPID: true
@ -56,7 +57,7 @@ spec:
- "--csi-addons-endpoint=$(CSI_ADDONS_ENDPOINT)" - "--csi-addons-endpoint=$(CSI_ADDONS_ENDPOINT)"
- "--v={{ .Values.logLevel }}" - "--v={{ .Values.logLevel }}"
- "--drivername=$(DRIVER_NAME)" - "--drivername=$(DRIVER_NAME)"
{{- if .Values.topology.enabled }} {{- if .Values.topology.domainLabels }}
- "--domainlabels={{ .Values.topology.domainLabels | join "," }}" - "--domainlabels={{ .Values.topology.domainLabels | join "," }}"
{{- end }} {{- end }}
{{- if .Values.nodeplugin.profiling.enabled }} {{- if .Values.nodeplugin.profiling.enabled }}

View File

@ -81,7 +81,7 @@ rules:
resources: ["persistentvolumeclaims/status"] resources: ["persistentvolumeclaims/status"]
verbs: ["update", "patch"] verbs: ["update", "patch"]
{{- end }} {{- end }}
{{- if .Values.topology.enabled }} {{- if .Values.topology.domainLabels }}
- apiGroups: [""] - apiGroups: [""]
resources: ["nodes"] resources: ["nodes"]
verbs: ["get", "list","watch"] verbs: ["get", "list","watch"]

View File

@ -57,6 +57,7 @@ spec:
{{ toYaml .Values.provisioner.affinity | indent 8 -}} {{ toYaml .Values.provisioner.affinity | indent 8 -}}
{{- end -}} {{- end -}}
{{- end }} {{- end }}
securityContext: {{ toYaml .Values.provisioner.podSecurityContext | nindent 8 }}
serviceAccountName: {{ include "ceph-csi-rbd.serviceAccountName.provisioner" . }} serviceAccountName: {{ include "ceph-csi-rbd.serviceAccountName.provisioner" . }}
hostNetwork: {{ .Values.provisioner.enableHostNetwork }} hostNetwork: {{ .Values.provisioner.enableHostNetwork }}
{{- if .Values.provisioner.priorityClassName }} {{- if .Values.provisioner.priorityClassName }}
@ -144,9 +145,6 @@ spec:
- "--extra-create-metadata=true" - "--extra-create-metadata=true"
- "--feature-gates=HonorPVReclaimPolicy=true" - "--feature-gates=HonorPVReclaimPolicy=true"
- "--prevent-volume-mode-conversion=true" - "--prevent-volume-mode-conversion=true"
{{- if .Values.topology.enabled }}
- "--feature-gates=Topology=true"
{{- end }}
{{- range .Values.provisioner.provisioner.extraArgs }} {{- range .Values.provisioner.provisioner.extraArgs }}
- "--{{ . }}" - "--{{ . }}"
{{- end }} {{- end }}

View File

@ -156,6 +156,8 @@ nodeplugin:
affinity: {} affinity: {}
podSecurityContext: {}
provisioner: provisioner:
name: provisioner name: provisioner
replicaCount: 3 replicaCount: 3
@ -241,7 +243,7 @@ provisioner:
provisioner: provisioner:
image: image:
repository: registry.k8s.io/sig-storage/csi-provisioner repository: registry.k8s.io/sig-storage/csi-provisioner
tag: v4.0.1 tag: v5.0.1
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
resources: {} resources: {}
## For further options, check ## For further options, check
@ -256,7 +258,7 @@ provisioner:
enabled: true enabled: true
image: image:
repository: registry.k8s.io/sig-storage/csi-attacher repository: registry.k8s.io/sig-storage/csi-attacher
tag: v4.5.1 tag: v4.6.1
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
resources: {} resources: {}
## For further options, check ## For further options, check
@ -268,7 +270,7 @@ provisioner:
enabled: true enabled: true
image: image:
repository: registry.k8s.io/sig-storage/csi-resizer repository: registry.k8s.io/sig-storage/csi-resizer
tag: v1.10.1 tag: v1.11.1
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
resources: {} resources: {}
## For further options, check ## For further options, check
@ -278,7 +280,7 @@ provisioner:
snapshotter: snapshotter:
image: image:
repository: registry.k8s.io/sig-storage/csi-snapshotter repository: registry.k8s.io/sig-storage/csi-snapshotter
tag: v7.0.2 tag: v8.0.1
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
resources: {} resources: {}
## For further options, check ## For further options, check
@ -295,10 +297,9 @@ provisioner:
affinity: {} affinity: {}
podSecurityContext: {}
topology: topology:
# Specifies whether topology based provisioning support should
# be exposed by CSI
enabled: false
# domainLabels define which node labels to use as domains # domainLabels define which node labels to use as domains
# for CSI nodeplugins to advertise their domains # for CSI nodeplugins to advertise their domains
# NOTE: the value here serves as an example and needs to be # NOTE: the value here serves as an example and needs to be

View File

@ -4,6 +4,11 @@ ARG BASE_IMAGE
FROM ${BASE_IMAGE} as updated_base FROM ${BASE_IMAGE} as updated_base
# Since CentOS Stream 8 is EOL, update the config to use vault.centos.org for CentOS Stream 8
# TODO: remove once https://github.com/ceph/ceph-csi/issues/4659 is fixed.
RUN sed -i 's|^mirrorlist=|#mirrorlist=|g' /etc/yum.repos.d/*.repo && \
sed -i 's|^#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/*.repo
# TODO: remove the following cmd, when issues # TODO: remove the following cmd, when issues
# https://github.com/ceph/ceph-container/issues/2034 # https://github.com/ceph/ceph-container/issues/2034
# https://github.com/ceph/ceph-container/issues/2141 are fixed. # https://github.com/ceph/ceph-container/issues/2141 are fixed.

View File

@ -92,14 +92,13 @@ spec:
- name: ceph-csi-encryption-kms-config - name: ceph-csi-encryption-kms-config
mountPath: /etc/ceph-csi-encryption-kms-config/ mountPath: /etc/ceph-csi-encryption-kms-config/
- name: csi-provisioner - name: csi-provisioner
image: registry.k8s.io/sig-storage/csi-provisioner:v4.0.1 image: registry.k8s.io/sig-storage/csi-provisioner:v5.0.1
args: args:
- "--csi-address=$(ADDRESS)" - "--csi-address=$(ADDRESS)"
- "--v=1" - "--v=1"
- "--timeout=150s" - "--timeout=150s"
- "--leader-election=true" - "--leader-election=true"
- "--retry-interval-start=500ms" - "--retry-interval-start=500ms"
- "--feature-gates=Topology=false"
- "--feature-gates=HonorPVReclaimPolicy=true" - "--feature-gates=HonorPVReclaimPolicy=true"
- "--prevent-volume-mode-conversion=true" - "--prevent-volume-mode-conversion=true"
- "--extra-create-metadata=true" - "--extra-create-metadata=true"
@ -111,7 +110,7 @@ spec:
- name: socket-dir - name: socket-dir
mountPath: /csi mountPath: /csi
- name: csi-resizer - name: csi-resizer
image: registry.k8s.io/sig-storage/csi-resizer:v1.10.1 image: registry.k8s.io/sig-storage/csi-resizer:v1.11.1
args: args:
- "--csi-address=$(ADDRESS)" - "--csi-address=$(ADDRESS)"
- "--v=1" - "--v=1"
@ -128,7 +127,7 @@ spec:
- name: socket-dir - name: socket-dir
mountPath: /csi mountPath: /csi
- name: csi-snapshotter - name: csi-snapshotter
image: registry.k8s.io/sig-storage/csi-snapshotter:v7.0.2 image: registry.k8s.io/sig-storage/csi-snapshotter:v8.0.1
args: args:
- "--csi-address=$(ADDRESS)" - "--csi-address=$(ADDRESS)"
- "--v=1" - "--v=1"

View File

@ -73,7 +73,7 @@ spec:
- name: keys-tmp-dir - name: keys-tmp-dir
mountPath: /tmp/csi/keys mountPath: /tmp/csi/keys
- name: csi-provisioner - name: csi-provisioner
image: registry.k8s.io/sig-storage/csi-provisioner:v4.0.1 image: registry.k8s.io/sig-storage/csi-provisioner:v5.0.1
args: args:
- "--csi-address=$(ADDRESS)" - "--csi-address=$(ADDRESS)"
- "--v=1" - "--v=1"
@ -90,7 +90,7 @@ spec:
- name: socket-dir - name: socket-dir
mountPath: /csi mountPath: /csi
- name: csi-resizer - name: csi-resizer
image: registry.k8s.io/sig-storage/csi-resizer:v1.10.1 image: registry.k8s.io/sig-storage/csi-resizer:v1.11.1
args: args:
- "--csi-address=$(ADDRESS)" - "--csi-address=$(ADDRESS)"
- "--v=1" - "--v=1"
@ -107,7 +107,7 @@ spec:
- name: socket-dir - name: socket-dir
mountPath: /csi mountPath: /csi
- name: csi-snapshotter - name: csi-snapshotter
image: registry.k8s.io/sig-storage/csi-snapshotter:v7.0.2 image: registry.k8s.io/sig-storage/csi-snapshotter:v8.0.1
args: args:
- "--csi-address=$(ADDRESS)" - "--csi-address=$(ADDRESS)"
- "--v=1" - "--v=1"

View File

@ -104,15 +104,13 @@ spec:
mountPath: /run/secrets/tokens mountPath: /run/secrets/tokens
readOnly: true readOnly: true
- name: csi-provisioner - name: csi-provisioner
image: registry.k8s.io/sig-storage/csi-provisioner:v4.0.1 image: registry.k8s.io/sig-storage/csi-provisioner:v5.0.1
args: args:
- "--csi-address=$(ADDRESS)" - "--csi-address=$(ADDRESS)"
- "--v=1" - "--v=1"
- "--timeout=150s" - "--timeout=150s"
- "--retry-interval-start=500ms" - "--retry-interval-start=500ms"
- "--leader-election=true" - "--leader-election=true"
# set it to true to use topology based provisioning
- "--feature-gates=Topology=false"
- "--feature-gates=HonorPVReclaimPolicy=true" - "--feature-gates=HonorPVReclaimPolicy=true"
- "--prevent-volume-mode-conversion=true" - "--prevent-volume-mode-conversion=true"
# if fstype is not specified in storageclass, ext4 is default # if fstype is not specified in storageclass, ext4 is default
@ -126,7 +124,7 @@ spec:
- name: socket-dir - name: socket-dir
mountPath: /csi mountPath: /csi
- name: csi-snapshotter - name: csi-snapshotter
image: registry.k8s.io/sig-storage/csi-snapshotter:v7.0.2 image: registry.k8s.io/sig-storage/csi-snapshotter:v8.0.1
args: args:
- "--csi-address=$(ADDRESS)" - "--csi-address=$(ADDRESS)"
- "--v=1" - "--v=1"
@ -142,7 +140,7 @@ spec:
- name: socket-dir - name: socket-dir
mountPath: /csi mountPath: /csi
- name: csi-attacher - name: csi-attacher
image: registry.k8s.io/sig-storage/csi-attacher:v4.5.1 image: registry.k8s.io/sig-storage/csi-attacher:v4.6.1
args: args:
- "--v=1" - "--v=1"
- "--csi-address=$(ADDRESS)" - "--csi-address=$(ADDRESS)"
@ -157,7 +155,7 @@ spec:
- name: socket-dir - name: socket-dir
mountPath: /csi mountPath: /csi
- name: csi-resizer - name: csi-resizer
image: registry.k8s.io/sig-storage/csi-resizer:v1.10.1 image: registry.k8s.io/sig-storage/csi-resizer:v1.11.1
args: args:
- "--csi-address=$(ADDRESS)" - "--csi-address=$(ADDRESS)"
- "--v=1" - "--v=1"

View File

@ -240,9 +240,6 @@ type yamlResourceNamespaced struct {
// set the number of replicas in a Deployment to 1. // set the number of replicas in a Deployment to 1.
oneReplica bool oneReplica bool
// enable topology support (for RBD)
enableTopology bool
// enable read affinity support (for RBD) // enable read affinity support (for RBD)
enableReadAffinity bool enableReadAffinity bool
} }
@ -257,10 +254,6 @@ func (yrn *yamlResourceNamespaced) Do(action kubectlAction) error {
data = oneReplicaDeployYaml(data) data = oneReplicaDeployYaml(data)
} }
if yrn.enableTopology {
data = enableTopologyInTemplate(data)
}
if yrn.domainLabel != "" { if yrn.domainLabel != "" {
data = addTopologyDomainsToDSYaml(data, yrn.domainLabel) data = addTopologyDomainsToDSYaml(data, yrn.domainLabel)
} }

View File

@ -58,6 +58,13 @@ func isRetryableAPIError(err error) bool {
return true return true
} }
// "pod nfs-820 does not have a host assigned" seems to get reported
// when a Pod is not completely started yet, or was restarted while
// trying to access it
if strings.Contains(err.Error(), "does not have a host assigned") {
return true
}
return false return false
} }

View File

@ -159,7 +159,6 @@ func createORDeleteRbdResources(action kubectlAction) {
filename: rbdDirPath + rbdProvisioner, filename: rbdDirPath + rbdProvisioner,
namespace: cephCSINamespace, namespace: cephCSINamespace,
oneReplica: true, oneReplica: true,
enableTopology: true,
}, },
// dependencies for the node-plugin // dependencies for the node-plugin
&yamlResourceNamespaced{ &yamlResourceNamespaced{

View File

@ -823,10 +823,6 @@ func oneReplicaDeployYaml(template string) string {
return re.ReplaceAllString(template, `$1 1`) return re.ReplaceAllString(template, `$1 1`)
} }
func enableTopologyInTemplate(data string) string {
return strings.ReplaceAll(data, "--feature-gates=Topology=false", "--feature-gates=Topology=true")
}
func enableReadAffinityInTemplate(template string) string { func enableReadAffinityInTemplate(template string) string {
return strings.ReplaceAll(template, "# - \"--enable-read-affinity=true\"", "- \"--enable-read-affinity=true\"") return strings.ReplaceAll(template, "# - \"--enable-read-affinity=true\"", "- \"--enable-read-affinity=true\"")
} }

102
go.mod
View File

@ -25,36 +25,36 @@ require (
github.com/pkg/xattr v0.4.9 github.com/pkg/xattr v0.4.9
github.com/prometheus/client_golang v1.18.0 github.com/prometheus/client_golang v1.18.0
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
golang.org/x/crypto v0.23.0 golang.org/x/crypto v0.24.0
golang.org/x/net v0.25.0 golang.org/x/net v0.26.0
golang.org/x/sys v0.20.0 golang.org/x/sys v0.21.0
google.golang.org/grpc v1.64.0 google.golang.org/grpc v1.64.0
google.golang.org/protobuf v1.34.1 google.golang.org/protobuf v1.34.1
// //
// when updating k8s.io/kubernetes, make sure to update the replace section too // when updating k8s.io/kubernetes, make sure to update the replace section too
// //
k8s.io/api v0.30.0 k8s.io/api v0.30.1
k8s.io/apimachinery v0.30.0 k8s.io/apimachinery v0.30.1
k8s.io/client-go v12.0.0+incompatible k8s.io/client-go v12.0.0+incompatible
k8s.io/cloud-provider v0.30.0 k8s.io/cloud-provider v0.30.1
k8s.io/klog/v2 v2.120.1 k8s.io/klog/v2 v2.120.1
k8s.io/kubernetes v1.30.0 k8s.io/kubernetes v1.30.1
k8s.io/mount-utils v0.30.0 k8s.io/mount-utils v0.29.3
k8s.io/pod-security-admission v0.30.0 k8s.io/pod-security-admission v0.30.1
k8s.io/utils v0.0.0-20230726121419-3b25d923346b k8s.io/utils v0.0.0-20230726121419-3b25d923346b
sigs.k8s.io/controller-runtime v0.18.2 sigs.k8s.io/controller-runtime v0.18.4
) )
require ( require (
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.1.0 github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.1.0
) )
require ( require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/ansel1/merry v1.6.2 // indirect github.com/ansel1/merry v1.6.2 // indirect
github.com/ansel1/merry/v2 v2.0.1 // indirect github.com/ansel1/merry/v2 v2.0.1 // indirect
@ -90,7 +90,7 @@ require (
github.com/go-openapi/swag v0.22.3 // indirect github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/cel-go v0.17.8 // indirect github.com/google/cel-go v0.17.8 // indirect
github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gnostic-models v0.6.8 // indirect
@ -160,11 +160,11 @@ require (
go.uber.org/zap v1.26.0 // indirect go.uber.org/zap v1.26.0 // indirect
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect
golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect
golang.org/x/sync v0.6.0 // indirect golang.org/x/sync v0.7.0 // indirect
golang.org/x/term v0.20.0 // indirect golang.org/x/term v0.21.0 // indirect
golang.org/x/text v0.15.0 // indirect golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.3.0 // indirect golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.18.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/appengine v1.6.8 // indirect google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
@ -174,12 +174,12 @@ require (
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.30.0 // indirect k8s.io/apiextensions-apiserver v0.30.1 // indirect
k8s.io/apiserver v0.30.0 // indirect k8s.io/apiserver v0.30.1 // indirect
k8s.io/component-base v0.30.0 // indirect k8s.io/component-base v0.30.1 // indirect
k8s.io/component-helpers v0.30.0 // indirect k8s.io/component-helpers v0.30.1 // indirect
k8s.io/controller-manager v0.30.0 // indirect k8s.io/controller-manager v0.30.1 // indirect
k8s.io/kms v0.30.0 // indirect k8s.io/kms v0.30.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
k8s.io/kubectl v0.0.0 // indirect k8s.io/kubectl v0.0.0 // indirect
k8s.io/kubelet v0.0.0 // indirect k8s.io/kubelet v0.0.0 // indirect
@ -199,35 +199,35 @@ replace (
// //
// k8s.io/kubernetes depends on these k8s.io packages, but unversioned // k8s.io/kubernetes depends on these k8s.io packages, but unversioned
// //
k8s.io/api => k8s.io/api v0.30.0 k8s.io/api => k8s.io/api v0.30.1
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.30.0 k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.30.1
k8s.io/apimachinery => k8s.io/apimachinery v0.30.0 k8s.io/apimachinery => k8s.io/apimachinery v0.30.1
k8s.io/apiserver => k8s.io/apiserver v0.30.0 k8s.io/apiserver => k8s.io/apiserver v0.30.1
k8s.io/cli-runtime => k8s.io/cli-runtime v0.30.0 k8s.io/cli-runtime => k8s.io/cli-runtime v0.30.1
k8s.io/client-go => k8s.io/client-go v0.30.0 k8s.io/client-go => k8s.io/client-go v0.30.1
k8s.io/cloud-provider => k8s.io/cloud-provider v0.30.0 k8s.io/cloud-provider => k8s.io/cloud-provider v0.30.1
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.30.0 k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.30.1
k8s.io/code-generator => k8s.io/code-generator v0.30.0 k8s.io/code-generator => k8s.io/code-generator v0.30.1
k8s.io/component-base => k8s.io/component-base v0.30.0 k8s.io/component-base => k8s.io/component-base v0.30.1
k8s.io/component-helpers => k8s.io/component-helpers v0.30.0 k8s.io/component-helpers => k8s.io/component-helpers v0.30.1
k8s.io/controller-manager => k8s.io/controller-manager v0.30.0 k8s.io/controller-manager => k8s.io/controller-manager v0.30.1
k8s.io/cri-api => k8s.io/cri-api v0.30.0 k8s.io/cri-api => k8s.io/cri-api v0.30.1
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.30.0 k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.30.1
k8s.io/dynamic-resource-allocation => k8s.io/dynamic-resource-allocation v0.30.0 k8s.io/dynamic-resource-allocation => k8s.io/dynamic-resource-allocation v0.30.1
k8s.io/endpointslice => k8s.io/endpointslice v0.30.0 k8s.io/endpointslice => k8s.io/endpointslice v0.30.1
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.30.0 k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.30.1
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.30.0 k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.30.1
k8s.io/kube-proxy => k8s.io/kube-proxy v0.30.0 k8s.io/kube-proxy => k8s.io/kube-proxy v0.30.1
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.30.0 k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.30.1
k8s.io/kubectl => k8s.io/kubectl v0.30.0 k8s.io/kubectl => k8s.io/kubectl v0.30.1
k8s.io/kubelet => k8s.io/kubelet v0.30.0 k8s.io/kubelet => k8s.io/kubelet v0.30.1
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.30.0 k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.30.1
k8s.io/metrics => k8s.io/metrics v0.30.0 k8s.io/metrics => k8s.io/metrics v0.30.1
// TODO: replace with latest once https://github.com/ceph/ceph-csi/issues/4633 is fixed // TODO: replace with latest once https://github.com/ceph/ceph-csi/issues/4633 is fixed
k8s.io/mount-utils => k8s.io/mount-utils v0.29.3 k8s.io/mount-utils => k8s.io/mount-utils v0.29.3
k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.30.0 k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.30.1
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.30.0 k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.30.1
// layeh.com seems to be misbehaving // layeh.com seems to be misbehaving
layeh.com/radius => github.com/layeh/radius v0.0.0-20190322222518-890bc1058917 layeh.com/radius => github.com/layeh/radius v0.0.0-20190322222518-890bc1058917
) )

120
go.sum
View File

@ -759,12 +759,12 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
github.com/Azure/azure-sdk-for-go v62.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v62.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA= github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.1.0 h1:h4Zxgmi9oyZL2l8jeg1iRTqPloHktywWcu0nlJmo1tA= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.1.0 h1:h4Zxgmi9oyZL2l8jeg1iRTqPloHktywWcu0nlJmo1tA=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.1.0/go.mod h1:LgLGXawqSreJz135Elog0ywTJDsm0Hz2k+N+6ZK35u8= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.1.0/go.mod h1:LgLGXawqSreJz135Elog0ywTJDsm0Hz2k+N+6ZK35u8=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
@ -781,8 +781,8 @@ github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcP
github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
github.com/Azure/go-autorest/logger v0.2.1/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/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/IBM/keyprotect-go-client v0.5.1/go.mod h1:5TwDM/4FRJq1ZOlwQL1xFahLWQ3TveR88VmL1u3njyI= github.com/IBM/keyprotect-go-client v0.5.1/go.mod h1:5TwDM/4FRJq1ZOlwQL1xFahLWQ3TveR88VmL1u3njyI=
@ -928,8 +928,6 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@ -1065,8 +1063,8 @@ github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
@ -1569,8 +1567,9 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
@ -1763,8 +1762,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -1831,8 +1830,9 @@ golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -1915,8 +1915,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1971,8 +1971,9 @@ golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -2088,8 +2089,8 @@ golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ= golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -2110,8 +2111,8 @@ golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -2133,8 +2134,8 @@ golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -2228,8 +2229,9 @@ golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -2607,27 +2609,27 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
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.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA= k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY=
k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM=
k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs= k8s.io/apiextensions-apiserver v0.30.1 h1:4fAJZ9985BmpJG6PkoxVRpXv9vmPUOVzl614xarePws=
k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= k8s.io/apiextensions-apiserver v0.30.1/go.mod h1:R4GuSrlhgq43oRY9sF2IToFh7PVlF1JjfWdoG3pixk4=
k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA= k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U=
k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/apiserver v0.30.0 h1:QCec+U72tMQ+9tR6A0sMBB5Vh6ImCEkoKkTDRABWq6M= k8s.io/apiserver v0.30.1 h1:BEWEe8bzS12nMtDKXzCF5Q5ovp6LjjYkSp8qOPk8LZ8=
k8s.io/apiserver v0.30.0/go.mod h1:smOIBq8t0MbKZi7O7SyIpjPsiKJ8qa+llcFCluKyqiY= k8s.io/apiserver v0.30.1/go.mod h1:i87ZnQ+/PGAmSbD/iEKM68bm1D5reX8fO4Ito4B01mo=
k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ= k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q=
k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY= k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc=
k8s.io/cloud-provider v0.30.0 h1:hz1MXkFjsyO167sRZVchXEi2YYMQ6kolBi79nuICjzw= k8s.io/cloud-provider v0.30.1 h1:OslHpog97zG9Kr7/vV1ki8nLKq8xTPUkN/kepCxBqKI=
k8s.io/cloud-provider v0.30.0/go.mod h1:iyVcGvDfmZ7m5cliI9TTHj0VTjYDNpc/K71Gp6hukjU= k8s.io/cloud-provider v0.30.1/go.mod h1:1uZp+FSskXQoeAAIU91/XCO8X/9N1U3z5usYeSLT4MI=
k8s.io/code-generator v0.30.0/go.mod h1:mBMZhfRR4IunJUh2+7LVmdcWwpouCH5+LNPkZ3t/v7Q= k8s.io/code-generator v0.30.1/go.mod h1:hFgxRsvOUg79mbpbVKfjJvRhVz1qLoe40yZDJ/hwRH4=
k8s.io/component-base v0.30.0 h1:cj6bp38g0ainlfYtaOQuRELh5KSYjhKxM+io7AUIk4o= k8s.io/component-base v0.30.1 h1:bvAtlPh1UrdaZL20D9+sWxsJljMi0QZ3Lmw+kmZAaxQ=
k8s.io/component-base v0.30.0/go.mod h1:V9x/0ePFNaKeKYA3bOvIbrNoluTSG+fSJKjLdjOoeXQ= k8s.io/component-base v0.30.1/go.mod h1:e/X9kDiOebwlI41AvBHuWdqFriSRrX50CdwA9TFaHLI=
k8s.io/component-helpers v0.30.0 h1:xbJtNCfSM4SB/Tz5JqCKDZv4eT5LVi/AWQ1VOxhmStU= k8s.io/component-helpers v0.30.1 h1:/UcxSLzZ0owluTE2WMDrFfZl2L+WVXKdYYYm68qnH7U=
k8s.io/component-helpers v0.30.0/go.mod h1:68HlSwXIumMKmCx8cZe1PoafQEYh581/sEpxMrkhmX4= k8s.io/component-helpers v0.30.1/go.mod h1:b1Xk27UJ3p/AmPqDx7khrnSxrdwQy9gTP7O1y6MZ6rg=
k8s.io/controller-manager v0.30.0 h1:jqqT8cK0Awdy0IfT0yuqYIRmwskbdzH5AEZqkuhEVMs= k8s.io/controller-manager v0.30.1 h1:vrpfinHQWGf40U08Zmrt+QxK/2yTgjJl/9DKtjaB1gI=
k8s.io/controller-manager v0.30.0/go.mod h1:suM1r/pxUuk2ij5Bbm7W9kBLrFujXuzIboNuWK5AfRA= k8s.io/controller-manager v0.30.1/go.mod h1:8rTEPbn8LRKC/vS+If+JAKBfsftCfTMaF8/n4SJC+PQ=
k8s.io/csi-translation-lib v0.30.0 h1:pEe6jshNVE4od2AdgYlsAtiKP/MH+NcsBbUPA/dWA6U= k8s.io/csi-translation-lib v0.30.1 h1:fIBtNMQjyr7HFv3xGSSH9cWOQS1K1kIBmZ1zRsHuVKs=
k8s.io/csi-translation-lib v0.30.0/go.mod h1:5TT/awOiKEX+8CcbReVYJyddT7xqlFrp3ChE9e45MyU= k8s.io/csi-translation-lib v0.30.1/go.mod h1:l0HrIBIxUKRvqnNWqn6AXTYgUa2mAFLT6bjo1lU+55U=
k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8= k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8=
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
@ -2637,22 +2639,22 @@ k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kms v0.30.0 h1:ZlnD/ei5lpvUlPw6eLfVvH7d8i9qZ6HwUQgydNVks8g= k8s.io/kms v0.30.1 h1:gEIbEeCbFiaN2tNfp/EUhFdGr5/CSj8Eyq6Mkr7cCiY=
k8s.io/kms v0.30.0/go.mod h1:GrMurD0qk3G4yNgGcsCEmepqf9KyyIrTXYR2lyUOJC4= k8s.io/kms v0.30.1/go.mod h1:GrMurD0qk3G4yNgGcsCEmepqf9KyyIrTXYR2lyUOJC4=
k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/kubectl v0.30.0 h1:xbPvzagbJ6RNYVMVuiHArC1grrV5vSmmIcSZuCdzRyk= k8s.io/kubectl v0.30.1 h1:sHFIRI3oP0FFZmBAVEE8ErjnTyXDPkBcvO88mH9RjuY=
k8s.io/kubectl v0.30.0/go.mod h1:zgolRw2MQXLPwmic2l/+iHs239L49fhSeICuMhQQXTI= k8s.io/kubectl v0.30.1/go.mod h1:7j+L0Cc38RYEcx+WH3y44jRBe1Q1jxdGPKkX0h4iDq0=
k8s.io/kubelet v0.30.0 h1:/pqHVR2Rn8ExCpn211wL3pMtqRFpcBcJPl4+1INbIMk= k8s.io/kubelet v0.30.1 h1:6gS1gWjrefUGfC/9n0ITOzxnKyt89FfkIhom70Bola4=
k8s.io/kubelet v0.30.0/go.mod h1:WukdKqbQxnj+csn3K8XOKeX7Sh60J/da25IILjvvB5s= k8s.io/kubelet v0.30.1/go.mod h1:5IUeAt3YlIfLNdT/YfRuCCONfEefm7qfcqz81b002Z8=
k8s.io/kubernetes v1.30.0 h1:u3Yw8rNlo2NDSGaDpoxoHXLPQnEu1tfqHATKOJe94HY= k8s.io/kubernetes v1.30.1 h1:XlqS6KslLEA5mQzLK2AJrhr4Z1m8oJfkhHiWJ5lue+I=
k8s.io/kubernetes v1.30.0/go.mod h1:yPbIk3MhmhGigX62FLJm+CphNtjxqCvAIFQXup6RKS0= k8s.io/kubernetes v1.30.1/go.mod h1:yPbIk3MhmhGigX62FLJm+CphNtjxqCvAIFQXup6RKS0=
k8s.io/mount-utils v0.29.3 h1:iEcqPP7Vv8UClH8nnMfovtmy/04fIloRW9JuSXykoZ0= k8s.io/mount-utils v0.29.3 h1:iEcqPP7Vv8UClH8nnMfovtmy/04fIloRW9JuSXykoZ0=
k8s.io/mount-utils v0.29.3/go.mod h1:9IWJTMe8tG0MYMLEp60xK9GYVeCdA3g4LowmnVi+t9Y= k8s.io/mount-utils v0.29.3/go.mod h1:9IWJTMe8tG0MYMLEp60xK9GYVeCdA3g4LowmnVi+t9Y=
k8s.io/pod-security-admission v0.30.0 h1:C8J/zbrA3hVR7jatN+mN/ymUWxwU6KceS5HsEEt6rTY= k8s.io/pod-security-admission v0.30.1 h1:r2NQSNXfnZDnm6KvLv1sYgai1ZXuO+m0qn11/Xymkf8=
k8s.io/pod-security-admission v0.30.0/go.mod h1:eyzZB+gtMwnNduqr9tVO2vjf2DdepZsUA11SzyfXhfM= k8s.io/pod-security-admission v0.30.1/go.mod h1:O5iry5U8N0CvtfI5kfe0CZ0Ct/KYj057j6Pa+QIwp24=
k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
@ -2716,8 +2718,8 @@ rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 h1:/U5vjBbQn3RChhv7P11uhYvCSm5G2GaIi5AIGBS6r4c= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 h1:/U5vjBbQn3RChhv7P11uhYvCSm5G2GaIi5AIGBS6r4c=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0/go.mod h1:z7+wmGM2dfIiLRfrC6jb5kV2Mq/sK1ZP303cxzkV5Y4= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0/go.mod h1:z7+wmGM2dfIiLRfrC6jb5kV2Mq/sK1ZP303cxzkV5Y4=
sigs.k8s.io/controller-runtime v0.2.2/go.mod h1:9dyohw3ZtoXQuV1e766PHUn+cmrRCIcBh6XIMFNMZ+I= sigs.k8s.io/controller-runtime v0.2.2/go.mod h1:9dyohw3ZtoXQuV1e766PHUn+cmrRCIcBh6XIMFNMZ+I=
sigs.k8s.io/controller-runtime v0.18.2 h1:RqVW6Kpeaji67CY5nPEfRz6ZfFMk0lWQlNrLqlNpx+Q= sigs.k8s.io/controller-runtime v0.18.4 h1:87+guW1zhvuPLh1PHybKdYFLU0YJp4FhJRmiHvm5BZw=
sigs.k8s.io/controller-runtime v0.18.2/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw= sigs.k8s.io/controller-runtime v0.18.4/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 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/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=

View File

@ -51,13 +51,6 @@ func (is *IdentityServer) GetPluginCapabilities(
}, },
}, },
}, },
{
Type: &csi.PluginCapability_Service_{
Service: &csi.PluginCapability_Service{
Type: csi.PluginCapability_Service_VOLUME_ACCESSIBILITY_CONSTRAINTS,
},
},
},
{ {
Type: &csi.PluginCapability_Service_{ Type: &csi.PluginCapability_Service_{
Service: &csi.PluginCapability_Service{ Service: &csi.PluginCapability_Service{

View File

@ -393,6 +393,7 @@ func getBackingSnapshotRoot(
return "", status.Errorf(codes.Internal, err.Error()) return "", status.Errorf(codes.Internal, err.Error())
} }
defer dir.Close()
// Read the contents of <root path>/.snap directory into a string slice. // Read the contents of <root path>/.snap directory into a string slice.

View File

@ -776,10 +776,6 @@ func getGRPCError(err error) error {
} }
errorStatusMap := map[error]codes.Code{ errorStatusMap := map[error]codes.Code{
corerbd.ErrFetchingLocalState: codes.Internal,
corerbd.ErrResyncImageFailed: codes.Internal,
corerbd.ErrDisableImageMirroringFailed: codes.Internal,
corerbd.ErrFetchingMirroringInfo: codes.Internal,
corerbd.ErrInvalidArgument: codes.InvalidArgument, corerbd.ErrInvalidArgument: codes.InvalidArgument,
corerbd.ErrAborted: codes.Aborted, corerbd.ErrAborted: codes.Aborted,
corerbd.ErrFailedPrecondition: codes.FailedPrecondition, corerbd.ErrFailedPrecondition: codes.FailedPrecondition,
@ -792,8 +788,8 @@ func getGRPCError(err error) error {
} }
} }
// Handle any other non nil error not listed in the map // Handle any other non nil error not listed in the map as internal error
return status.Error(codes.Unknown, err.Error()) return status.Error(codes.Internal, err.Error())
} }
// GetVolumeReplicationInfo extracts the RBD volume information from the volumeID, If the // GetVolumeReplicationInfo extracts the RBD volume information from the volumeID, If the

View File

@ -541,26 +541,6 @@ func TestGetGRPCError(t *testing.T) {
err error err error
expectedErr error expectedErr error
}{ }{
{
name: "FetchingLocalStateFailed",
err: corerbd.ErrFetchingLocalState,
expectedErr: status.Error(codes.Internal, corerbd.ErrFetchingLocalState.Error()),
},
{
name: "ResyncImageFailed",
err: corerbd.ErrResyncImageFailed,
expectedErr: status.Error(codes.Internal, corerbd.ErrResyncImageFailed.Error()),
},
{
name: "DisableImageMirroringFailed",
err: corerbd.ErrDisableImageMirroringFailed,
expectedErr: status.Error(codes.Internal, corerbd.ErrDisableImageMirroringFailed.Error()),
},
{
name: "FetchingMirroringInfoFailed",
err: corerbd.ErrFetchingMirroringInfo,
expectedErr: status.Error(codes.Internal, corerbd.ErrFetchingMirroringInfo.Error()),
},
{ {
name: "InvalidArgument", name: "InvalidArgument",
err: corerbd.ErrInvalidArgument, err: corerbd.ErrInvalidArgument,
@ -584,7 +564,7 @@ func TestGetGRPCError(t *testing.T) {
{ {
name: "InvalidError", name: "InvalidError",
err: errors.New("some error"), err: errors.New("some error"),
expectedErr: status.Error(codes.Unknown, "some error"), expectedErr: status.Error(codes.Internal, "some error"),
}, },
{ {
name: "NilError", name: "NilError",

View File

@ -55,14 +55,6 @@ var (
ErrAborted = errors.New("operation got aborted") ErrAborted = errors.New("operation got aborted")
// ErrInvalidArgument is returned when the client specified an invalid argument. // ErrInvalidArgument is returned when the client specified an invalid argument.
ErrInvalidArgument = errors.New("invalid arguments provided") ErrInvalidArgument = errors.New("invalid arguments provided")
// ErrFetchingLocalState is returned when the operation to fetch local state fails.
ErrFetchingLocalState = errors.New("failed to get local state")
// ErrDisableImageMirroringFailed is returned when the operation to disable image mirroring fails.
ErrDisableImageMirroringFailed = errors.New("failed to disable image mirroring")
// ErrFetchingMirroringInfo is returned when the operation to fetch mirroring info of image fails.
ErrFetchingMirroringInfo = errors.New("failed to get mirroring info of image")
// ErrResyncImageFailed is returned when the operation to resync the image fails.
ErrResyncImageFailed = errors.New("failed to resync image")
// ErrImageInUse is returned when the image is in use. // ErrImageInUse is returned when the image is in use.
ErrImageInUse = errors.New("image is in use") ErrImageInUse = errors.New("image is in use")
) )

View File

@ -243,11 +243,8 @@ func TestReadAffinity_GetReadAffinityMapOptions(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("failed to marshal csi config info %v", err) t.Errorf("failed to marshal csi config info %v", err)
} }
tmpConfPath := util.CsiConfigFile tmpConfPath := t.TempDir() + "/ceph-csi.json"
err = os.Mkdir("/etc/ceph-csi-config", 0o600)
if err != nil {
t.Errorf("failed to create directory %s: %v", "/etc/ceph-csi-config", err)
}
err = os.WriteFile(tmpConfPath, csiConfigFileContent, 0o600) err = os.WriteFile(tmpConfPath, csiConfigFileContent, 0o600)
if err != nil { if err != nil {
t.Errorf("failed to write %s file content: %v", util.CsiConfigFile, err) t.Errorf("failed to write %s file content: %v", util.CsiConfigFile, err)

View File

@ -25,7 +25,7 @@ import (
func (rv *rbdVolume) ResyncVol(localStatus librbd.SiteMirrorImageStatus) error { func (rv *rbdVolume) ResyncVol(localStatus librbd.SiteMirrorImageStatus) error {
if err := rv.resyncImage(); err != nil { if err := rv.resyncImage(); err != nil {
return fmt.Errorf("%w: failed to resync image: %w", ErrResyncImageFailed, err) return fmt.Errorf("failed to resync image: %w", err)
} }
// If we issued a resync, return a non-final error as image needs to be recreated // If we issued a resync, return a non-final error as image needs to be recreated
@ -73,7 +73,7 @@ func (rv *rbdVolume) DisableVolumeReplication(
// replication Kubernetes artifacts after failback operation. // replication Kubernetes artifacts after failback operation.
localStatus, rErr := rv.GetLocalState() localStatus, rErr := rv.GetLocalState()
if rErr != nil { if rErr != nil {
return fmt.Errorf("%w: %w", ErrFetchingLocalState, rErr) return fmt.Errorf("failed to get local state: %w", rErr)
} }
if localStatus.Up && localStatus.State == librbd.MirrorImageStatusStateReplaying { if localStatus.Up && localStatus.State == librbd.MirrorImageStatusStateReplaying {
return nil return nil
@ -84,13 +84,13 @@ func (rv *rbdVolume) DisableVolumeReplication(
} }
err := rv.DisableImageMirroring(force) err := rv.DisableImageMirroring(force)
if err != nil { if err != nil {
return fmt.Errorf("%w: %w", ErrDisableImageMirroringFailed, err) return fmt.Errorf("failed to disable image mirroring: %w", err)
} }
// the image state can be still disabling once we disable the mirroring // the image state can be still disabling once we disable the mirroring
// check the mirroring is disabled or not // check the mirroring is disabled or not
mirroringInfo, err = rv.GetImageMirroringInfo() mirroringInfo, err = rv.GetImageMirroringInfo()
if err != nil { if err != nil {
return fmt.Errorf("%w: %w", ErrFetchingMirroringInfo, err) return fmt.Errorf("failed to get mirroring info of image: %w", err)
} }
if mirroringInfo.State == librbd.MirrorImageDisabling { if mirroringInfo.State == librbd.MirrorImageDisabling {
return fmt.Errorf("%w: %q is in disabling state", ErrAborted, rv.VolID) return fmt.Errorf("%w: %q is in disabling state", ErrAborted, rv.VolID)

View File

@ -1,6 +1,11 @@
ARG BASE_IMAGE ARG BASE_IMAGE
FROM ${BASE_IMAGE} FROM ${BASE_IMAGE}
# Since CentOS Stream 8 is EOL, update the config to use vault.centos.org for CentOS Stream 8
# TODO: remove once https://github.com/ceph/ceph-csi/issues/4659 is fixed.
RUN sed -i 's|^mirrorlist=|#mirrorlist=|g' /etc/yum.repos.d/*.repo && \
sed -i 's|^#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/*.repo
ARG GOROOT=/usr/local/go ARG GOROOT=/usr/local/go
ARG GOARCH ARG GOARCH

View File

@ -190,7 +190,7 @@ install_cephcsi_helm_charts() {
kubectl_retry delete cm ceph-config --namespace "${NAMESPACE}" kubectl_retry delete cm ceph-config --namespace "${NAMESPACE}"
# shellcheck disable=SC2086 # shellcheck disable=SC2086
"${HELM}" install --namespace ${NAMESPACE} --set provisioner.fullnameOverride=csi-rbdplugin-provisioner --set nodeplugin.fullnameOverride=csi-rbdplugin --set configMapName=ceph-csi-config --set provisioner.replicaCount=1 --set-json='commonLabels={"app.kubernetes.io/name": "ceph-csi-rbd", "app.kubernetes.io/managed-by": "helm"}' ${SET_SC_TEMPLATE_VALUES} ${RBD_SECRET_TEMPLATE_VALUES} ${RBD_CHART_NAME} "${SCRIPT_DIR}"/../charts/ceph-csi-rbd --set topology.enabled=true --set topology.domainLabels="{${NODE_LABEL_REGION},${NODE_LABEL_ZONE}}" --set provisioner.maxSnapshotsOnImage=3 --set provisioner.minSnapshotsOnImage=2 ${READ_AFFINITY_VALUES} --set provisioner.snapshotter.args.enableVolumeGroupSnapshots=true "${HELM}" install --namespace ${NAMESPACE} --set provisioner.fullnameOverride=csi-rbdplugin-provisioner --set nodeplugin.fullnameOverride=csi-rbdplugin --set configMapName=ceph-csi-config --set provisioner.replicaCount=1 --set-json='commonLabels={"app.kubernetes.io/name": "ceph-csi-rbd", "app.kubernetes.io/managed-by": "helm"}' ${SET_SC_TEMPLATE_VALUES} ${RBD_SECRET_TEMPLATE_VALUES} ${RBD_CHART_NAME} "${SCRIPT_DIR}"/../charts/ceph-csi-rbd --set topology.domainLabels="{${NODE_LABEL_REGION},${NODE_LABEL_ZONE}}" --set provisioner.maxSnapshotsOnImage=3 --set provisioner.minSnapshotsOnImage=2 ${READ_AFFINITY_VALUES} --set provisioner.snapshotter.args.enableVolumeGroupSnapshots=true
check_deployment_status app=ceph-csi-rbd "${NAMESPACE}" check_deployment_status app=ceph-csi-rbd "${NAMESPACE}"
check_daemonset_status app=ceph-csi-rbd "${NAMESPACE}" check_daemonset_status app=ceph-csi-rbd "${NAMESPACE}"

View File

@ -1,5 +1,56 @@
# Release History # Release History
## 1.11.1 (2024-04-02)
### Bugs Fixed
* Pollers that use the `Location` header won't consider `http.StatusRequestTimeout` a terminal failure.
* `runtime.Poller[T].Result` won't consider non-terminal error responses as terminal.
## 1.11.0 (2024-04-01)
### Features Added
* Added `StatusCodes` to `arm/policy.RegistrationOptions` to allow supporting non-standard HTTP status codes during registration.
* Added field `InsecureAllowCredentialWithHTTP` to `azcore.ClientOptions` and dependent authentication pipeline policies.
* Added type `MultipartContent` to the `streaming` package to support multipart/form payloads with custom Content-Type and file name.
### Bugs Fixed
* `runtime.SetMultipartFormData` won't try to stringify `[]byte` values.
* Pollers that use the `Location` header won't consider `http.StatusTooManyRequests` a terminal failure.
### Other Changes
* Update dependencies.
## 1.10.0 (2024-02-29)
### Features Added
* Added logging event `log.EventResponseError` that will contain the contents of `ResponseError.Error()` whenever an `azcore.ResponseError` is created.
* Added `runtime.NewResponseErrorWithErrorCode` for creating an `azcore.ResponseError` with a caller-supplied error code.
* Added type `MatchConditions` for use in conditional requests.
### Bugs Fixed
* Fixed a potential race condition between `NullValue` and `IsNullValue`.
* `runtime.EncodeQueryParams` will escape semicolons before calling `url.ParseQuery`.
### Other Changes
* Update dependencies.
## 1.9.2 (2024-02-06)
### Bugs Fixed
* `runtime.MarshalAsByteArray` and `runtime.MarshalAsJSON` will preserve the preexisting value of the `Content-Type` header.
### Other Changes
* Update to latest version of `internal`.
## 1.9.1 (2023-12-11) ## 1.9.1 (2023-12-11)
### Bugs Fixed ### Bugs Fixed

View File

@ -20,6 +20,11 @@ type BearerTokenOptions struct {
// policy's credential must support multitenant authentication. // policy's credential must support multitenant authentication.
AuxiliaryTenants []string AuxiliaryTenants []string
// InsecureAllowCredentialWithHTTP enables authenticated requests over HTTP.
// By default, authenticated requests to an HTTP endpoint are rejected by the client.
// WARNING: setting this to true will allow sending the authentication key in clear text. Use with caution.
InsecureAllowCredentialWithHTTP bool
// Scopes contains the list of permission scopes required for the token. // Scopes contains the list of permission scopes required for the token.
Scopes []string Scopes []string
} }
@ -44,6 +49,11 @@ type RegistrationOptions struct {
// The default valule is 5 minutes. // The default valule is 5 minutes.
// NOTE: Setting this to a small value might cause the policy to prematurely fail. // NOTE: Setting this to a small value might cause the policy to prematurely fail.
PollingDuration time.Duration PollingDuration time.Duration
// StatusCodes contains the slice of custom HTTP status codes to use instead
// of the default http.StatusConflict. This should only be set if a service
// returns a non-standard HTTP status code when unregistered.
StatusCodes []int
} }
// ClientOptions contains configuration settings for a client's pipeline. // ClientOptions contains configuration settings for a client's pipeline.

View File

@ -31,6 +31,7 @@ func NewPipeline(module, version string, cred azcore.TokenCredential, plOpts azr
} }
authPolicy := NewBearerTokenPolicy(cred, &armpolicy.BearerTokenOptions{ authPolicy := NewBearerTokenPolicy(cred, &armpolicy.BearerTokenOptions{
AuxiliaryTenants: options.AuxiliaryTenants, AuxiliaryTenants: options.AuxiliaryTenants,
InsecureAllowCredentialWithHTTP: options.InsecureAllowCredentialWithHTTP,
Scopes: []string{conf.Audience + "/.default"}, Scopes: []string{conf.Audience + "/.default"},
}) })
perRetry := make([]azpolicy.Policy, len(plOpts.PerRetry), len(plOpts.PerRetry)+1) perRetry := make([]azpolicy.Policy, len(plOpts.PerRetry), len(plOpts.PerRetry)+1)

View File

@ -64,6 +64,7 @@ func NewBearerTokenPolicy(cred azcore.TokenCredential, opts *armpolicy.BearerTok
p.scopes = make([]string, len(opts.Scopes)) p.scopes = make([]string, len(opts.Scopes))
copy(p.scopes, opts.Scopes) copy(p.scopes, opts.Scopes)
p.btp = azruntime.NewBearerTokenPolicy(cred, opts.Scopes, &azpolicy.BearerTokenOptions{ p.btp = azruntime.NewBearerTokenPolicy(cred, opts.Scopes, &azpolicy.BearerTokenOptions{
InsecureAllowCredentialWithHTTP: opts.InsecureAllowCredentialWithHTTP,
AuthorizationHandler: azpolicy.AuthorizationHandler{ AuthorizationHandler: azpolicy.AuthorizationHandler{
OnChallenge: p.onChallenge, OnChallenge: p.onChallenge,
OnRequest: p.onRequest, OnRequest: p.onRequest,

View File

@ -8,7 +8,6 @@ package runtime
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
@ -16,6 +15,7 @@ import (
"time" "time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/internal/resource"
armpolicy "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/policy" armpolicy "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported" "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared" "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
@ -45,6 +45,9 @@ func setDefaults(r *armpolicy.RegistrationOptions) {
if r.PollingDuration == 0 { if r.PollingDuration == 0 {
r.PollingDuration = 5 * time.Minute r.PollingDuration = 5 * time.Minute
} }
if len(r.StatusCodes) == 0 {
r.StatusCodes = []int{http.StatusConflict}
}
} }
// NewRPRegistrationPolicy creates a policy object configured using the specified options. // NewRPRegistrationPolicy creates a policy object configured using the specified options.
@ -88,7 +91,7 @@ func (r *rpRegistrationPolicy) Do(req *azpolicy.Request) (*http.Response, error)
// make the original request // make the original request
resp, err = req.Next() resp, err = req.Next()
// getting a 409 is the first indication that the RP might need to be registered, check error response // getting a 409 is the first indication that the RP might need to be registered, check error response
if err != nil || resp.StatusCode != http.StatusConflict { if err != nil || !runtime.HasStatusCode(resp, r.options.StatusCodes...) {
return resp, err return resp, err
} }
var reqErr requestError var reqErr requestError
@ -105,17 +108,12 @@ func (r *rpRegistrationPolicy) Do(req *azpolicy.Request) (*http.Response, error)
// to the caller so its error unmarshalling will kick in // to the caller so its error unmarshalling will kick in
return resp, err return resp, err
} }
// RP needs to be registered. start by getting the subscription ID from the original request res, err := resource.ParseResourceID(req.Raw().URL.Path)
subID, err := getSubscription(req.Raw().URL.Path)
if err != nil { if err != nil {
return resp, err return resp, err
} }
// now get the RP from the error rp = res.ResourceType.Namespace
rp, err = getProvider(reqErr) logRegistrationExit := func(v any) {
if err != nil {
return resp, err
}
logRegistrationExit := func(v interface{}) {
log.Writef(LogRPRegistration, "END registration for %s: %v", rp, v) log.Writef(LogRPRegistration, "END registration for %s: %v", rp, v)
} }
log.Writef(LogRPRegistration, "BEGIN registration for %s", rp) log.Writef(LogRPRegistration, "BEGIN registration for %s", rp)
@ -124,7 +122,7 @@ func (r *rpRegistrationPolicy) Do(req *azpolicy.Request) (*http.Response, error)
rpOps := &providersOperations{ rpOps := &providersOperations{
p: r.pipeline, p: r.pipeline,
u: r.endpoint, u: r.endpoint,
subID: subID, subID: res.SubscriptionID,
} }
if _, err = rpOps.Register(&shared.ContextWithDeniedValues{Context: req.Raw().Context()}, rp); err != nil { if _, err = rpOps.Register(&shared.ContextWithDeniedValues{Context: req.Raw().Context()}, rp); err != nil {
logRegistrationExit(err) logRegistrationExit(err)
@ -189,23 +187,6 @@ func isUnregisteredRPCode(errorCode string) bool {
return false return false
} }
func getSubscription(path string) (string, error) {
parts := strings.Split(path, "/")
for i, v := range parts {
if v == "subscriptions" && (i+1) < len(parts) {
return parts[i+1], nil
}
}
return "", fmt.Errorf("failed to obtain subscription ID from %s", path)
}
func getProvider(re requestError) (string, error) {
if len(re.ServiceError.Details) > 0 {
return re.ServiceError.Details[0].Target, nil
}
return "", errors.New("unexpected empty Details")
}
// minimal error definitions to simplify detection // minimal error definitions to simplify detection
type requestError struct { type requestError struct {
ServiceError *serviceError `json:"error"` ServiceError *serviceError `json:"error"`
@ -213,12 +194,6 @@ type requestError struct {
type serviceError struct { type serviceError struct {
Code string `json:"code"` Code string `json:"code"`
Details []serviceErrorDetails `json:"details"`
}
type serviceErrorDetails struct {
Code string `json:"code"`
Target string `json:"target"`
} }
/////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -23,7 +23,7 @@ pr:
- sdk/azcore/ - sdk/azcore/
- eng/ - eng/
stages: extends:
- template: /eng/pipelines/templates/jobs/archetype-sdk-client.yml template: /eng/pipelines/templates/jobs/archetype-sdk-client.yml
parameters: parameters:
ServiceDirectory: azcore ServiceDirectory: azcore

View File

@ -8,6 +8,7 @@ package azcore
import ( import (
"reflect" "reflect"
"sync"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported" "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared" "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
@ -41,13 +42,28 @@ func NewSASCredential(sas string) *SASCredential {
} }
// holds sentinel values used to send nulls // holds sentinel values used to send nulls
var nullables map[reflect.Type]interface{} = map[reflect.Type]interface{}{} var nullables map[reflect.Type]any = map[reflect.Type]any{}
var nullablesMu sync.RWMutex
// NullValue is used to send an explicit 'null' within a request. // NullValue is used to send an explicit 'null' within a request.
// This is typically used in JSON-MERGE-PATCH operations to delete a value. // This is typically used in JSON-MERGE-PATCH operations to delete a value.
func NullValue[T any]() T { func NullValue[T any]() T {
t := shared.TypeOfT[T]() t := shared.TypeOfT[T]()
nullablesMu.RLock()
v, found := nullables[t] v, found := nullables[t]
nullablesMu.RUnlock()
if found {
// return the sentinel object
return v.(T)
}
// promote to exclusive lock and check again (double-checked locking pattern)
nullablesMu.Lock()
defer nullablesMu.Unlock()
v, found = nullables[t]
if !found { if !found {
var o reflect.Value var o reflect.Value
if k := t.Kind(); k == reflect.Map { if k := t.Kind(); k == reflect.Map {
@ -72,6 +88,9 @@ func NullValue[T any]() T {
func IsNullValue[T any](v T) bool { func IsNullValue[T any](v T) bool {
// see if our map has a sentinel object for this *T // see if our map has a sentinel object for this *T
t := reflect.TypeOf(v) t := reflect.TypeOf(v)
nullablesMu.RLock()
defer nullablesMu.RUnlock()
if o, found := nullables[t]; found { if o, found := nullables[t]; found {
o1 := reflect.ValueOf(o) o1 := reflect.ValueOf(o)
v1 := reflect.ValueOf(v) v1 := reflect.ValueOf(v)

View File

@ -46,3 +46,12 @@ func (e ETag) WeakEquals(other ETag) bool {
func (e ETag) IsWeak() bool { func (e ETag) IsWeak() bool {
return len(e) >= 4 && strings.HasPrefix(string(e), "W/\"") && strings.HasSuffix(string(e), "\"") return len(e) >= 4 && strings.HasPrefix(string(e), "W/\"") && strings.HasSuffix(string(e), "\"")
} }
// MatchConditions specifies HTTP options for conditional requests.
type MatchConditions struct {
// Optionally limit requests to resources that have a matching ETag.
IfMatch *ETag
// Optionally limit requests to resources that do not match the ETag.
IfNoneMatch *ETag
}

View File

@ -51,15 +51,15 @@ type Request struct {
values opValues values opValues
} }
type opValues map[reflect.Type]interface{} type opValues map[reflect.Type]any
// Set adds/changes a value // Set adds/changes a value
func (ov opValues) set(value interface{}) { func (ov opValues) set(value any) {
ov[reflect.TypeOf(value)] = value ov[reflect.TypeOf(value)] = value
} }
// Get looks for a value set by SetValue first // Get looks for a value set by SetValue first
func (ov opValues) get(value interface{}) bool { func (ov opValues) get(value any) bool {
v, ok := ov[reflect.ValueOf(value).Elem().Type()] v, ok := ov[reflect.ValueOf(value).Elem().Type()]
if ok { if ok {
reflect.ValueOf(value).Elem().Set(reflect.ValueOf(v)) reflect.ValueOf(value).Elem().Set(reflect.ValueOf(v))
@ -108,7 +108,7 @@ func (req *Request) Next() (*http.Response, error) {
} }
// SetOperationValue adds/changes a mutable key/value associated with a single operation. // SetOperationValue adds/changes a mutable key/value associated with a single operation.
func (req *Request) SetOperationValue(value interface{}) { func (req *Request) SetOperationValue(value any) {
if req.values == nil { if req.values == nil {
req.values = opValues{} req.values = opValues{}
} }
@ -116,7 +116,7 @@ func (req *Request) SetOperationValue(value interface{}) {
} }
// OperationValue looks for a value set by SetOperationValue(). // OperationValue looks for a value set by SetOperationValue().
func (req *Request) OperationValue(value interface{}) bool { func (req *Request) OperationValue(value any) bool {
if req.values == nil { if req.values == nil {
return false return false
} }
@ -125,46 +125,11 @@ func (req *Request) OperationValue(value interface{}) bool {
// SetBody sets the specified ReadSeekCloser as the HTTP request body, and sets Content-Type and Content-Length // SetBody sets the specified ReadSeekCloser as the HTTP request body, and sets Content-Type and Content-Length
// accordingly. If the ReadSeekCloser is nil or empty, Content-Length won't be set. If contentType is "", // accordingly. If the ReadSeekCloser is nil or empty, Content-Length won't be set. If contentType is "",
// Content-Type won't be set. // Content-Type won't be set, and if it was set, will be deleted.
// Use streaming.NopCloser to turn an io.ReadSeeker into an io.ReadSeekCloser. // Use streaming.NopCloser to turn an io.ReadSeeker into an io.ReadSeekCloser.
func (req *Request) SetBody(body io.ReadSeekCloser, contentType string) error { func (req *Request) SetBody(body io.ReadSeekCloser, contentType string) error {
var err error // clobber the existing Content-Type to preserve behavior
var size int64 return SetBody(req, body, contentType, true)
if body != nil {
size, err = body.Seek(0, io.SeekEnd) // Seek to the end to get the stream's size
if err != nil {
return err
}
}
if size == 0 {
// treat an empty stream the same as a nil one: assign req a nil body
body = nil
// RFC 9110 specifies a client shouldn't set Content-Length on a request containing no content
// (Del is a no-op when the header has no value)
req.req.Header.Del(shared.HeaderContentLength)
} else {
_, err = body.Seek(0, io.SeekStart)
if err != nil {
return err
}
req.req.Header.Set(shared.HeaderContentLength, strconv.FormatInt(size, 10))
req.Raw().GetBody = func() (io.ReadCloser, error) {
_, err := body.Seek(0, io.SeekStart) // Seek back to the beginning of the stream
return body, err
}
}
// keep a copy of the body argument. this is to handle cases
// where req.Body is replaced, e.g. httputil.DumpRequest and friends.
req.body = body
req.req.Body = body
req.req.ContentLength = size
if contentType == "" {
// Del is a no-op when the header has no value
req.req.Header.Del(shared.HeaderContentType)
} else {
req.req.Header.Set(shared.HeaderContentType, contentType)
}
return nil
} }
// RewindBody seeks the request's Body stream back to the beginning so it can be resent when retrying an operation. // RewindBody seeks the request's Body stream back to the beginning so it can be resent when retrying an operation.
@ -211,3 +176,48 @@ type PolicyFunc func(*Request) (*http.Response, error)
func (pf PolicyFunc) Do(req *Request) (*http.Response, error) { func (pf PolicyFunc) Do(req *Request) (*http.Response, error) {
return pf(req) return pf(req)
} }
// SetBody sets the specified ReadSeekCloser as the HTTP request body, and sets Content-Type and Content-Length accordingly.
// - req is the request to modify
// - body is the request body; if nil or empty, Content-Length won't be set
// - contentType is the value for the Content-Type header; if empty, Content-Type will be deleted
// - clobberContentType when true, will overwrite the existing value of Content-Type with contentType
func SetBody(req *Request, body io.ReadSeekCloser, contentType string, clobberContentType bool) error {
var err error
var size int64
if body != nil {
size, err = body.Seek(0, io.SeekEnd) // Seek to the end to get the stream's size
if err != nil {
return err
}
}
if size == 0 {
// treat an empty stream the same as a nil one: assign req a nil body
body = nil
// RFC 9110 specifies a client shouldn't set Content-Length on a request containing no content
// (Del is a no-op when the header has no value)
req.req.Header.Del(shared.HeaderContentLength)
} else {
_, err = body.Seek(0, io.SeekStart)
if err != nil {
return err
}
req.req.Header.Set(shared.HeaderContentLength, strconv.FormatInt(size, 10))
req.Raw().GetBody = func() (io.ReadCloser, error) {
_, err := body.Seek(0, io.SeekStart) // Seek back to the beginning of the stream
return body, err
}
}
// keep a copy of the body argument. this is to handle cases
// where req.Body is replaced, e.g. httputil.DumpRequest and friends.
req.body = body
req.req.Body = body
req.req.ContentLength = size
if contentType == "" {
// Del is a no-op when the header has no value
req.req.Header.Del(shared.HeaderContentType)
} else if req.req.Header.Get(shared.HeaderContentType) == "" || clobberContentType {
req.req.Header.Set(shared.HeaderContentType, contentType)
}
return nil
}

View File

@ -13,6 +13,7 @@ import (
"net/http" "net/http"
"regexp" "regexp"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/log"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared" "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
"github.com/Azure/azure-sdk-for-go/sdk/internal/exported" "github.com/Azure/azure-sdk-for-go/sdk/internal/exported"
) )
@ -20,36 +21,45 @@ import (
// NewResponseError creates a new *ResponseError from the provided HTTP response. // NewResponseError creates a new *ResponseError from the provided HTTP response.
// Exported as runtime.NewResponseError(). // Exported as runtime.NewResponseError().
func NewResponseError(resp *http.Response) error { func NewResponseError(resp *http.Response) error {
respErr := &ResponseError{
StatusCode: resp.StatusCode,
RawResponse: resp,
}
// prefer the error code in the response header // prefer the error code in the response header
if ec := resp.Header.Get(shared.HeaderXMSErrorCode); ec != "" { if ec := resp.Header.Get(shared.HeaderXMSErrorCode); ec != "" {
respErr.ErrorCode = ec return NewResponseErrorWithErrorCode(resp, ec)
return respErr
} }
// if we didn't get x-ms-error-code, check in the response body // if we didn't get x-ms-error-code, check in the response body
body, err := exported.Payload(resp, nil) body, err := exported.Payload(resp, nil)
if err != nil { if err != nil {
// since we're not returning the ResponseError in this
// case we also don't want to write it to the log.
return err return err
} }
var errorCode string
if len(body) > 0 { if len(body) > 0 {
if code := extractErrorCodeJSON(body); code != "" { if fromJSON := extractErrorCodeJSON(body); fromJSON != "" {
respErr.ErrorCode = code errorCode = fromJSON
} else if code := extractErrorCodeXML(body); code != "" { } else if fromXML := extractErrorCodeXML(body); fromXML != "" {
respErr.ErrorCode = code errorCode = fromXML
} }
} }
return NewResponseErrorWithErrorCode(resp, errorCode)
}
// NewResponseErrorWithErrorCode creates an *azcore.ResponseError from the provided HTTP response and errorCode.
// Exported as runtime.NewResponseErrorWithErrorCode().
func NewResponseErrorWithErrorCode(resp *http.Response, errorCode string) error {
respErr := &ResponseError{
ErrorCode: errorCode,
StatusCode: resp.StatusCode,
RawResponse: resp,
}
log.Write(log.EventResponseError, respErr.Error())
return respErr return respErr
} }
func extractErrorCodeJSON(body []byte) string { func extractErrorCodeJSON(body []byte) string {
var rawObj map[string]interface{} var rawObj map[string]any
if err := json.Unmarshal(body, &rawObj); err != nil { if err := json.Unmarshal(body, &rawObj); err != nil {
// not a JSON object // not a JSON object
return "" return ""
@ -58,7 +68,7 @@ func extractErrorCodeJSON(body []byte) string {
// check if this is a wrapped error, i.e. { "error": { ... } } // check if this is a wrapped error, i.e. { "error": { ... } }
// if so then unwrap it // if so then unwrap it
if wrapped, ok := rawObj["error"]; ok { if wrapped, ok := rawObj["error"]; ok {
unwrapped, ok := wrapped.(map[string]interface{}) unwrapped, ok := wrapped.(map[string]any)
if !ok { if !ok {
return "" return ""
} }

View File

@ -17,22 +17,34 @@ type Event = log.Event
const ( const (
EventRequest = azlog.EventRequest EventRequest = azlog.EventRequest
EventResponse = azlog.EventResponse EventResponse = azlog.EventResponse
EventResponseError = azlog.EventResponseError
EventRetryPolicy = azlog.EventRetryPolicy EventRetryPolicy = azlog.EventRetryPolicy
EventLRO = azlog.EventLRO EventLRO = azlog.EventLRO
) )
// Write invokes the underlying listener with the specified event and message.
// If the event shouldn't be logged or there is no listener then Write does nothing.
func Write(cls log.Event, msg string) { func Write(cls log.Event, msg string) {
log.Write(cls, msg) log.Write(cls, msg)
} }
func Writef(cls log.Event, format string, a ...interface{}) { // Writef invokes the underlying listener with the specified event and formatted message.
// If the event shouldn't be logged or there is no listener then Writef does nothing.
func Writef(cls log.Event, format string, a ...any) {
log.Writef(cls, format, a...) log.Writef(cls, format, a...)
} }
// SetListener will set the Logger to write to the specified listener.
func SetListener(lst func(Event, string)) { func SetListener(lst func(Event, string)) {
log.SetListener(lst) log.SetListener(lst)
} }
// Should returns true if the specified log event should be written to the log.
// By default all log events will be logged. Call SetEvents() to limit
// the log events for logging.
// If no listener has been set this will return false.
// Calling this method is useful when the message to log is computationally expensive
// and you want to avoid the overhead if its log event is not enabled.
func Should(cls log.Event) bool { func Should(cls log.Event) bool {
return log.Should(cls) return log.Should(cls)
} }

View File

@ -27,7 +27,7 @@ func Applicable(resp *http.Response) bool {
} }
// CanResume returns true if the token can rehydrate this poller type. // CanResume returns true if the token can rehydrate this poller type.
func CanResume(token map[string]interface{}) bool { func CanResume(token map[string]any) bool {
_, ok := token["asyncURL"] _, ok := token["asyncURL"]
return ok return ok
} }

View File

@ -29,7 +29,7 @@ func Applicable(resp *http.Response) bool {
} }
// CanResume returns true if the token can rehydrate this poller type. // CanResume returns true if the token can rehydrate this poller type.
func CanResume(token map[string]interface{}) bool { func CanResume(token map[string]any) bool {
t, ok := token["type"] t, ok := token["type"]
if !ok { if !ok {
return false return false

View File

@ -26,7 +26,7 @@ func Applicable(resp *http.Response) bool {
} }
// CanResume returns true if the token can rehydrate this poller type. // CanResume returns true if the token can rehydrate this poller type.
func CanResume(token map[string]interface{}) bool { func CanResume(token map[string]any) bool {
_, ok := token["fakeURL"] _, ok := token["fakeURL"]
return ok return ok
} }

View File

@ -28,7 +28,7 @@ func Applicable(resp *http.Response) bool {
} }
// CanResume returns true if the token can rehydrate this poller type. // CanResume returns true if the token can rehydrate this poller type.
func CanResume(token map[string]interface{}) bool { func CanResume(token map[string]any) bool {
t, ok := token["type"] t, ok := token["type"]
if !ok { if !ok {
return false return false
@ -103,6 +103,10 @@ func (p *Poller[T]) Poll(ctx context.Context) (*http.Response, error) {
} else if resp.StatusCode > 199 && resp.StatusCode < 300 { } else if resp.StatusCode > 199 && resp.StatusCode < 300 {
// any 2xx other than a 202 indicates success // any 2xx other than a 202 indicates success
p.CurState = poller.StatusSucceeded p.CurState = poller.StatusSucceeded
} else if pollers.IsNonTerminalHTTPStatusCode(resp) {
// the request timed out or is being throttled.
// DO NOT include this as a terminal failure. preserve
// the existing state and return the response.
} else { } else {
p.CurState = poller.StatusFailed p.CurState = poller.StatusFailed
} }

View File

@ -25,7 +25,7 @@ func Applicable(resp *http.Response) bool {
} }
// CanResume returns true if the token can rehydrate this poller type. // CanResume returns true if the token can rehydrate this poller type.
func CanResume(token map[string]interface{}) bool { func CanResume(token map[string]any) bool {
_, ok := token["oplocURL"] _, ok := token["oplocURL"]
return ok return ok
} }

View File

@ -74,7 +74,7 @@ func ExtractToken(token string) ([]byte, error) {
// IsTokenValid returns an error if the specified token isn't applicable for generic type T. // IsTokenValid returns an error if the specified token isn't applicable for generic type T.
func IsTokenValid[T any](token string) error { func IsTokenValid[T any](token string) error {
raw := map[string]interface{}{} raw := map[string]any{}
if err := json.Unmarshal([]byte(token), &raw); err != nil { if err := json.Unmarshal([]byte(token), &raw); err != nil {
return err return err
} }
@ -185,3 +185,16 @@ func ResultHelper[T any](resp *http.Response, failed bool, out *T) error {
} }
return nil return nil
} }
// IsNonTerminalHTTPStatusCode returns true if the HTTP status code should be
// considered non-terminal thus eligible for retry.
func IsNonTerminalHTTPStatusCode(resp *http.Response) bool {
return exported.HasStatusCode(resp,
http.StatusRequestTimeout, // 408
http.StatusTooManyRequests, // 429
http.StatusInternalServerError, // 500
http.StatusBadGateway, // 502
http.StatusServiceUnavailable, // 503
http.StatusGatewayTimeout, // 504
)
}

View File

@ -40,5 +40,5 @@ const (
Module = "azcore" Module = "azcore"
// Version is the semantic version (see http://semver.org) of this module. // Version is the semantic version (see http://semver.org) of this module.
Version = "v1.9.1" Version = "v1.11.1"
) )

View File

@ -23,6 +23,11 @@ const (
// This includes information like the HTTP status code, headers, and request URL. // This includes information like the HTTP status code, headers, and request URL.
EventResponse Event = "Response" EventResponse Event = "Response"
// EventResponseError entries contain information about HTTP responses that returned
// an *azcore.ResponseError (i.e. responses with a non 2xx HTTP status code).
// This includes the contents of ResponseError.Error().
EventResponseError Event = "ResponseError"
// EventRetryPolicy entries contain information specific to the retry policy in use. // EventRetryPolicy entries contain information specific to the retry policy in use.
EventRetryPolicy Event = "Retry" EventRetryPolicy Event = "Retry"

View File

@ -39,6 +39,11 @@ type ClientOptions struct {
// Cloud specifies a cloud for the client. The default is Azure Public Cloud. // Cloud specifies a cloud for the client. The default is Azure Public Cloud.
Cloud cloud.Configuration Cloud cloud.Configuration
// InsecureAllowCredentialWithHTTP enables authenticated requests over HTTP.
// By default, authenticated requests to an HTTP endpoint are rejected by the client.
// WARNING: setting this to true will allow sending the credential in clear text. Use with caution.
InsecureAllowCredentialWithHTTP bool
// Logging configures the built-in logging policy. // Logging configures the built-in logging policy.
Logging LogOptions Logging LogOptions
@ -147,6 +152,11 @@ type BearerTokenOptions struct {
// When this field isn't set, the policy follows its default behavior of authorizing every request with a bearer token from // When this field isn't set, the policy follows its default behavior of authorizing every request with a bearer token from
// its given credential. // its given credential.
AuthorizationHandler AuthorizationHandler AuthorizationHandler AuthorizationHandler
// InsecureAllowCredentialWithHTTP enables authenticated requests over HTTP.
// By default, authenticated requests to an HTTP endpoint are rejected by the client.
// WARNING: setting this to true will allow sending the bearer token in clear text. Use with caution.
InsecureAllowCredentialWithHTTP bool
} }
// AuthorizationHandler allows SDK developers to insert custom logic that runs when BearerTokenPolicy must authorize a request. // AuthorizationHandler allows SDK developers to insert custom logic that runs when BearerTokenPolicy must authorize a request.

View File

@ -14,6 +14,14 @@ import (
// NewResponseError creates an *azcore.ResponseError from the provided HTTP response. // NewResponseError creates an *azcore.ResponseError from the provided HTTP response.
// Call this when a service request returns a non-successful status code. // Call this when a service request returns a non-successful status code.
// The error code will be extracted from the *http.Response, either from the x-ms-error-code
// header (preferred) or attempted to be parsed from the response body.
func NewResponseError(resp *http.Response) error { func NewResponseError(resp *http.Response) error {
return exported.NewResponseError(resp) return exported.NewResponseError(resp)
} }
// NewResponseErrorWithErrorCode creates an *azcore.ResponseError from the provided HTTP response and errorCode.
// Use this variant when the error code is in a non-standard location.
func NewResponseErrorWithErrorCode(resp *http.Response, errorCode string) error {
return exported.NewResponseErrorWithErrorCode(resp, errorCode)
}

View File

@ -24,6 +24,7 @@ type BearerTokenPolicy struct {
authzHandler policy.AuthorizationHandler authzHandler policy.AuthorizationHandler
cred exported.TokenCredential cred exported.TokenCredential
scopes []string scopes []string
allowHTTP bool
} }
type acquiringResourceState struct { type acquiringResourceState struct {
@ -55,6 +56,7 @@ func NewBearerTokenPolicy(cred exported.TokenCredential, scopes []string, opts *
cred: cred, cred: cred,
scopes: scopes, scopes: scopes,
mainResource: temporal.NewResource(acquire), mainResource: temporal.NewResource(acquire),
allowHTTP: opts.InsecureAllowCredentialWithHTTP,
} }
} }
@ -80,7 +82,7 @@ func (b *BearerTokenPolicy) Do(req *policy.Request) (*http.Response, error) {
return req.Next() return req.Next()
} }
if err := checkHTTPSForAuth(req); err != nil { if err := checkHTTPSForAuth(req, b.allowHTTP); err != nil {
return nil, err return nil, err
} }
@ -113,8 +115,8 @@ func (b *BearerTokenPolicy) Do(req *policy.Request) (*http.Response, error) {
return res, err return res, err
} }
func checkHTTPSForAuth(req *policy.Request) error { func checkHTTPSForAuth(req *policy.Request, allowHTTP bool) error {
if strings.ToLower(req.Raw().URL.Scheme) != "https" { if strings.ToLower(req.Raw().URL.Scheme) != "https" && !allowHTTP {
return errorinfo.NonRetriableError(errors.New("authenticated requests are not permitted for non TLS protected (https) endpoints")) return errorinfo.NonRetriableError(errors.New("authenticated requests are not permitted for non TLS protected (https) endpoints"))
} }
return nil return nil

View File

@ -15,10 +15,16 @@ type KeyCredentialPolicy struct {
cred *exported.KeyCredential cred *exported.KeyCredential
header string header string
prefix string prefix string
allowHTTP bool
} }
// KeyCredentialPolicyOptions contains the optional values configuring [KeyCredentialPolicy]. // KeyCredentialPolicyOptions contains the optional values configuring [KeyCredentialPolicy].
type KeyCredentialPolicyOptions struct { type KeyCredentialPolicyOptions struct {
// InsecureAllowCredentialWithHTTP enables authenticated requests over HTTP.
// By default, authenticated requests to an HTTP endpoint are rejected by the client.
// WARNING: setting this to true will allow sending the authentication key in clear text. Use with caution.
InsecureAllowCredentialWithHTTP bool
// Prefix is used if the key requires a prefix before it's inserted into the HTTP request. // Prefix is used if the key requires a prefix before it's inserted into the HTTP request.
Prefix string Prefix string
} }
@ -35,6 +41,7 @@ func NewKeyCredentialPolicy(cred *exported.KeyCredential, header string, options
cred: cred, cred: cred,
header: header, header: header,
prefix: options.Prefix, prefix: options.Prefix,
allowHTTP: options.InsecureAllowCredentialWithHTTP,
} }
} }
@ -44,7 +51,7 @@ func (k *KeyCredentialPolicy) Do(req *policy.Request) (*http.Response, error) {
// this prevents a panic that might be hard to diagnose and allows testing // this prevents a panic that might be hard to diagnose and allows testing
// against http endpoints that don't require authentication. // against http endpoints that don't require authentication.
if k.cred != nil { if k.cred != nil {
if err := checkHTTPSForAuth(req); err != nil { if err := checkHTTPSForAuth(req, k.allowHTTP); err != nil {
return nil, err return nil, err
} }
val := exported.KeyCredentialGet(k.cred) val := exported.KeyCredentialGet(k.cred)

View File

@ -14,11 +14,15 @@ import (
type SASCredentialPolicy struct { type SASCredentialPolicy struct {
cred *exported.SASCredential cred *exported.SASCredential
header string header string
allowHTTP bool
} }
// SASCredentialPolicyOptions contains the optional values configuring [SASCredentialPolicy]. // SASCredentialPolicyOptions contains the optional values configuring [SASCredentialPolicy].
type SASCredentialPolicyOptions struct { type SASCredentialPolicyOptions struct {
// placeholder for future optional values // InsecureAllowCredentialWithHTTP enables authenticated requests over HTTP.
// By default, authenticated requests to an HTTP endpoint are rejected by the client.
// WARNING: setting this to true will allow sending the authentication key in clear text. Use with caution.
InsecureAllowCredentialWithHTTP bool
} }
// NewSASCredentialPolicy creates a new instance of [SASCredentialPolicy]. // NewSASCredentialPolicy creates a new instance of [SASCredentialPolicy].
@ -26,9 +30,13 @@ type SASCredentialPolicyOptions struct {
// - header is the name of the HTTP request header in which the shared access signature is placed // - header is the name of the HTTP request header in which the shared access signature is placed
// - options contains optional configuration, pass nil to accept the default values // - options contains optional configuration, pass nil to accept the default values
func NewSASCredentialPolicy(cred *exported.SASCredential, header string, options *SASCredentialPolicyOptions) *SASCredentialPolicy { func NewSASCredentialPolicy(cred *exported.SASCredential, header string, options *SASCredentialPolicyOptions) *SASCredentialPolicy {
if options == nil {
options = &SASCredentialPolicyOptions{}
}
return &SASCredentialPolicy{ return &SASCredentialPolicy{
cred: cred, cred: cred,
header: header, header: header,
allowHTTP: options.InsecureAllowCredentialWithHTTP,
} }
} }
@ -38,7 +46,7 @@ func (k *SASCredentialPolicy) Do(req *policy.Request) (*http.Response, error) {
// this prevents a panic that might be hard to diagnose and allows testing // this prevents a panic that might be hard to diagnose and allows testing
// against http endpoints that don't require authentication. // against http endpoints that don't require authentication.
if k.cred != nil { if k.cred != nil {
if err := checkHTTPSForAuth(req); err != nil { if err := checkHTTPSForAuth(req, k.allowHTTP); err != nil {
return nil, err return nil, err
} }
req.Raw().Header.Add(k.header, exported.SASCredentialGet(k.cred)) req.Raw().Header.Add(k.header, exported.SASCredentialGet(k.cred))

View File

@ -154,7 +154,7 @@ func NewPollerFromResumeToken[T any](token string, pl exported.Pipeline, options
if err != nil { if err != nil {
return nil, err return nil, err
} }
var asJSON map[string]interface{} var asJSON map[string]any
if err := json.Unmarshal(raw, &asJSON); err != nil { if err := json.Unmarshal(raw, &asJSON); err != nil {
return nil, err return nil, err
} }
@ -240,7 +240,7 @@ func (p *Poller[T]) PollUntilDone(ctx context.Context, options *PollUntilDoneOpt
} }
start := time.Now() start := time.Now()
logPollUntilDoneExit := func(v interface{}) { logPollUntilDoneExit := func(v any) {
log.Writef(log.EventLRO, "END PollUntilDone() for %T: %v, total time: %s", p.op, v, time.Since(start)) log.Writef(log.EventLRO, "END PollUntilDone() for %T: %v, total time: %s", p.op, v, time.Since(start))
} }
log.Writef(log.EventLRO, "BEGIN PollUntilDone() for %T", p.op) log.Writef(log.EventLRO, "BEGIN PollUntilDone() for %T", p.op)
@ -334,6 +334,11 @@ func (p *Poller[T]) Result(ctx context.Context) (res T, err error) {
err = p.op.Result(ctx, p.result) err = p.op.Result(ctx, p.result)
var respErr *exported.ResponseError var respErr *exported.ResponseError
if errors.As(err, &respErr) { if errors.As(err, &respErr) {
if pollers.IsNonTerminalHTTPStatusCode(respErr.RawResponse) {
// the request failed in a non-terminal way.
// don't cache the error or mark the Poller as done
return
}
// the LRO failed. record the error // the LRO failed. record the error
p.err = err p.err = err
} else if err != nil { } else if err != nil {

View File

@ -11,9 +11,11 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"errors"
"fmt" "fmt"
"io" "io"
"mime/multipart" "mime/multipart"
"net/textproto"
"net/url" "net/url"
"path" "path"
"strings" "strings"
@ -21,6 +23,7 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported" "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared" "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming"
) )
// Base64Encoding is usesd to specify which base-64 encoder/decoder to use when // Base64Encoding is usesd to specify which base-64 encoder/decoder to use when
@ -42,12 +45,19 @@ func NewRequest(ctx context.Context, httpMethod string, endpoint string) (*polic
} }
// EncodeQueryParams will parse and encode any query parameters in the specified URL. // EncodeQueryParams will parse and encode any query parameters in the specified URL.
// Any semicolons will automatically be escaped.
func EncodeQueryParams(u string) (string, error) { func EncodeQueryParams(u string) (string, error) {
before, after, found := strings.Cut(u, "?") before, after, found := strings.Cut(u, "?")
if !found { if !found {
return u, nil return u, nil
} }
qp, err := url.ParseQuery(after) // starting in Go 1.17, url.ParseQuery will reject semicolons in query params.
// so, we must escape them first. note that this assumes that semicolons aren't
// being used as query param separators which is per the current RFC.
// for more info:
// https://github.com/golang/go/issues/25192
// https://github.com/golang/go/issues/50034
qp, err := url.ParseQuery(strings.ReplaceAll(after, ";", "%3B"))
if err != nil { if err != nil {
return "", err return "", err
} }
@ -97,20 +107,22 @@ func EncodeByteArray(v []byte, format Base64Encoding) string {
func MarshalAsByteArray(req *policy.Request, v []byte, format Base64Encoding) error { func MarshalAsByteArray(req *policy.Request, v []byte, format Base64Encoding) error {
// send as a JSON string // send as a JSON string
encode := fmt.Sprintf("\"%s\"", EncodeByteArray(v, format)) encode := fmt.Sprintf("\"%s\"", EncodeByteArray(v, format))
return req.SetBody(exported.NopCloser(strings.NewReader(encode)), shared.ContentTypeAppJSON) // tsp generated code can set Content-Type so we must prefer that
return exported.SetBody(req, exported.NopCloser(strings.NewReader(encode)), shared.ContentTypeAppJSON, false)
} }
// MarshalAsJSON calls json.Marshal() to get the JSON encoding of v then calls SetBody. // MarshalAsJSON calls json.Marshal() to get the JSON encoding of v then calls SetBody.
func MarshalAsJSON(req *policy.Request, v interface{}) error { func MarshalAsJSON(req *policy.Request, v any) error {
b, err := json.Marshal(v) b, err := json.Marshal(v)
if err != nil { if err != nil {
return fmt.Errorf("error marshalling type %T: %s", v, err) return fmt.Errorf("error marshalling type %T: %s", v, err)
} }
return req.SetBody(exported.NopCloser(bytes.NewReader(b)), shared.ContentTypeAppJSON) // tsp generated code can set Content-Type so we must prefer that
return exported.SetBody(req, exported.NopCloser(bytes.NewReader(b)), shared.ContentTypeAppJSON, false)
} }
// MarshalAsXML calls xml.Marshal() to get the XML encoding of v then calls SetBody. // MarshalAsXML calls xml.Marshal() to get the XML encoding of v then calls SetBody.
func MarshalAsXML(req *policy.Request, v interface{}) error { func MarshalAsXML(req *policy.Request, v any) error {
b, err := xml.Marshal(v) b, err := xml.Marshal(v)
if err != nil { if err != nil {
return fmt.Errorf("error marshalling type %T: %s", v, err) return fmt.Errorf("error marshalling type %T: %s", v, err)
@ -120,10 +132,10 @@ func MarshalAsXML(req *policy.Request, v interface{}) error {
return req.SetBody(exported.NopCloser(bytes.NewReader(b)), shared.ContentTypeAppXML) return req.SetBody(exported.NopCloser(bytes.NewReader(b)), shared.ContentTypeAppXML)
} }
// SetMultipartFormData writes the specified keys/values as multi-part form // SetMultipartFormData writes the specified keys/values as multi-part form fields with the specified value.
// fields with the specified value. File content must be specified as a ReadSeekCloser. // File content must be specified as an [io.ReadSeekCloser] or [streaming.MultipartContent].
// All other values are treated as string values. // Byte slices will be treated as JSON. All other values are treated as string values.
func SetMultipartFormData(req *policy.Request, formData map[string]interface{}) error { func SetMultipartFormData(req *policy.Request, formData map[string]any) error {
body := bytes.Buffer{} body := bytes.Buffer{}
writer := multipart.NewWriter(&body) writer := multipart.NewWriter(&body)
@ -139,6 +151,60 @@ func SetMultipartFormData(req *policy.Request, formData map[string]interface{})
return nil return nil
} }
quoteEscaper := strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
writeMultipartContent := func(fieldname string, mpc streaming.MultipartContent) error {
if mpc.Body == nil {
return errors.New("streaming.MultipartContent.Body cannot be nil")
}
// use fieldname for the file name when unspecified
filename := fieldname
if mpc.ContentType == "" && mpc.Filename == "" {
return writeContent(fieldname, filename, mpc.Body)
}
if mpc.Filename != "" {
filename = mpc.Filename
}
// this is pretty much copied from multipart.Writer.CreateFormFile
// but lets us set the caller provided Content-Type and filename
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition",
fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
quoteEscaper.Replace(fieldname), quoteEscaper.Replace(filename)))
contentType := "application/octet-stream"
if mpc.ContentType != "" {
contentType = mpc.ContentType
}
h.Set("Content-Type", contentType)
fd, err := writer.CreatePart(h)
if err != nil {
return err
}
// copy the data to the form file
if _, err = io.Copy(fd, mpc.Body); err != nil {
return err
}
return nil
}
// the same as multipart.Writer.WriteField but lets us specify the Content-Type
writeField := func(fieldname, contentType string, value string) error {
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition",
fmt.Sprintf(`form-data; name="%s"`, quoteEscaper.Replace(fieldname)))
h.Set("Content-Type", contentType)
fd, err := writer.CreatePart(h)
if err != nil {
return err
}
if _, err = fd.Write([]byte(value)); err != nil {
return err
}
return nil
}
for k, v := range formData { for k, v := range formData {
if rsc, ok := v.(io.ReadSeekCloser); ok { if rsc, ok := v.(io.ReadSeekCloser); ok {
if err := writeContent(k, k, rsc); err != nil { if err := writeContent(k, k, rsc); err != nil {
@ -152,13 +218,35 @@ func SetMultipartFormData(req *policy.Request, formData map[string]interface{})
} }
} }
continue continue
} else if mpc, ok := v.(streaming.MultipartContent); ok {
if err := writeMultipartContent(k, mpc); err != nil {
return err
} }
continue
} else if mpcs, ok := v.([]streaming.MultipartContent); ok {
for _, mpc := range mpcs {
if err := writeMultipartContent(k, mpc); err != nil {
return err
}
}
continue
}
var content string
contentType := shared.ContentTypeTextPlain
switch tt := v.(type) {
case []byte:
// JSON, don't quote it
content = string(tt)
contentType = shared.ContentTypeAppJSON
case string:
content = tt
default:
// ensure the value is in string format // ensure the value is in string format
s, ok := v.(string) content = fmt.Sprintf("%v", v)
if !ok {
s = fmt.Sprintf("%v", v)
} }
if err := writer.WriteField(k, s); err != nil {
if err := writeField(k, contentType, content); err != nil {
return err return err
} }
} }

View File

@ -40,7 +40,7 @@ func UnmarshalAsByteArray(resp *http.Response, v *[]byte, format Base64Encoding)
} }
// UnmarshalAsJSON calls json.Unmarshal() to unmarshal the received payload into the value pointed to by v. // UnmarshalAsJSON calls json.Unmarshal() to unmarshal the received payload into the value pointed to by v.
func UnmarshalAsJSON(resp *http.Response, v interface{}) error { func UnmarshalAsJSON(resp *http.Response, v any) error {
payload, err := Payload(resp) payload, err := Payload(resp)
if err != nil { if err != nil {
return err return err
@ -61,7 +61,7 @@ func UnmarshalAsJSON(resp *http.Response, v interface{}) error {
} }
// UnmarshalAsXML calls xml.Unmarshal() to unmarshal the received payload into the value pointed to by v. // UnmarshalAsXML calls xml.Unmarshal() to unmarshal the received payload into the value pointed to by v.
func UnmarshalAsXML(resp *http.Response, v interface{}) error { func UnmarshalAsXML(resp *http.Response, v any) error {
payload, err := Payload(resp) payload, err := Payload(resp)
if err != nil { if err != nil {
return err return err

View File

@ -73,3 +73,17 @@ func (p *progress) Seek(offset int64, whence int) (int64, error) {
func (p *progress) Close() error { func (p *progress) Close() error {
return p.rc.Close() return p.rc.Close()
} }
// MultipartContent contains streaming content used in multipart/form payloads.
type MultipartContent struct {
// Body contains the required content body.
Body io.ReadSeekCloser
// ContentType optionally specifies the HTTP Content-Type for this Body.
// The default value is application/octet-stream.
ContentType string
// Filename optionally specifies the filename for this Body.
// The default value is the field name for the multipart/form section.
Filename string
}

View File

@ -0,0 +1,4 @@
# live test artifacts
Dockerfile
k8s.yaml
sshkey*

View File

@ -1,5 +1,66 @@
# Release History # Release History
## 1.6.0 (2024-06-10)
### Features Added
* `NewOnBehalfOfCredentialWithClientAssertions` creates an on-behalf-of credential
that authenticates with client assertions such as federated credentials
### Breaking Changes
> These changes affect only code written against a beta version such as v1.6.0-beta.4
* Removed `AzurePipelinesCredential` and the persistent token caching API.
They will return in v1.7.0-beta.1
### Bugs Fixed
* Managed identity bug fixes
## 1.6.0-beta.4 (2024-05-14)
### Features Added
* `AzurePipelinesCredential` authenticates an Azure Pipeline service connection with
workload identity federation
## 1.6.0-beta.3 (2024-04-09)
### Breaking Changes
* `DefaultAzureCredential` now sends a probe request with no retries for IMDS managed identity
environments to avoid excessive retry delays when the IMDS endpoint is not available. This
should improve credential chain resolution for local development scenarios.
### Bugs Fixed
* `ManagedIdentityCredential` now specifies resource IDs correctly for Azure Container Instances
## 1.5.2 (2024-04-09)
### Bugs Fixed
* `ManagedIdentityCredential` now specifies resource IDs correctly for Azure Container Instances
### Other Changes
* Restored v1.4.0 error behavior for empty tenant IDs
* Upgraded dependencies
## 1.6.0-beta.2 (2024-02-06)
### Breaking Changes
> These changes affect only code written against a beta version such as v1.6.0-beta.1
* Replaced `ErrAuthenticationRequired` with `AuthenticationRequiredError`, a struct
type that carries the `TokenRequestOptions` passed to the `GetToken` call which
returned the error.
### Bugs Fixed
* Fixed more cases in which credential chains like `DefaultAzureCredential`
should try their next credential after attempting managed identity
authentication in a Docker Desktop container
### Other Changes
* `AzureCLICredential` uses the CLI's `expires_on` value for token expiration
## 1.6.0-beta.1 (2024-01-17)
### Features Added
* Restored persistent token caching API first added in v1.5.0-beta.1
* Added `AzureCLICredentialOptions.Subscription`
## 1.5.1 (2024-01-17) ## 1.5.1 (2024-01-17)
### Bugs Fixed ### Bugs Fixed
@ -126,7 +187,7 @@
### Features Added ### Features Added
* By default, credentials set client capability "CP1" to enable support for * By default, credentials set client capability "CP1" to enable support for
[Continuous Access Evaluation (CAE)](https://docs.microsoft.com/azure/active-directory/develop/app-resilience-continuous-access-evaluation). [Continuous Access Evaluation (CAE)](https://learn.microsoft.com/entra/identity-platform/app-resilience-continuous-access-evaluation).
This indicates to Microsoft Entra ID that your application can handle CAE claims challenges. This indicates to Microsoft Entra ID that your application can handle CAE claims challenges.
You can disable this behavior by setting the environment variable "AZURE_IDENTITY_DISABLE_CP1" to "true". You can disable this behavior by setting the environment variable "AZURE_IDENTITY_DISABLE_CP1" to "true".
* `InteractiveBrowserCredentialOptions.LoginHint` enables pre-populating the login * `InteractiveBrowserCredentialOptions.LoginHint` enables pre-populating the login

View File

@ -1,6 +1,6 @@
# Migrating from autorest/adal to azidentity # Migrating from autorest/adal to azidentity
`azidentity` provides Microsoft Entra ID ([formerly Azure Active Directory](https://learn.microsoft.com/azure/active-directory/fundamentals/new-name)) authentication for the newest Azure SDK modules (`github.com/azure-sdk-for-go/sdk/...`). Older Azure SDK packages (`github.com/azure-sdk-for-go/services/...`) use types from `github.com/go-autorest/autorest/adal` instead. `azidentity` provides Microsoft Entra ID ([formerly Azure Active Directory](https://learn.microsoft.com/entra/fundamentals/new-name)) authentication for the newest Azure SDK modules (`github.com/azure-sdk-for-go/sdk/...`). Older Azure SDK packages (`github.com/azure-sdk-for-go/services/...`) use types from `github.com/go-autorest/autorest/adal` instead.
This guide shows common authentication code using `autorest/adal` and its equivalent using `azidentity`. This guide shows common authentication code using `autorest/adal` and its equivalent using `azidentity`.
@ -284,7 +284,7 @@ if err == nil {
} }
``` ```
Note that `azidentity` credentials use the Microsoft Entra endpoint, which requires OAuth 2 scopes instead of the resource identifiers `autorest/adal` expects. For more information, see [Microsoft Entra ID documentation](https://learn.microsoft.com/azure/active-directory/develop/permissions-consent-overview). Note that `azidentity` credentials use the Microsoft Entra endpoint, which requires OAuth 2 scopes instead of the resource identifiers `autorest/adal` expects. For more information, see [Microsoft Entra ID documentation](https://learn.microsoft.com/entra/identity-platform/permissions-consent-overview).
## Use azidentity credentials with older packages ## Use azidentity credentials with older packages

View File

@ -1,9 +1,9 @@
# Azure Identity Client Module for Go # Azure Identity Client Module for Go
The Azure Identity module provides Microsoft Entra ID ([formerly Azure Active Directory](https://learn.microsoft.com/azure/active-directory/fundamentals/new-name)) token authentication support across the Azure SDK. It includes a set of `TokenCredential` implementations, which can be used with Azure SDK clients supporting token authentication. The Azure Identity module provides Microsoft Entra ID ([formerly Azure Active Directory](https://learn.microsoft.com/entra/fundamentals/new-name)) token authentication support across the Azure SDK. It includes a set of `TokenCredential` implementations, which can be used with Azure SDK clients supporting token authentication.
[![PkgGoDev](https://pkg.go.dev/badge/github.com/Azure/azure-sdk-for-go/sdk/azidentity)](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity) [![PkgGoDev](https://pkg.go.dev/badge/github.com/Azure/azure-sdk-for-go/sdk/azidentity)](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity)
| [Microsoft Entra ID documentation](https://learn.microsoft.com/azure/active-directory/) | [Microsoft Entra ID documentation](https://learn.microsoft.com/entra/identity/)
| [Source code](https://github.com/Azure/azure-sdk-for-go/tree/main/sdk/azidentity) | [Source code](https://github.com/Azure/azure-sdk-for-go/tree/main/sdk/azidentity)
# Getting started # Getting started
@ -30,7 +30,7 @@ When debugging and executing code locally, developers typically use their own ac
#### Authenticating via the Azure CLI #### Authenticating via the Azure CLI
`DefaultAzureCredential` and `AzureCLICredential` can authenticate as the user `DefaultAzureCredential` and `AzureCLICredential` can authenticate as the user
signed in to the [Azure CLI](https://docs.microsoft.com/cli/azure). To sign in to the Azure CLI, run `az login`. On a system with a default web browser, the Azure CLI will launch the browser to authenticate a user. signed in to the [Azure CLI](https://learn.microsoft.com/cli/azure). To sign in to the Azure CLI, run `az login`. On a system with a default web browser, the Azure CLI will launch the browser to authenticate a user.
When no default browser is available, `az login` will use the device code When no default browser is available, `az login` will use the device code
authentication flow. This can also be selected manually by running `az login --use-device-code`. authentication flow. This can also be selected manually by running `az login --use-device-code`.
@ -69,14 +69,14 @@ The `azidentity` module focuses on OAuth authentication with Microsoft Entra ID.
## Managed Identity ## Managed Identity
`DefaultAzureCredential` and `ManagedIdentityCredential` support `DefaultAzureCredential` and `ManagedIdentityCredential` support
[managed identity authentication](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview) [managed identity authentication](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview)
in any hosting environment which supports managed identities, such as (this list is not exhaustive): in any hosting environment which supports managed identities, such as (this list is not exhaustive):
* [Azure App Service](https://docs.microsoft.com/azure/app-service/overview-managed-identity) * [Azure App Service](https://learn.microsoft.com/azure/app-service/overview-managed-identity)
* [Azure Arc](https://docs.microsoft.com/azure/azure-arc/servers/managed-identity-authentication) * [Azure Arc](https://learn.microsoft.com/azure/azure-arc/servers/managed-identity-authentication)
* [Azure Cloud Shell](https://docs.microsoft.com/azure/cloud-shell/msi-authorization) * [Azure Cloud Shell](https://learn.microsoft.com/azure/cloud-shell/msi-authorization)
* [Azure Kubernetes Service](https://docs.microsoft.com/azure/aks/use-managed-identity) * [Azure Kubernetes Service](https://learn.microsoft.com/azure/aks/use-managed-identity)
* [Azure Service Fabric](https://docs.microsoft.com/azure/service-fabric/concepts-managed-identity) * [Azure Service Fabric](https://learn.microsoft.com/azure/service-fabric/concepts-managed-identity)
* [Azure Virtual Machines](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token) * [Azure Virtual Machines](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/how-to-use-vm-token)
## Examples ## Examples
@ -207,7 +207,7 @@ For more details, see the [token caching documentation](https://aka.ms/azsdk/go/
Credentials return an `error` when they fail to authenticate or lack data they require to authenticate. For guidance on resolving errors from specific credential types, see the [troubleshooting guide](https://aka.ms/azsdk/go/identity/troubleshoot). Credentials return an `error` when they fail to authenticate or lack data they require to authenticate. For guidance on resolving errors from specific credential types, see the [troubleshooting guide](https://aka.ms/azsdk/go/identity/troubleshoot).
For more details on handling specific Microsoft Entra errors, see the Microsoft Entra [error code documentation](https://learn.microsoft.com/azure/active-directory/develop/reference-error-codes). For more details on handling specific Microsoft Entra errors, see the Microsoft Entra [error code documentation](https://learn.microsoft.com/entra/identity-platform/reference-error-codes).
### Logging ### Logging

View File

@ -45,7 +45,7 @@ With persistent disk token caching enabled, the library first determines if a va
#### Example code #### Example code
See the [package documentation](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity@v1.5.0-beta.1#pkg-overview) for code examples demonstrating how to configure persistent caching and access cached data. See the [package documentation](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity@v1.6.0-beta.2#pkg-overview) for example code demonstrating how to configure persistent caching and access cached data.
### Credentials supporting token caching ### Credentials supporting token caching

View File

@ -58,7 +58,7 @@ This error contains several pieces of information:
- __Failing Credential Type__: The type of credential that failed to authenticate. This can be helpful when diagnosing issues with chained credential types such as `DefaultAzureCredential` or `ChainedTokenCredential`. - __Failing Credential Type__: The type of credential that failed to authenticate. This can be helpful when diagnosing issues with chained credential types such as `DefaultAzureCredential` or `ChainedTokenCredential`.
- __Microsoft Entra ID Error Code and Message__: The error code and message returned by Microsoft Entra ID. This can give insight into the specific reason the request failed. For instance, in this case authentication failed because the provided client secret is incorrect. [Microsoft Entra ID documentation](https://learn.microsoft.com/azure/active-directory/develop/reference-error-codes#aadsts-error-codes) has more information on AADSTS error codes. - __Microsoft Entra ID Error Code and Message__: The error code and message returned by Microsoft Entra ID. This can give insight into the specific reason the request failed. For instance, in this case authentication failed because the provided client secret is incorrect. [Microsoft Entra ID documentation](https://learn.microsoft.com/entra/identity-platform/reference-error-codes#aadsts-error-codes) has more information on AADSTS error codes.
- __Correlation ID and Timestamp__: The correlation ID and timestamp identify the request in server-side logs. This information can be useful to support engineers diagnosing unexpected Microsoft Entra failures. - __Correlation ID and Timestamp__: The correlation ID and timestamp identify the request in server-side logs. This information can be useful to support engineers diagnosing unexpected Microsoft Entra failures.
@ -97,17 +97,17 @@ azlog.SetEvents(azidentity.EventAuthentication)
| Error Code | Issue | Mitigation | | Error Code | Issue | Mitigation |
|---|---|---| |---|---|---|
|AADSTS7000215|An invalid client secret was provided.|Ensure the secret provided to the credential constructor is valid. If unsure, create a new client secret using the Azure portal. Details on creating a new client secret are in [Microsoft Entra ID documentation](https://learn.microsoft.com/azure/active-directory/develop/howto-create-service-principal-portal#option-2-create-a-new-application-secret).| |AADSTS7000215|An invalid client secret was provided.|Ensure the secret provided to the credential constructor is valid. If unsure, create a new client secret using the Azure portal. Details on creating a new client secret are in [Microsoft Entra ID documentation](https://learn.microsoft.com/entra/identity-platform/howto-create-service-principal-portal#option-2-create-a-new-application-secret).|
|AADSTS7000222|An expired client secret was provided.|Create a new client secret using the Azure portal. Details on creating a new client secret are in [Microsoft Entra ID documentation](https://learn.microsoft.com/azure/active-directory/develop/howto-create-service-principal-portal#option-2-create-a-new-application-secret).| |AADSTS7000222|An expired client secret was provided.|Create a new client secret using the Azure portal. Details on creating a new client secret are in [Microsoft Entra ID documentation](https://learn.microsoft.com/entra/identity-platform/howto-create-service-principal-portal#option-2-create-a-new-application-secret).|
|AADSTS700016|The specified application wasn't found in the specified tenant.|Ensure the client and tenant IDs provided to the credential constructor are correct for your application registration. For multi-tenant apps, ensure the application has been added to the desired tenant by a tenant admin. To add a new application in the desired tenant, follow the [Microsoft Entra ID instructions](https://learn.microsoft.com/azure/active-directory/develop/howto-create-service-principal-portal).| |AADSTS700016|The specified application wasn't found in the specified tenant.|Ensure the client and tenant IDs provided to the credential constructor are correct for your application registration. For multi-tenant apps, ensure the application has been added to the desired tenant by a tenant admin. To add a new application in the desired tenant, follow the [Microsoft Entra ID instructions](https://learn.microsoft.com/entra/identity-platform/howto-create-service-principal-portal).|
<a id="client-cert"></a> <a id="client-cert"></a>
## Troubleshoot ClientCertificateCredential authentication issues ## Troubleshoot ClientCertificateCredential authentication issues
| Error Code | Description | Mitigation | | Error Code | Description | Mitigation |
|---|---|---| |---|---|---|
|AADSTS700027|Client assertion contains an invalid signature.|Ensure the specified certificate has been uploaded to the application registration as described in [Microsoft Entra ID documentation](https://learn.microsoft.com/azure/active-directory/develop/howto-create-service-principal-portal#option-1-upload-a-certificate).| |AADSTS700027|Client assertion contains an invalid signature.|Ensure the specified certificate has been uploaded to the application registration as described in [Microsoft Entra ID documentation](https://learn.microsoft.com/entra/identity-platform/howto-create-service-principal-portal#option-1-upload-a-certificate).|
|AADSTS700016|The specified application wasn't found in the specified tenant.|Ensure the client and tenant IDs provided to the credential constructor are correct for your application registration. For multi-tenant apps, ensure the application has been added to the desired tenant by a tenant admin. To add a new application in the desired tenant, follow the [Microsoft Entra ID instructions](https://learn.microsoft.com/azure/active-directory/develop/howto-create-service-principal-portal).| |AADSTS700016|The specified application wasn't found in the specified tenant.|Ensure the client and tenant IDs provided to the credential constructor are correct for your application registration. For multi-tenant apps, ensure the application has been added to the desired tenant by a tenant admin. To add a new application in the desired tenant, follow the [Microsoft Entra ID instructions](https://learn.microsoft.com/entra/identity-platform/howto-create-service-principal-portal).|
<a id="username-password"></a> <a id="username-password"></a>
## Troubleshoot UsernamePasswordCredential authentication issues ## Troubleshoot UsernamePasswordCredential authentication issues
@ -123,20 +123,20 @@ azlog.SetEvents(azidentity.EventAuthentication)
|Host Environment| | | |Host Environment| | |
|---|---|---| |---|---|---|
|Azure Virtual Machines and Scale Sets|[Configuration](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/qs-configure-portal-windows-vm)|[Troubleshooting](#azure-virtual-machine-managed-identity)| |Azure Virtual Machines and Scale Sets|[Configuration](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/qs-configure-portal-windows-vm)|[Troubleshooting](#azure-virtual-machine-managed-identity)|
|Azure App Service and Azure Functions|[Configuration](https://docs.microsoft.com/azure/app-service/overview-managed-identity)|[Troubleshooting](#azure-app-service-and-azure-functions-managed-identity)| |Azure App Service and Azure Functions|[Configuration](https://learn.microsoft.com/azure/app-service/overview-managed-identity)|[Troubleshooting](#azure-app-service-and-azure-functions-managed-identity)|
|Azure Kubernetes Service|[Configuration](https://azure.github.io/aad-pod-identity/docs/)|[Troubleshooting](#azure-kubernetes-service-managed-identity)| |Azure Kubernetes Service|[Configuration](https://azure.github.io/aad-pod-identity/docs/)|[Troubleshooting](#azure-kubernetes-service-managed-identity)|
|Azure Arc|[Configuration](https://docs.microsoft.com/azure/azure-arc/servers/managed-identity-authentication)|| |Azure Arc|[Configuration](https://learn.microsoft.com/azure/azure-arc/servers/managed-identity-authentication)||
|Azure Service Fabric|[Configuration](https://docs.microsoft.com/azure/service-fabric/concepts-managed-identity)|| |Azure Service Fabric|[Configuration](https://learn.microsoft.com/azure/service-fabric/concepts-managed-identity)||
### Azure Virtual Machine managed identity ### Azure Virtual Machine managed identity
| Error Message |Description| Mitigation | | Error Message |Description| Mitigation |
|---|---|---| |---|---|---|
|The requested identity hasnt been assigned to this resource.|The IMDS endpoint responded with a status code of 400, indicating the requested identity isnt assigned to the VM.|If using a user assigned identity, ensure the specified ID is correct.<p/><p/>If using a system assigned identity, make sure it has been enabled as described in [managed identity documentation](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/qs-configure-portal-windows-vm#enable-system-assigned-managed-identity-on-an-existing-vm).| |The requested identity hasnt been assigned to this resource.|The IMDS endpoint responded with a status code of 400, indicating the requested identity isnt assigned to the VM.|If using a user assigned identity, ensure the specified ID is correct.<p/><p/>If using a system assigned identity, make sure it has been enabled as described in [managed identity documentation](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/qs-configure-portal-windows-vm#enable-system-assigned-managed-identity-on-an-existing-vm).|
|The request failed due to a gateway error.|The request to the IMDS endpoint failed due to a gateway error, 502 or 504 status code.|IMDS doesn't support requests via proxy or gateway. Disable proxies or gateways running on the VM for requests to the IMDS endpoint `http://169.254.169.254`| |The request failed due to a gateway error.|The request to the IMDS endpoint failed due to a gateway error, 502 or 504 status code.|IMDS doesn't support requests via proxy or gateway. Disable proxies or gateways running on the VM for requests to the IMDS endpoint `http://169.254.169.254`|
|No response received from the managed identity endpoint.|No response was received for the request to IMDS or the request timed out.|<ul><li>Ensure the VM is configured for managed identity as described in [managed identity documentation](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/qs-configure-portal-windows-vm).</li><li>Verify the IMDS endpoint is reachable on the VM. See [below](#verify-imds-is-available-on-the-vm) for instructions.</li></ul>| |No response received from the managed identity endpoint.|No response was received for the request to IMDS or the request timed out.|<ul><li>Ensure the VM is configured for managed identity as described in [managed identity documentation](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/qs-configure-portal-windows-vm).</li><li>Verify the IMDS endpoint is reachable on the VM. See [below](#verify-imds-is-available-on-the-vm) for instructions.</li></ul>|
|Multiple attempts failed to obtain a token from the managed identity endpoint.|The credential has exhausted its retries for a token request.|<ul><li>Refer to the error message for more details on specific failures.<li>Ensure the VM is configured for managed identity as described in [managed identity documentation](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/qs-configure-portal-windows-vm).</li><li>Verify the IMDS endpoint is reachable on the VM. See [below](#verify-imds-is-available-on-the-vm) for instructions.</li></ul>| |Multiple attempts failed to obtain a token from the managed identity endpoint.|The credential has exhausted its retries for a token request.|<ul><li>Refer to the error message for more details on specific failures.<li>Ensure the VM is configured for managed identity as described in [managed identity documentation](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/qs-configure-portal-windows-vm).</li><li>Verify the IMDS endpoint is reachable on the VM. See [below](#verify-imds-is-available-on-the-vm) for instructions.</li></ul>|
#### Verify IMDS is available on the VM #### Verify IMDS is available on the VM
@ -152,7 +152,7 @@ curl 'http://169.254.169.254/metadata/identity/oauth2/token?resource=https://man
| Error Message |Description| Mitigation | | Error Message |Description| Mitigation |
|---|---|---| |---|---|---|
|Get "`http://169.254.169.254/...`" i/o timeout|The App Service host hasn't set environment variables for managed identity configuration.|<ul><li>Ensure the App Service is configured for managed identity as described in [App Service documentation](https://docs.microsoft.com/azure/app-service/overview-managed-identity).</li><li>Verify the App Service environment is properly configured and the managed identity endpoint is available. See [below](#verify-the-app-service-managed-identity-endpoint-is-available) for instructions.</li></ul>| |Get "`http://169.254.169.254/...`" i/o timeout|The App Service host hasn't set environment variables for managed identity configuration.|<ul><li>Ensure the App Service is configured for managed identity as described in [App Service documentation](https://learn.microsoft.com/azure/app-service/overview-managed-identity).</li><li>Verify the App Service environment is properly configured and the managed identity endpoint is available. See [below](#verify-the-app-service-managed-identity-endpoint-is-available) for instructions.</li></ul>|
#### Verify the App Service managed identity endpoint is available #### Verify the App Service managed identity endpoint is available
@ -177,8 +177,8 @@ curl "$IDENTITY_ENDPOINT?resource=https://management.core.windows.net&api-versio
| Error Message |Description| Mitigation | | Error Message |Description| Mitigation |
|---|---|---| |---|---|---|
|Azure CLI not found on path|The Azure CLI isnt installed or isn't on the application's path.|<ul><li>Ensure the Azure CLI is installed as described in [Azure CLI documentation](https://docs.microsoft.com/cli/azure/install-azure-cli).</li><li>Validate the installation location is in the application's `PATH` environment variable.</li></ul>| |Azure CLI not found on path|The Azure CLI isnt installed or isn't on the application's path.|<ul><li>Ensure the Azure CLI is installed as described in [Azure CLI documentation](https://learn.microsoft.com/cli/azure/install-azure-cli).</li><li>Validate the installation location is in the application's `PATH` environment variable.</li></ul>|
|Please run 'az login' to set up account|No account is currently logged into the Azure CLI, or the login has expired.|<ul><li>Run `az login` to log into the Azure CLI. More information about Azure CLI authentication is available in the [Azure CLI documentation](https://docs.microsoft.com/cli/azure/authenticate-azure-cli).</li><li>Verify that the Azure CLI can obtain tokens. See [below](#verify-the-azure-cli-can-obtain-tokens) for instructions.</li></ul>| |Please run 'az login' to set up account|No account is currently logged into the Azure CLI, or the login has expired.|<ul><li>Run `az login` to log into the Azure CLI. More information about Azure CLI authentication is available in the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli).</li><li>Verify that the Azure CLI can obtain tokens. See [below](#verify-the-azure-cli-can-obtain-tokens) for instructions.</li></ul>|
#### Verify the Azure CLI can obtain tokens #### Verify the Azure CLI can obtain tokens

View File

@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "go", "AssetsRepoPrefixPath": "go",
"TagPrefix": "go/azidentity", "TagPrefix": "go/azidentity",
"Tag": "go/azidentity_db4a26f583" "Tag": "go/azidentity_087379b475"
} }

View File

@ -121,6 +121,9 @@ func alphanumeric(r rune) bool {
} }
func validTenantID(tenantID string) bool { func validTenantID(tenantID string) bool {
if len(tenantID) < 1 {
return false
}
for _, r := range tenantID { for _, r := range tenantID {
if !(alphanumeric(r) || r == '.' || r == '-') { if !(alphanumeric(r) || r == '.' || r == '-') {
return false return false

View File

@ -35,9 +35,9 @@ type AzureCLICredentialOptions struct {
// logged in account can access. // logged in account can access.
AdditionallyAllowedTenants []string AdditionallyAllowedTenants []string
// subscription is the name or ID of a subscription. Set this to acquire tokens for an account other // Subscription is the name or ID of a subscription. Set this to acquire tokens for an account other
// than the Azure CLI's current account. // than the Azure CLI's current account.
subscription string Subscription string
// TenantID identifies the tenant the credential should authenticate in. // TenantID identifies the tenant the credential should authenticate in.
// Defaults to the CLI's default tenant, which is typically the home tenant of the logged in user. // Defaults to the CLI's default tenant, which is typically the home tenant of the logged in user.
@ -68,9 +68,9 @@ func NewAzureCLICredential(options *AzureCLICredentialOptions) (*AzureCLICredent
if options != nil { if options != nil {
cp = *options cp = *options
} }
for _, r := range cp.subscription { for _, r := range cp.Subscription {
if !(alphanumeric(r) || r == '-' || r == '_' || r == ' ' || r == '.') { if !(alphanumeric(r) || r == '-' || r == '_' || r == ' ' || r == '.') {
return nil, fmt.Errorf("%s: invalid Subscription %q", credNameAzureCLI, cp.subscription) return nil, fmt.Errorf("%s: invalid Subscription %q", credNameAzureCLI, cp.Subscription)
} }
} }
if cp.TenantID != "" && !validTenantID(cp.TenantID) { if cp.TenantID != "" && !validTenantID(cp.TenantID) {
@ -97,7 +97,7 @@ func (c *AzureCLICredential) GetToken(ctx context.Context, opts policy.TokenRequ
} }
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
b, err := c.opts.tokenProvider(ctx, opts.Scopes, tenant, c.opts.subscription) b, err := c.opts.tokenProvider(ctx, opts.Scopes, tenant, c.opts.Subscription)
if err == nil { if err == nil {
at, err = c.createAccessToken(b) at, err = c.createAccessToken(b)
} }
@ -164,25 +164,20 @@ var defaultAzTokenProvider azTokenProvider = func(ctx context.Context, scopes []
func (c *AzureCLICredential) createAccessToken(tk []byte) (azcore.AccessToken, error) { func (c *AzureCLICredential) createAccessToken(tk []byte) (azcore.AccessToken, error) {
t := struct { t := struct {
AccessToken string `json:"accessToken"` AccessToken string `json:"accessToken"`
Authority string `json:"_authority"` Expires_On int64 `json:"expires_on"`
ClientID string `json:"_clientId"`
ExpiresOn string `json:"expiresOn"` ExpiresOn string `json:"expiresOn"`
IdentityProvider string `json:"identityProvider"`
IsMRRT bool `json:"isMRRT"`
RefreshToken string `json:"refreshToken"`
Resource string `json:"resource"`
TokenType string `json:"tokenType"`
UserID string `json:"userId"`
}{} }{}
err := json.Unmarshal(tk, &t) err := json.Unmarshal(tk, &t)
if err != nil { if err != nil {
return azcore.AccessToken{}, err return azcore.AccessToken{}, err
} }
// the Azure CLI's "expiresOn" is local time exp := time.Unix(t.Expires_On, 0)
exp, err := time.ParseInLocation("2006-01-02 15:04:05.999999", t.ExpiresOn, time.Local) if t.Expires_On == 0 {
exp, err = time.ParseInLocation("2006-01-02 15:04:05.999999", t.ExpiresOn, time.Local)
if err != nil { if err != nil {
return azcore.AccessToken{}, fmt.Errorf("Error parsing token expiration time %q: %v", t.ExpiresOn, err) return azcore.AccessToken{}, fmt.Errorf("%s: error parsing token expiration time %q: %v", credNameAzureCLI, t.ExpiresOn, err)
}
} }
converted := azcore.AccessToken{ converted := azcore.AccessToken{

View File

@ -0,0 +1,130 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package azidentity
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
)
const (
credNameAzurePipelines = "AzurePipelinesCredential"
oidcAPIVersion = "7.1"
systemAccessToken = "SYSTEM_ACCESSTOKEN"
systemOIDCRequestURI = "SYSTEM_OIDCREQUESTURI"
)
// azurePipelinesCredential authenticates with workload identity federation in an Azure Pipeline. See
// [Azure Pipelines documentation] for more information.
//
// [Azure Pipelines documentation]: https://learn.microsoft.com/azure/devops/pipelines/library/connect-to-azure?view=azure-devops#create-an-azure-resource-manager-service-connection-that-uses-workload-identity-federation
type azurePipelinesCredential struct {
connectionID, oidcURI, systemAccessToken string
cred *ClientAssertionCredential
}
// azurePipelinesCredentialOptions contains optional parameters for AzurePipelinesCredential.
type azurePipelinesCredentialOptions struct {
azcore.ClientOptions
// AdditionallyAllowedTenants specifies additional tenants for which the credential may acquire tokens.
// Add the wildcard value "*" to allow the credential to acquire tokens for any tenant in which the
// application is registered.
AdditionallyAllowedTenants []string
// DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or
// private clouds such as Azure Stack. It determines whether the credential requests Microsoft Entra instance metadata
// from https://login.microsoft.com before authenticating. Setting this to true will skip this request, making
// the application responsible for ensuring the configured authority is valid and trustworthy.
DisableInstanceDiscovery bool
}
// newAzurePipelinesCredential is the constructor for AzurePipelinesCredential. In addition to its required arguments,
// it reads a security token for the running build, which is required to authenticate the service connection, from the
// environment variable SYSTEM_ACCESSTOKEN. See the [Azure Pipelines documentation] for an example showing how to set
// this variable in build job YAML.
//
// [Azure Pipelines documentation]: https://learn.microsoft.com/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#systemaccesstoken
func newAzurePipelinesCredential(tenantID, clientID, serviceConnectionID string, options *azurePipelinesCredentialOptions) (*azurePipelinesCredential, error) {
if options == nil {
options = &azurePipelinesCredentialOptions{}
}
u := os.Getenv(systemOIDCRequestURI)
if u == "" {
return nil, fmt.Errorf("no value for environment variable %s. This should be set by Azure Pipelines", systemOIDCRequestURI)
}
sat := os.Getenv(systemAccessToken)
if sat == "" {
return nil, errors.New("no value for environment variable " + systemAccessToken)
}
a := azurePipelinesCredential{
connectionID: serviceConnectionID,
oidcURI: u,
systemAccessToken: sat,
}
caco := ClientAssertionCredentialOptions{
AdditionallyAllowedTenants: options.AdditionallyAllowedTenants,
ClientOptions: options.ClientOptions,
DisableInstanceDiscovery: options.DisableInstanceDiscovery,
}
cred, err := NewClientAssertionCredential(tenantID, clientID, a.getAssertion, &caco)
if err != nil {
return nil, err
}
cred.client.name = credNameAzurePipelines
a.cred = cred
return &a, nil
}
// GetToken requests an access token from Microsoft Entra ID. Azure SDK clients call this method automatically.
func (a *azurePipelinesCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
var err error
ctx, endSpan := runtime.StartSpan(ctx, credNameAzurePipelines+"."+traceOpGetToken, a.cred.client.azClient.Tracer(), nil)
defer func() { endSpan(err) }()
tk, err := a.cred.GetToken(ctx, opts)
return tk, err
}
func (a *azurePipelinesCredential) getAssertion(ctx context.Context) (string, error) {
url := a.oidcURI + "?api-version=" + oidcAPIVersion + "&serviceConnectionId=" + a.connectionID
url, err := runtime.EncodeQueryParams(url)
if err != nil {
return "", newAuthenticationFailedError(credNameAzurePipelines, "couldn't encode OIDC URL: "+err.Error(), nil, nil)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)
if err != nil {
return "", newAuthenticationFailedError(credNameAzurePipelines, "couldn't create OIDC token request: "+err.Error(), nil, nil)
}
req.Header.Set("Authorization", "Bearer "+a.systemAccessToken)
res, err := doForClient(a.cred.client.azClient, req)
if err != nil {
return "", newAuthenticationFailedError(credNameAzurePipelines, "couldn't send OIDC token request: "+err.Error(), nil, nil)
}
if res.StatusCode != http.StatusOK {
msg := res.Status + " response from the OIDC endpoint. Check service connection ID and Pipeline configuration"
// include the response because its body, if any, probably contains an error message.
// OK responses aren't included with errors because they probably contain secrets
return "", newAuthenticationFailedError(credNameAzurePipelines, msg, res, nil)
}
b, err := runtime.Payload(res)
if err != nil {
return "", newAuthenticationFailedError(credNameAzurePipelines, "couldn't read OIDC response content: "+err.Error(), nil, nil)
}
var r struct {
OIDCToken string `json:"oidcToken"`
}
err = json.Unmarshal(b, &r)
if err != nil {
return "", newAuthenticationFailedError(credNameAzurePipelines, "unexpected response from OIDC endpoint", nil, nil)
}
return r.OIDCToken, nil
}

View File

@ -86,7 +86,7 @@ func (c *ChainedTokenCredential) GetToken(ctx context.Context, opts policy.Token
errs []error errs []error
successfulCredential azcore.TokenCredential successfulCredential azcore.TokenCredential
token azcore.AccessToken token azcore.AccessToken
unavailableErr *credentialUnavailableError unavailableErr credentialUnavailable
) )
for _, cred := range c.sources { for _, cred := range c.sources {
token, err = cred.GetToken(ctx, opts) token, err = cred.GetToken(ctx, opts)

View File

@ -21,15 +21,26 @@ pr:
include: include:
- sdk/azidentity/ - sdk/azidentity/
stages: extends:
- template: /eng/pipelines/templates/jobs/archetype-sdk-client.yml template: /eng/pipelines/templates/jobs/archetype-sdk-client.yml
parameters: parameters:
RunLiveTests: true
UsePipelineProxy: false
ServiceDirectory: 'azidentity'
CloudConfig: CloudConfig:
Public: Public:
SubscriptionConfigurations: SubscriptionConfigurations:
- $(sub-config-azure-cloud-test-resources) - $(sub-config-azure-cloud-test-resources)
# Contains alternate tenant, AAD app and cert info for testing
- $(sub-config-identity-test-resources) - $(sub-config-identity-test-resources)
EnvVars:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
RunLiveTests: true
ServiceDirectory: azidentity
UsePipelineProxy: false
${{ if endsWith(variables['Build.DefinitionName'], 'weekly') }}:
MatrixConfigs:
- Name: managed_identity_matrix
GenerateVMJobs: true
Path: sdk/azidentity/managed-identity-matrix.json
Selection: sparse
MatrixReplace:
- Pool=.*LINUXPOOL.*/azsdk-pool-mms-ubuntu-2204-identitymsi
- OSVmImage=.*LINUXNEXTVMIMAGE.*/azsdk-pool-mms-ubuntu-2204-1espt

View File

@ -23,7 +23,7 @@ const credNameAssertion = "ClientAssertionCredential"
// the most common assertion scenario, authenticating a service principal with a certificate. See // the most common assertion scenario, authenticating a service principal with a certificate. See
// [Microsoft Entra ID documentation] for details of the assertion format. // [Microsoft Entra ID documentation] for details of the assertion format.
// //
// [Microsoft Entra ID documentation]: https://learn.microsoft.com/azure/active-directory/develop/active-directory-certificate-credentials#assertion-format // [Microsoft Entra ID documentation]: https://learn.microsoft.com/entra/identity-platform/certificate-credentials#assertion-format
type ClientAssertionCredential struct { type ClientAssertionCredential struct {
client *confidentialClient client *confidentialClient
} }

View File

@ -51,7 +51,8 @@ type ClientCertificateCredential struct {
client *confidentialClient client *confidentialClient
} }
// NewClientCertificateCredential constructs a ClientCertificateCredential. Pass nil for options to accept defaults. // NewClientCertificateCredential constructs a ClientCertificateCredential. Pass nil for options to accept defaults. See
// [ParseCertificates] for help loading a certificate.
func NewClientCertificateCredential(tenantID string, clientID string, certs []*x509.Certificate, key crypto.PrivateKey, options *ClientCertificateCredentialOptions) (*ClientCertificateCredential, error) { func NewClientCertificateCredential(tenantID string, clientID string, certs []*x509.Certificate, key crypto.PrivateKey, options *ClientCertificateCredentialOptions) (*ClientCertificateCredential, error) {
if len(certs) == 0 { if len(certs) == 0 {
return nil, errors.New("at least one certificate is required") return nil, errors.New("at least one certificate is required")
@ -86,8 +87,10 @@ func (c *ClientCertificateCredential) GetToken(ctx context.Context, opts policy.
return tk, err return tk, err
} }
// ParseCertificates loads certificates and a private key, in PEM or PKCS12 format, for use with NewClientCertificateCredential. // ParseCertificates loads certificates and a private key, in PEM or PKCS#12 format, for use with [NewClientCertificateCredential].
// Pass nil for password if the private key isn't encrypted. This function can't decrypt keys in PEM format. // Pass nil for password if the private key isn't encrypted. This function has limitations, for example it can't decrypt keys in
// PEM format or PKCS#12 certificates that use SHA256 for message authentication. If you encounter such limitations, consider
// using another module to load the certificate and private key.
func ParseCertificates(certData []byte, password []byte) ([]*x509.Certificate, crypto.PrivateKey, error) { func ParseCertificates(certData []byte, password []byte) ([]*x509.Certificate, crypto.PrivateKey, error) {
var blocks []*pem.Block var blocks []*pem.Block
var err error var err error

View File

@ -91,7 +91,7 @@ func (c *confidentialClient) GetToken(ctx context.Context, tro policy.TokenReque
} }
tro.TenantID = tenant tro.TenantID = tenant
} }
client, mu, err := c.client(ctx, tro) client, mu, err := c.client(tro)
if err != nil { if err != nil {
return azcore.AccessToken{}, err return azcore.AccessToken{}, err
} }
@ -109,7 +109,7 @@ func (c *confidentialClient) GetToken(ctx context.Context, tro policy.TokenReque
if err != nil { if err != nil {
// We could get a credentialUnavailableError from managed identity authentication because in that case the error comes from our code. // We could get a credentialUnavailableError from managed identity authentication because in that case the error comes from our code.
// We return it directly because it affects the behavior of credential chains. Otherwise, we return AuthenticationFailedError. // We return it directly because it affects the behavior of credential chains. Otherwise, we return AuthenticationFailedError.
var unavailableErr *credentialUnavailableError var unavailableErr credentialUnavailable
if !errors.As(err, &unavailableErr) { if !errors.As(err, &unavailableErr) {
res := getResponseFromError(err) res := getResponseFromError(err)
err = newAuthenticationFailedError(c.name, err.Error(), res, err) err = newAuthenticationFailedError(c.name, err.Error(), res, err)
@ -121,7 +121,7 @@ func (c *confidentialClient) GetToken(ctx context.Context, tro policy.TokenReque
return azcore.AccessToken{Token: ar.AccessToken, ExpiresOn: ar.ExpiresOn.UTC()}, err return azcore.AccessToken{Token: ar.AccessToken, ExpiresOn: ar.ExpiresOn.UTC()}, err
} }
func (c *confidentialClient) client(ctx context.Context, tro policy.TokenRequestOptions) (msalConfidentialClient, *sync.Mutex, error) { func (c *confidentialClient) client(tro policy.TokenRequestOptions) (msalConfidentialClient, *sync.Mutex, error) {
c.clientMu.Lock() c.clientMu.Lock()
defer c.clientMu.Unlock() defer c.clientMu.Unlock()
if tro.EnableCAE { if tro.EnableCAE {

View File

@ -8,10 +8,8 @@ package azidentity
import ( import (
"context" "context"
"errors"
"os" "os"
"strings" "strings"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
@ -98,13 +96,13 @@ func NewDefaultAzureCredential(options *DefaultAzureCredentialOptions) (*Default
creds = append(creds, &defaultCredentialErrorReporter{credType: credNameWorkloadIdentity, err: err}) creds = append(creds, &defaultCredentialErrorReporter{credType: credNameWorkloadIdentity, err: err})
} }
o := &ManagedIdentityCredentialOptions{ClientOptions: options.ClientOptions} o := &ManagedIdentityCredentialOptions{ClientOptions: options.ClientOptions, dac: true}
if ID, ok := os.LookupEnv(azureClientID); ok { if ID, ok := os.LookupEnv(azureClientID); ok {
o.ID = ClientID(ID) o.ID = ClientID(ID)
} }
miCred, err := NewManagedIdentityCredential(o) miCred, err := NewManagedIdentityCredential(o)
if err == nil { if err == nil {
creds = append(creds, &timeoutWrapper{mic: miCred, timeout: time.Second}) creds = append(creds, miCred)
} else { } else {
errorMessages = append(errorMessages, credNameManagedIdentity+": "+err.Error()) errorMessages = append(errorMessages, credNameManagedIdentity+": "+err.Error())
creds = append(creds, &defaultCredentialErrorReporter{credType: credNameManagedIdentity, err: err}) creds = append(creds, &defaultCredentialErrorReporter{credType: credNameManagedIdentity, err: err})
@ -158,51 +156,10 @@ type defaultCredentialErrorReporter struct {
} }
func (d *defaultCredentialErrorReporter) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) { func (d *defaultCredentialErrorReporter) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
if _, ok := d.err.(*credentialUnavailableError); ok { if _, ok := d.err.(credentialUnavailable); ok {
return azcore.AccessToken{}, d.err return azcore.AccessToken{}, d.err
} }
return azcore.AccessToken{}, newCredentialUnavailableError(d.credType, d.err.Error()) return azcore.AccessToken{}, newCredentialUnavailableError(d.credType, d.err.Error())
} }
var _ azcore.TokenCredential = (*defaultCredentialErrorReporter)(nil) var _ azcore.TokenCredential = (*defaultCredentialErrorReporter)(nil)
// timeoutWrapper prevents a potentially very long timeout when managed identity isn't available
type timeoutWrapper struct {
mic *ManagedIdentityCredential
// timeout applies to all auth attempts until one doesn't time out
timeout time.Duration
}
// GetToken wraps DefaultAzureCredential's initial managed identity auth attempt with a short timeout
// because managed identity may not be available and connecting to IMDS can take several minutes to time out.
func (w *timeoutWrapper) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
var tk azcore.AccessToken
var err error
// no need to synchronize around this value because it's written only within ChainedTokenCredential's critical section
if w.timeout > 0 {
c, cancel := context.WithTimeout(ctx, w.timeout)
defer cancel()
tk, err = w.mic.GetToken(c, opts)
if isAuthFailedDueToContext(err) {
err = newCredentialUnavailableError(credNameManagedIdentity, "managed identity timed out. See https://aka.ms/azsdk/go/identity/troubleshoot#dac for more information")
} else {
// some managed identity implementation is available, so don't apply the timeout to future calls
w.timeout = 0
}
} else {
tk, err = w.mic.GetToken(ctx, opts)
}
return tk, err
}
// unwraps nested AuthenticationFailedErrors to get the root error
func isAuthFailedDueToContext(err error) bool {
for {
var authFailedErr *AuthenticationFailedError
if !errors.As(err, &authFailedErr) {
break
}
err = authFailedErr.err
}
return errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded)
}

View File

@ -19,7 +19,7 @@ const cliTimeout = 10 * time.Second
// the next credential in its chain (another developer credential). // the next credential in its chain (another developer credential).
func unavailableIfInChain(err error, inDefaultChain bool) error { func unavailableIfInChain(err error, inDefaultChain bool) error {
if err != nil && inDefaultChain { if err != nil && inDefaultChain {
var unavailableErr *credentialUnavailableError var unavailableErr credentialUnavailable
if !errors.As(err, &unavailableErr) { if !errors.As(err, &unavailableErr) {
err = newCredentialUnavailableError(credNameAzureDeveloperCLI, err.Error()) err = newCredentialUnavailableError(credNameAzureDeveloperCLI, err.Error())
} }

View File

@ -34,8 +34,8 @@ type DeviceCodeCredentialOptions struct {
ClientID string ClientID string
// disableAutomaticAuthentication prevents the credential from automatically prompting the user to authenticate. // disableAutomaticAuthentication prevents the credential from automatically prompting the user to authenticate.
// When this option is true, [DeviceCodeCredential.GetToken] will return [ErrAuthenticationRequired] when user // When this option is true, GetToken will return authenticationRequiredError when user interaction is necessary
// interaction is necessary to acquire a token. // to acquire a token.
disableAutomaticAuthentication bool disableAutomaticAuthentication bool
// DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or // DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or

View File

@ -57,6 +57,9 @@ type EnvironmentCredentialOptions struct {
// //
// AZURE_CLIENT_CERTIFICATE_PASSWORD: (optional) password for the certificate file. // AZURE_CLIENT_CERTIFICATE_PASSWORD: (optional) password for the certificate file.
// //
// Note that this credential uses [ParseCertificates] to load the certificate and key from the file. If this
// function isn't able to parse your certificate, use [ClientCertificateCredential] instead.
//
// # User with username and password // # User with username and password
// //
// AZURE_TENANT_ID: (optional) tenant to authenticate in. Defaults to "organizations". // AZURE_TENANT_ID: (optional) tenant to authenticate in. Defaults to "organizations".
@ -121,7 +124,7 @@ func NewEnvironmentCredential(options *EnvironmentCredentialOptions) (*Environme
} }
certs, key, err := ParseCertificates(certData, password) certs, key, err := ParseCertificates(certData, password)
if err != nil { if err != nil {
return nil, fmt.Errorf(`failed to load certificate from "%s": %v`, certPath, err) return nil, fmt.Errorf("failed to parse %q due to error %q. This may be due to a limitation of this module's certificate loader. Consider calling NewClientCertificateCredential instead", certPath, err.Error())
} }
o := &ClientCertificateCredentialOptions{ o := &ClientCertificateCredentialOptions{
AdditionallyAllowedTenants: additionalTenants, AdditionallyAllowedTenants: additionalTenants,

View File

@ -13,15 +13,12 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
"github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo" "github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo"
msal "github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors" msal "github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors"
) )
// errAuthenticationRequired indicates a credential's Authenticate method must be called to acquire a token
// because user interaction is required and the credential is configured not to automatically prompt the user.
var errAuthenticationRequired error = &credentialUnavailableError{"can't acquire a token without user interaction. Call Authenticate to interactively authenticate a user"}
// getResponseFromError retrieves the response carried by // getResponseFromError retrieves the response carried by
// an AuthenticationFailedError or MSAL CallErr, if any // an AuthenticationFailedError or MSAL CallErr, if any
func getResponseFromError(err error) *http.Response { func getResponseFromError(err error) *http.Response {
@ -56,7 +53,7 @@ func (e *AuthenticationFailedError) Error() string {
return e.credType + ": " + e.message return e.credType + ": " + e.message
} }
msg := &bytes.Buffer{} msg := &bytes.Buffer{}
fmt.Fprintf(msg, e.credType+" authentication failed\n") fmt.Fprintf(msg, "%s authentication failed. %s\n", e.credType, e.message)
if e.RawResponse.Request != nil { if e.RawResponse.Request != nil {
fmt.Fprintf(msg, "%s %s://%s%s\n", e.RawResponse.Request.Method, e.RawResponse.Request.URL.Scheme, e.RawResponse.Request.URL.Host, e.RawResponse.Request.URL.Path) fmt.Fprintf(msg, "%s %s://%s%s\n", e.RawResponse.Request.Method, e.RawResponse.Request.URL.Scheme, e.RawResponse.Request.URL.Host, e.RawResponse.Request.URL.Path)
} else { } else {
@ -110,8 +107,34 @@ func (*AuthenticationFailedError) NonRetriable() {
var _ errorinfo.NonRetriable = (*AuthenticationFailedError)(nil) var _ errorinfo.NonRetriable = (*AuthenticationFailedError)(nil)
// credentialUnavailableError indicates a credential can't attempt authentication because it lacks required // authenticationRequiredError indicates a credential's Authenticate method must be called to acquire a token
// data or state // because the credential requires user interaction and is configured not to request it automatically.
type authenticationRequiredError struct {
credentialUnavailableError
// TokenRequestOptions for the required token. Pass this to the credential's Authenticate method.
TokenRequestOptions policy.TokenRequestOptions
}
func newauthenticationRequiredError(credType string, tro policy.TokenRequestOptions) error {
return &authenticationRequiredError{
credentialUnavailableError: credentialUnavailableError{
credType + " can't acquire a token without user interaction. Call Authenticate to authenticate a user interactively",
},
TokenRequestOptions: tro,
}
}
var (
_ credentialUnavailable = (*authenticationRequiredError)(nil)
_ errorinfo.NonRetriable = (*authenticationRequiredError)(nil)
)
type credentialUnavailable interface {
error
credentialUnavailable()
}
type credentialUnavailableError struct { type credentialUnavailableError struct {
message string message string
} }
@ -135,6 +158,11 @@ func (e *credentialUnavailableError) Error() string {
} }
// NonRetriable is a marker method indicating this error should not be retried. It has no implementation. // NonRetriable is a marker method indicating this error should not be retried. It has no implementation.
func (e *credentialUnavailableError) NonRetriable() {} func (*credentialUnavailableError) NonRetriable() {}
var _ errorinfo.NonRetriable = (*credentialUnavailableError)(nil) func (*credentialUnavailableError) credentialUnavailable() {}
var (
_ credentialUnavailable = (*credentialUnavailableError)(nil)
_ errorinfo.NonRetriable = (*credentialUnavailableError)(nil)
)

View File

@ -3,26 +3,40 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0-beta.1/go.mod h1:3Ug6Qzto9an
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 h1:fb8kj/Dh4CSwgsOzHeZY4Xh68cFVbzXx+ONXGMY//4w= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 h1:fb8kj/Dh4CSwgsOzHeZY4Xh68cFVbzXx+ONXGMY//4w=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0/go.mod h1:uReU2sSxZExRPBAg3qKzmAucSi51+SP1OhohieR821Q= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0/go.mod h1:uReU2sSxZExRPBAg3qKzmAucSi51+SP1OhohieR821Q=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA= github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/keybase/dbus v0.0.0-20220506165403-5aa21ea2c23a/go.mod h1:YPNKjjE7Ubp9dTbnWvsP3HT+hYnY6TfXzubYTBeUxc8=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -33,7 +47,14 @@ golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -33,8 +33,8 @@ type InteractiveBrowserCredentialOptions struct {
ClientID string ClientID string
// disableAutomaticAuthentication prevents the credential from automatically prompting the user to authenticate. // disableAutomaticAuthentication prevents the credential from automatically prompting the user to authenticate.
// When this option is true, [InteractiveBrowserCredential.GetToken] will return [ErrAuthenticationRequired] when // When this option is true, GetToken will return authenticationRequiredError when user interaction is necessary
// user interaction is necessary to acquire a token. // to acquire a token.
disableAutomaticAuthentication bool disableAutomaticAuthentication bool
// DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or // DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or

View File

@ -0,0 +1,17 @@
{
"include": [
{
"Agent": {
"msi_image": {
"ArmTemplateParameters": "@{deployResources = $true}",
"OSVmImage": "env:LINUXNEXTVMIMAGE",
"Pool": "env:LINUXPOOL"
}
},
"GoVersion": [
"1.22.1"
],
"IDENTITY_IMDS_AVAILABLE": "1"
}
]
}

View File

@ -14,13 +14,15 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"path/filepath"
"runtime"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" azruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming" "github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming"
"github.com/Azure/azure-sdk-for-go/sdk/internal/log" "github.com/Azure/azure-sdk-for-go/sdk/internal/log"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"
@ -34,16 +36,18 @@ const (
identityServerThumbprint = "IDENTITY_SERVER_THUMBPRINT" identityServerThumbprint = "IDENTITY_SERVER_THUMBPRINT"
headerMetadata = "Metadata" headerMetadata = "Metadata"
imdsEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token" imdsEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token"
miResID = "mi_res_id"
msiEndpoint = "MSI_ENDPOINT" msiEndpoint = "MSI_ENDPOINT"
msiResID = "msi_res_id"
msiSecret = "MSI_SECRET" msiSecret = "MSI_SECRET"
imdsAPIVersion = "2018-02-01" imdsAPIVersion = "2018-02-01"
azureArcAPIVersion = "2019-08-15" azureArcAPIVersion = "2019-08-15"
serviceFabricAPIVersion = "2019-07-01-preview"
qpClientID = "client_id" qpClientID = "client_id"
qpResID = "mi_res_id" serviceFabricAPIVersion = "2019-07-01-preview"
) )
var imdsProbeTimeout = time.Second
type msiType int type msiType int
const ( const (
@ -55,13 +59,28 @@ const (
msiTypeServiceFabric msiTypeServiceFabric
) )
// managedIdentityClient provides the base for authenticating in managed identity environments
// This type includes an runtime.Pipeline and TokenCredentialOptions.
type managedIdentityClient struct { type managedIdentityClient struct {
azClient *azcore.Client azClient *azcore.Client
msiType msiType
endpoint string endpoint string
id ManagedIDKind id ManagedIDKind
msiType msiType
probeIMDS bool
}
// arcKeyDirectory returns the directory expected to contain Azure Arc keys
var arcKeyDirectory = func() (string, error) {
switch runtime.GOOS {
case "linux":
return "/var/opt/azcmagent/tokens", nil
case "windows":
pd := os.Getenv("ProgramData")
if pd == "" {
return "", errors.New("environment variable ProgramData has no value")
}
return filepath.Join(pd, "AzureConnectedMachineAgent", "Tokens"), nil
default:
return "", fmt.Errorf("unsupported OS %q", runtime.GOOS)
}
} }
type wrappedNumber json.Number type wrappedNumber json.Number
@ -88,7 +107,7 @@ func setIMDSRetryOptionDefaults(o *policy.RetryOptions) {
if o.StatusCodes == nil { if o.StatusCodes == nil {
o.StatusCodes = []int{ o.StatusCodes = []int{
// IMDS docs recommend retrying 404, 410, 429 and 5xx // IMDS docs recommend retrying 404, 410, 429 and 5xx
// https://learn.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#error-handling // https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#error-handling
http.StatusNotFound, // 404 http.StatusNotFound, // 404
http.StatusGone, // 410 http.StatusGone, // 410
http.StatusTooManyRequests, // 429 http.StatusTooManyRequests, // 429
@ -147,11 +166,12 @@ func newManagedIdentityClient(options *ManagedIdentityCredentialOptions) (*manag
c.msiType = msiTypeCloudShell c.msiType = msiTypeCloudShell
} }
} else { } else {
c.probeIMDS = options.dac
setIMDSRetryOptionDefaults(&cp.Retry) setIMDSRetryOptionDefaults(&cp.Retry)
} }
client, err := azcore.NewClient(module, version, runtime.PipelineOptions{ client, err := azcore.NewClient(module, version, azruntime.PipelineOptions{
Tracing: runtime.TracingOptions{ Tracing: azruntime.TracingOptions{
Namespace: traceNamespace, Namespace: traceNamespace,
}, },
}, &cp) }, &cp)
@ -180,6 +200,27 @@ func (c *managedIdentityClient) provideToken(ctx context.Context, params confide
// authenticate acquires an access token // authenticate acquires an access token
func (c *managedIdentityClient) authenticate(ctx context.Context, id ManagedIDKind, scopes []string) (azcore.AccessToken, error) { func (c *managedIdentityClient) authenticate(ctx context.Context, id ManagedIDKind, scopes []string) (azcore.AccessToken, error) {
// no need to synchronize around this value because it's true only when DefaultAzureCredential constructed the client,
// and in that case ChainedTokenCredential.GetToken synchronizes goroutines that would execute this block
if c.probeIMDS {
cx, cancel := context.WithTimeout(ctx, imdsProbeTimeout)
defer cancel()
cx = policy.WithRetryOptions(cx, policy.RetryOptions{MaxRetries: -1})
req, err := azruntime.NewRequest(cx, http.MethodGet, c.endpoint)
if err == nil {
_, err = c.azClient.Pipeline().Do(req)
}
if err != nil {
msg := err.Error()
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
msg = "managed identity timed out. See https://aka.ms/azsdk/go/identity/troubleshoot#dac for more information"
}
return azcore.AccessToken{}, newCredentialUnavailableError(credNameManagedIdentity, msg)
}
// send normal token requests from now on because something responded
c.probeIMDS = false
}
msg, err := c.createAuthRequest(ctx, id, scopes) msg, err := c.createAuthRequest(ctx, id, scopes)
if err != nil { if err != nil {
return azcore.AccessToken{}, err return azcore.AccessToken{}, err
@ -190,7 +231,7 @@ func (c *managedIdentityClient) authenticate(ctx context.Context, id ManagedIDKi
return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, err.Error(), nil, err) return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, err.Error(), nil, err)
} }
if runtime.HasStatusCode(resp, http.StatusOK, http.StatusCreated) { if azruntime.HasStatusCode(resp, http.StatusOK, http.StatusCreated) {
return c.createAccessToken(resp) return c.createAccessToken(resp)
} }
@ -201,15 +242,15 @@ func (c *managedIdentityClient) authenticate(ctx context.Context, id ManagedIDKi
return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, "the requested identity isn't assigned to this resource", resp, nil) return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, "the requested identity isn't assigned to this resource", resp, nil)
} }
msg := "failed to authenticate a system assigned identity" msg := "failed to authenticate a system assigned identity"
if body, err := runtime.Payload(resp); err == nil && len(body) > 0 { if body, err := azruntime.Payload(resp); err == nil && len(body) > 0 {
msg += fmt.Sprintf(". The endpoint responded with %s", body) msg += fmt.Sprintf(". The endpoint responded with %s", body)
} }
return azcore.AccessToken{}, newCredentialUnavailableError(credNameManagedIdentity, msg) return azcore.AccessToken{}, newCredentialUnavailableError(credNameManagedIdentity, msg)
case http.StatusForbidden: case http.StatusForbidden:
// Docker Desktop runs a proxy that responds 403 to IMDS token requests. If we get that response, // Docker Desktop runs a proxy that responds 403 to IMDS token requests. If we get that response,
// we return credentialUnavailableError so credential chains continue to their next credential // we return credentialUnavailableError so credential chains continue to their next credential
body, err := runtime.Payload(resp) body, err := azruntime.Payload(resp)
if err == nil && strings.Contains(string(body), "A socket operation was attempted to an unreachable network") { if err == nil && strings.Contains(string(body), "unreachable") {
return azcore.AccessToken{}, newCredentialUnavailableError(credNameManagedIdentity, fmt.Sprintf("unexpected response %q", string(body))) return azcore.AccessToken{}, newCredentialUnavailableError(credNameManagedIdentity, fmt.Sprintf("unexpected response %q", string(body)))
} }
} }
@ -226,7 +267,7 @@ func (c *managedIdentityClient) createAccessToken(res *http.Response) (azcore.Ac
ExpiresIn wrappedNumber `json:"expires_in,omitempty"` // this field should always return the number of seconds for which a token is valid ExpiresIn wrappedNumber `json:"expires_in,omitempty"` // this field should always return the number of seconds for which a token is valid
ExpiresOn interface{} `json:"expires_on,omitempty"` // the value returned in this field varies between a number and a date string ExpiresOn interface{} `json:"expires_on,omitempty"` // the value returned in this field varies between a number and a date string
}{} }{}
if err := runtime.UnmarshalAsJSON(res, &value); err != nil { if err := azruntime.UnmarshalAsJSON(res, &value); err != nil {
return azcore.AccessToken{}, fmt.Errorf("internal AccessToken: %v", err) return azcore.AccessToken{}, fmt.Errorf("internal AccessToken: %v", err)
} }
if value.ExpiresIn != "" { if value.ExpiresIn != "" {
@ -276,7 +317,7 @@ func (c *managedIdentityClient) createAuthRequest(ctx context.Context, id Manage
} }
func (c *managedIdentityClient) createIMDSAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) { func (c *managedIdentityClient) createIMDSAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) {
request, err := runtime.NewRequest(ctx, http.MethodGet, c.endpoint) request, err := azruntime.NewRequest(ctx, http.MethodGet, c.endpoint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -286,7 +327,7 @@ func (c *managedIdentityClient) createIMDSAuthRequest(ctx context.Context, id Ma
q.Add("resource", strings.Join(scopes, " ")) q.Add("resource", strings.Join(scopes, " "))
if id != nil { if id != nil {
if id.idKind() == miResourceID { if id.idKind() == miResourceID {
q.Add(qpResID, id.String()) q.Add(msiResID, id.String())
} else { } else {
q.Add(qpClientID, id.String()) q.Add(qpClientID, id.String())
} }
@ -296,7 +337,7 @@ func (c *managedIdentityClient) createIMDSAuthRequest(ctx context.Context, id Ma
} }
func (c *managedIdentityClient) createAppServiceAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) { func (c *managedIdentityClient) createAppServiceAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) {
request, err := runtime.NewRequest(ctx, http.MethodGet, c.endpoint) request, err := azruntime.NewRequest(ctx, http.MethodGet, c.endpoint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -306,7 +347,7 @@ func (c *managedIdentityClient) createAppServiceAuthRequest(ctx context.Context,
q.Add("resource", scopes[0]) q.Add("resource", scopes[0])
if id != nil { if id != nil {
if id.idKind() == miResourceID { if id.idKind() == miResourceID {
q.Add(qpResID, id.String()) q.Add(miResID, id.String())
} else { } else {
q.Add(qpClientID, id.String()) q.Add(qpClientID, id.String())
} }
@ -316,7 +357,7 @@ func (c *managedIdentityClient) createAppServiceAuthRequest(ctx context.Context,
} }
func (c *managedIdentityClient) createAzureMLAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) { func (c *managedIdentityClient) createAzureMLAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) {
request, err := runtime.NewRequest(ctx, http.MethodGet, c.endpoint) request, err := azruntime.NewRequest(ctx, http.MethodGet, c.endpoint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -329,7 +370,7 @@ func (c *managedIdentityClient) createAzureMLAuthRequest(ctx context.Context, id
if id.idKind() == miResourceID { if id.idKind() == miResourceID {
log.Write(EventAuthentication, "WARNING: Azure ML doesn't support specifying a managed identity by resource ID") log.Write(EventAuthentication, "WARNING: Azure ML doesn't support specifying a managed identity by resource ID")
q.Set("clientid", "") q.Set("clientid", "")
q.Set(qpResID, id.String()) q.Set(miResID, id.String())
} else { } else {
q.Set("clientid", id.String()) q.Set("clientid", id.String())
} }
@ -339,7 +380,7 @@ func (c *managedIdentityClient) createAzureMLAuthRequest(ctx context.Context, id
} }
func (c *managedIdentityClient) createServiceFabricAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) { func (c *managedIdentityClient) createServiceFabricAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) {
request, err := runtime.NewRequest(ctx, http.MethodGet, c.endpoint) request, err := azruntime.NewRequest(ctx, http.MethodGet, c.endpoint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -351,7 +392,7 @@ func (c *managedIdentityClient) createServiceFabricAuthRequest(ctx context.Conte
if id != nil { if id != nil {
log.Write(EventAuthentication, "WARNING: Service Fabric doesn't support selecting a user-assigned identity at runtime") log.Write(EventAuthentication, "WARNING: Service Fabric doesn't support selecting a user-assigned identity at runtime")
if id.idKind() == miResourceID { if id.idKind() == miResourceID {
q.Add(qpResID, id.String()) q.Add(miResID, id.String())
} else { } else {
q.Add(qpClientID, id.String()) q.Add(qpClientID, id.String())
} }
@ -362,7 +403,7 @@ func (c *managedIdentityClient) createServiceFabricAuthRequest(ctx context.Conte
func (c *managedIdentityClient) getAzureArcSecretKey(ctx context.Context, resources []string) (string, error) { func (c *managedIdentityClient) getAzureArcSecretKey(ctx context.Context, resources []string) (string, error) {
// create the request to retreive the secret key challenge provided by the HIMDS service // create the request to retreive the secret key challenge provided by the HIMDS service
request, err := runtime.NewRequest(ctx, http.MethodGet, c.endpoint) request, err := azruntime.NewRequest(ctx, http.MethodGet, c.endpoint)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -384,22 +425,36 @@ func (c *managedIdentityClient) getAzureArcSecretKey(ctx context.Context, resour
} }
header := response.Header.Get("WWW-Authenticate") header := response.Header.Get("WWW-Authenticate")
if len(header) == 0 { if len(header) == 0 {
return "", errors.New("did not receive a value from WWW-Authenticate header") return "", newAuthenticationFailedError(credNameManagedIdentity, "HIMDS response has no WWW-Authenticate header", nil, nil)
} }
// the WWW-Authenticate header is expected in the following format: Basic realm=/some/file/path.key // the WWW-Authenticate header is expected in the following format: Basic realm=/some/file/path.key
pos := strings.LastIndex(header, "=") _, p, found := strings.Cut(header, "=")
if pos == -1 { if !found {
return "", fmt.Errorf("did not receive a correct value from WWW-Authenticate header: %s", header) return "", newAuthenticationFailedError(credNameManagedIdentity, "unexpected WWW-Authenticate header from HIMDS: "+header, nil, nil)
} }
key, err := os.ReadFile(header[pos+1:]) expected, err := arcKeyDirectory()
if err != nil { if err != nil {
return "", fmt.Errorf("could not read file (%s) contents: %v", header[pos+1:], err) return "", err
}
if filepath.Dir(p) != expected || !strings.HasSuffix(p, ".key") {
return "", newAuthenticationFailedError(credNameManagedIdentity, "unexpected file path from HIMDS service: "+p, nil, nil)
}
f, err := os.Stat(p)
if err != nil {
return "", newAuthenticationFailedError(credNameManagedIdentity, fmt.Sprintf("could not stat %q: %v", p, err), nil, nil)
}
if s := f.Size(); s > 4096 {
return "", newAuthenticationFailedError(credNameManagedIdentity, fmt.Sprintf("key is too large (%d bytes)", s), nil, nil)
}
key, err := os.ReadFile(p)
if err != nil {
return "", newAuthenticationFailedError(credNameManagedIdentity, fmt.Sprintf("could not read %q: %v", p, err), nil, nil)
} }
return string(key), nil return string(key), nil
} }
func (c *managedIdentityClient) createAzureArcAuthRequest(ctx context.Context, id ManagedIDKind, resources []string, key string) (*policy.Request, error) { func (c *managedIdentityClient) createAzureArcAuthRequest(ctx context.Context, id ManagedIDKind, resources []string, key string) (*policy.Request, error) {
request, err := runtime.NewRequest(ctx, http.MethodGet, c.endpoint) request, err := azruntime.NewRequest(ctx, http.MethodGet, c.endpoint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -411,7 +466,7 @@ func (c *managedIdentityClient) createAzureArcAuthRequest(ctx context.Context, i
if id != nil { if id != nil {
log.Write(EventAuthentication, "WARNING: Azure Arc doesn't support user-assigned managed identities") log.Write(EventAuthentication, "WARNING: Azure Arc doesn't support user-assigned managed identities")
if id.idKind() == miResourceID { if id.idKind() == miResourceID {
q.Add(qpResID, id.String()) q.Add(miResID, id.String())
} else { } else {
q.Add(qpClientID, id.String()) q.Add(qpClientID, id.String())
} }
@ -421,7 +476,7 @@ func (c *managedIdentityClient) createAzureArcAuthRequest(ctx context.Context, i
} }
func (c *managedIdentityClient) createCloudShellAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) { func (c *managedIdentityClient) createCloudShellAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) {
request, err := runtime.NewRequest(ctx, http.MethodPost, c.endpoint) request, err := azruntime.NewRequest(ctx, http.MethodPost, c.endpoint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -437,7 +492,7 @@ func (c *managedIdentityClient) createCloudShellAuthRequest(ctx context.Context,
log.Write(EventAuthentication, "WARNING: Cloud Shell doesn't support user-assigned managed identities") log.Write(EventAuthentication, "WARNING: Cloud Shell doesn't support user-assigned managed identities")
q := request.Raw().URL.Query() q := request.Raw().URL.Query()
if id.idKind() == miResourceID { if id.idKind() == miResourceID {
q.Add(qpResID, id.String()) q.Add(miResID, id.String())
} else { } else {
q.Add(qpClientID, id.String()) q.Add(qpClientID, id.String())
} }

View File

@ -64,12 +64,19 @@ type ManagedIdentityCredentialOptions struct {
// instead of the hosting environment's default. The value may be the identity's client ID or resource ID, but note that // instead of the hosting environment's default. The value may be the identity's client ID or resource ID, but note that
// some platforms don't accept resource IDs. // some platforms don't accept resource IDs.
ID ManagedIDKind ID ManagedIDKind
// dac indicates whether the credential is part of DefaultAzureCredential. When true, and the environment doesn't have
// configuration for a specific managed identity API, the credential tries to determine whether IMDS is available before
// sending its first token request. It does this by sending a malformed request with a short timeout. Any response to that
// request is taken to mean IMDS is available, in which case the credential will send ordinary token requests thereafter
// with no special timeout. The purpose of this behavior is to prevent a very long timeout when IMDS isn't available.
dac bool
} }
// ManagedIdentityCredential authenticates an Azure managed identity in any hosting environment supporting managed identities. // ManagedIdentityCredential authenticates an Azure managed identity in any hosting environment supporting managed identities.
// This credential authenticates a system-assigned identity by default. Use ManagedIdentityCredentialOptions.ID to specify a // This credential authenticates a system-assigned identity by default. Use ManagedIdentityCredentialOptions.ID to specify a
// user-assigned identity. See Microsoft Entra ID documentation for more information about managed identities: // user-assigned identity. See Microsoft Entra ID documentation for more information about managed identities:
// https://learn.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview // https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview
type ManagedIdentityCredential struct { type ManagedIdentityCredential struct {
client *confidentialClient client *confidentialClient
mic *managedIdentityClient mic *managedIdentityClient

View File

@ -10,6 +10,7 @@ import (
"context" "context"
"crypto" "crypto"
"crypto/x509" "crypto/x509"
"errors"
"github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
@ -24,7 +25,7 @@ const credNameOBO = "OnBehalfOfCredential"
// is not an interactive authentication flow, an application using it must have admin consent for any delegated // is not an interactive authentication flow, an application using it must have admin consent for any delegated
// permissions before requesting tokens for them. See [Microsoft Entra ID documentation] for more details. // permissions before requesting tokens for them. See [Microsoft Entra ID documentation] for more details.
// //
// [Microsoft Entra ID documentation]: https://learn.microsoft.com/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow // [Microsoft Entra ID documentation]: https://learn.microsoft.com/entra/identity-platform/v2-oauth2-on-behalf-of-flow
type OnBehalfOfCredential struct { type OnBehalfOfCredential struct {
client *confidentialClient client *confidentialClient
} }
@ -60,6 +61,19 @@ func NewOnBehalfOfCredentialWithCertificate(tenantID, clientID, userAssertion st
return newOnBehalfOfCredential(tenantID, clientID, userAssertion, cred, options) return newOnBehalfOfCredential(tenantID, clientID, userAssertion, cred, options)
} }
// NewOnBehalfOfCredentialWithClientAssertions constructs an OnBehalfOfCredential that authenticates with client assertions.
// userAssertion is the user's access token for the application. The getAssertion function should return client assertions
// that authenticate the application to Microsoft Entra ID, such as federated credentials.
func NewOnBehalfOfCredentialWithClientAssertions(tenantID, clientID, userAssertion string, getAssertion func(context.Context) (string, error), options *OnBehalfOfCredentialOptions) (*OnBehalfOfCredential, error) {
if getAssertion == nil {
return nil, errors.New("getAssertion can't be nil. It must be a function that returns client assertions")
}
cred := confidential.NewCredFromAssertionCallback(func(ctx context.Context, _ confidential.AssertionRequestOptions) (string, error) {
return getAssertion(ctx)
})
return newOnBehalfOfCredential(tenantID, clientID, userAssertion, cred, options)
}
// NewOnBehalfOfCredentialWithSecret constructs an OnBehalfOfCredential that authenticates with a client secret. // NewOnBehalfOfCredentialWithSecret constructs an OnBehalfOfCredential that authenticates with a client secret.
func NewOnBehalfOfCredentialWithSecret(tenantID, clientID, userAssertion, clientSecret string, options *OnBehalfOfCredentialOptions) (*OnBehalfOfCredential, error) { func NewOnBehalfOfCredentialWithSecret(tenantID, clientID, userAssertion, clientSecret string, options *OnBehalfOfCredentialOptions) (*OnBehalfOfCredential, error) {
cred, err := confidential.NewCredFromSecret(clientSecret) cred, err := confidential.NewCredFromSecret(clientSecret)

View File

@ -152,7 +152,7 @@ func (p *publicClient) GetToken(ctx context.Context, tro policy.TokenRequestOpti
return p.token(ar, err) return p.token(ar, err)
} }
if p.opts.DisableAutomaticAuthentication { if p.opts.DisableAutomaticAuthentication {
return azcore.AccessToken{}, errAuthenticationRequired return azcore.AccessToken{}, newauthenticationRequiredError(p.name, tro)
} }
at, err := p.reqToken(ctx, client, tro) at, err := p.reqToken(ctx, client, tro)
if err == nil { if err == nil {

View File

@ -0,0 +1,112 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
# IMPORTANT: Do not invoke this file directly. Please instead run eng/common/TestResources/New-TestResources.ps1 from the repository root.
param (
[hashtable] $AdditionalParameters = @{},
[hashtable] $DeploymentOutputs
)
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
if ($CI) {
if (!$AdditionalParameters['deployResources']) {
Write-Host "Skipping post-provisioning script because resources weren't deployed"
return
}
az login --service-principal -u $DeploymentOutputs['AZIDENTITY_CLIENT_ID'] -p $DeploymentOutputs['AZIDENTITY_CLIENT_SECRET'] --tenant $DeploymentOutputs['AZIDENTITY_TENANT_ID']
az account set --subscription $DeploymentOutputs['AZIDENTITY_SUBSCRIPTION_ID']
}
Write-Host "Building container"
$image = "$($DeploymentOutputs['AZIDENTITY_ACR_LOGIN_SERVER'])/azidentity-managed-id-test"
Set-Content -Path "$PSScriptRoot/Dockerfile" -Value @"
FROM mcr.microsoft.com/oss/go/microsoft/golang:latest as builder
ENV GOARCH=amd64 GOWORK=off
COPY . /azidentity
WORKDIR /azidentity/testdata/managed-id-test
RUN go mod tidy
RUN go build -o /build/managed-id-test .
RUN GOOS=windows go build -o /build/managed-id-test.exe .
FROM mcr.microsoft.com/mirror/docker/library/alpine:3.16
RUN apk add gcompat
COPY --from=builder /build/* .
RUN chmod +x managed-id-test
CMD ["./managed-id-test"]
"@
# build from sdk/azidentity because we need that dir in the context (because the test app uses local azidentity)
docker build -t $image "$PSScriptRoot"
az acr login -n $DeploymentOutputs['AZIDENTITY_ACR_NAME']
docker push $image
$rg = $DeploymentOutputs['AZIDENTITY_RESOURCE_GROUP']
# ACI is easier to provision here than in the bicep file because the image isn't available before now
Write-Host "Deploying Azure Container Instance"
$aciName = "azidentity-test"
az container create -g $rg -n $aciName --image $image `
--acr-identity $($DeploymentOutputs['AZIDENTITY_USER_ASSIGNED_IDENTITY']) `
--assign-identity [system] $($DeploymentOutputs['AZIDENTITY_USER_ASSIGNED_IDENTITY']) `
--role "Storage Blob Data Reader" `
--scope $($DeploymentOutputs['AZIDENTITY_STORAGE_ID']) `
-e AZIDENTITY_STORAGE_NAME=$($DeploymentOutputs['AZIDENTITY_STORAGE_NAME']) `
AZIDENTITY_STORAGE_NAME_USER_ASSIGNED=$($DeploymentOutputs['AZIDENTITY_STORAGE_NAME_USER_ASSIGNED']) `
AZIDENTITY_USER_ASSIGNED_IDENTITY=$($DeploymentOutputs['AZIDENTITY_USER_ASSIGNED_IDENTITY']) `
FUNCTIONS_CUSTOMHANDLER_PORT=80
Write-Host "##vso[task.setvariable variable=AZIDENTITY_ACI_NAME;]$aciName"
# Azure Functions deployment: copy the Windows binary from the Docker image, deploy it in a zip
Write-Host "Deploying to Azure Functions"
$container = docker create $image
docker cp ${container}:managed-id-test.exe "$PSScriptRoot/testdata/managed-id-test/"
docker rm -v $container
Compress-Archive -Path "$PSScriptRoot/testdata/managed-id-test/*" -DestinationPath func.zip -Force
az functionapp deploy -g $rg -n $DeploymentOutputs['AZIDENTITY_FUNCTION_NAME'] --src-path func.zip --type zip
Write-Host "Creating federated identity"
$aksName = $DeploymentOutputs['AZIDENTITY_AKS_NAME']
$idName = $DeploymentOutputs['AZIDENTITY_USER_ASSIGNED_IDENTITY_NAME']
$issuer = az aks show -g $rg -n $aksName --query "oidcIssuerProfile.issuerUrl" -otsv
$podName = "azidentity-test"
$serviceAccountName = "workload-identity-sa"
az identity federated-credential create -g $rg --identity-name $idName --issuer $issuer --name $idName --subject system:serviceaccount:default:$serviceAccountName
Write-Host "Deploying to AKS"
az aks get-credentials -g $rg -n $aksName
az aks update --attach-acr $DeploymentOutputs['AZIDENTITY_ACR_NAME'] -g $rg -n $aksName
Set-Content -Path "$PSScriptRoot/k8s.yaml" -Value @"
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
azure.workload.identity/client-id: $($DeploymentOutputs['AZIDENTITY_USER_ASSIGNED_IDENTITY_CLIENT_ID'])
name: $serviceAccountName
namespace: default
---
apiVersion: v1
kind: Pod
metadata:
name: $podName
namespace: default
labels:
app: $podName
azure.workload.identity/use: "true"
spec:
serviceAccountName: $serviceAccountName
containers:
- name: $podName
image: $image
env:
- name: AZIDENTITY_STORAGE_NAME
value: $($DeploymentOutputs['AZIDENTITY_STORAGE_NAME_USER_ASSIGNED'])
- name: AZIDENTITY_USE_WORKLOAD_IDENTITY
value: "true"
- name: FUNCTIONS_CUSTOMHANDLER_PORT
value: "80"
nodeSelector:
kubernetes.io/os: linux
"@
kubectl apply -f "$PSScriptRoot/k8s.yaml"
Write-Host "##vso[task.setvariable variable=AZIDENTITY_POD_NAME;]$podName"

View File

@ -1,36 +1,44 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
# IMPORTANT: Do not invoke this file directly. Please instead run eng/common/TestResources/New-TestResources.ps1 from the repository root.
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
param ( param (
[hashtable] $AdditionalParameters = @{},
# Captures any arguments from eng/New-TestResources.ps1 not declared here (no parameter errors). # Captures any arguments from eng/New-TestResources.ps1 not declared here (no parameter errors).
[Parameter(ValueFromRemainingArguments = $true)] [Parameter(ValueFromRemainingArguments = $true)]
$RemainingArguments $RemainingArguments
) )
if (-not (Test-Path "$PSScriptRoot/sshkey.pub")) {
ssh-keygen -t rsa -b 4096 -f "$PSScriptRoot/sshkey" -N '' -C ''
}
$templateFileParameters['sshPubKey'] = Get-Content "$PSScriptRoot/sshkey.pub"
if (!$CI) { if (!$CI) {
# TODO: Remove this once auto-cloud config downloads are supported locally # TODO: Remove this once auto-cloud config downloads are supported locally
Write-Host "Skipping cert setup in local testing mode" Write-Host "Skipping cert setup in local testing mode"
return return
} }
if ($EnvironmentVariables -eq $null -or $EnvironmentVariables.Count -eq 0) { if ($null -eq $EnvironmentVariables -or $EnvironmentVariables.Count -eq 0) {
throw "EnvironmentVariables must be set in the calling script New-TestResources.ps1" throw "EnvironmentVariables must be set in the calling script New-TestResources.ps1"
} }
$tmp = $env:TEMP ? $env:TEMP : [System.IO.Path]::GetTempPath() $tmp = $env:TEMP ? $env:TEMP : [System.IO.Path]::GetTempPath()
$pfxPath = Join-Path $tmp "test.pfx" $pfxPath = Join-Path $tmp "test.pfx"
$pemPath = Join-Path $tmp "test.pem" $pemPath = Join-Path $tmp "test.pem"
$sniPath = Join-Path $tmp "testsni.pfx"
Write-Host "Creating identity test files: $pfxPath $pemPath $sniPath" Write-Host "Creating identity test files: $pfxPath $pemPath"
[System.Convert]::FromBase64String($EnvironmentVariables['PFX_CONTENTS']) | Set-Content -Path $pfxPath -AsByteStream [System.Convert]::FromBase64String($EnvironmentVariables['PFX_CONTENTS']) | Set-Content -Path $pfxPath -AsByteStream
Set-Content -Path $pemPath -Value $EnvironmentVariables['PEM_CONTENTS'] Set-Content -Path $pemPath -Value $EnvironmentVariables['PEM_CONTENTS']
[System.Convert]::FromBase64String($EnvironmentVariables['SNI_CONTENTS']) | Set-Content -Path $sniPath -AsByteStream
# Set for pipeline # Set for pipeline
Write-Host "##vso[task.setvariable variable=IDENTITY_SP_CERT_PFX;]$pfxPath" Write-Host "##vso[task.setvariable variable=IDENTITY_SP_CERT_PFX;]$pfxPath"
Write-Host "##vso[task.setvariable variable=IDENTITY_SP_CERT_PEM;]$pemPath" Write-Host "##vso[task.setvariable variable=IDENTITY_SP_CERT_PEM;]$pemPath"
Write-Host "##vso[task.setvariable variable=IDENTITY_SP_CERT_SNI;]$sniPath"
# Set for local # Set for local
$env:IDENTITY_SP_CERT_PFX = $pfxPath $env:IDENTITY_SP_CERT_PFX = $pfxPath
$env:IDENTITY_SP_CERT_PEM = $pemPath $env:IDENTITY_SP_CERT_PEM = $pemPath
$env:IDENTITY_SP_CERT_SNI = $sniPath

View File

@ -1 +1,219 @@
param baseName string // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
@description('Kubernetes cluster admin user name.')
param adminUser string = 'azureuser'
@minLength(6)
@maxLength(23)
@description('The base resource name.')
param baseName string = resourceGroup().name
@description('Whether to deploy resources. When set to false, this file deploys nothing.')
param deployResources bool = false
param sshPubKey string = ''
@description('The location of the resource. By default, this is the same as the resource group.')
param location string = resourceGroup().location
// https://learn.microsoft.com/azure/role-based-access-control/built-in-roles
var acrPull = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')
var blobReader = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1')
resource sa 'Microsoft.Storage/storageAccounts@2021-08-01' = if (deployResources) {
kind: 'StorageV2'
location: location
name: 'sa${uniqueString(baseName)}'
properties: {
accessTier: 'Hot'
}
sku: {
name: 'Standard_LRS'
}
}
resource saUserAssigned 'Microsoft.Storage/storageAccounts@2021-08-01' = if (deployResources) {
kind: 'StorageV2'
location: location
name: 'sa2${uniqueString(baseName)}'
properties: {
accessTier: 'Hot'
}
sku: {
name: 'Standard_LRS'
}
}
resource usermgdid 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = if (deployResources) {
location: location
name: baseName
}
resource acrPullContainerInstance 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (deployResources) {
name: guid(resourceGroup().id, acrPull, 'containerInstance')
properties: {
principalId: deployResources ? usermgdid.properties.principalId : ''
principalType: 'ServicePrincipal'
roleDefinitionId: acrPull
}
scope: containerRegistry
}
resource blobRoleUserAssigned 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (deployResources) {
scope: saUserAssigned
name: guid(resourceGroup().id, blobReader, usermgdid.id)
properties: {
principalId: deployResources ? usermgdid.properties.principalId : ''
principalType: 'ServicePrincipal'
roleDefinitionId: blobReader
}
}
resource blobRoleFunc 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (deployResources) {
name: guid(resourceGroup().id, blobReader, 'azfunc')
properties: {
principalId: deployResources ? azfunc.identity.principalId : ''
roleDefinitionId: blobReader
principalType: 'ServicePrincipal'
}
scope: sa
}
resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' = if (deployResources) {
location: location
name: uniqueString(resourceGroup().id)
properties: {
adminUserEnabled: true
}
sku: {
name: 'Basic'
}
}
resource farm 'Microsoft.Web/serverfarms@2021-03-01' = if (deployResources) {
kind: 'app'
location: location
name: '${baseName}_asp'
properties: {}
sku: {
capacity: 1
family: 'B'
name: 'B1'
size: 'B1'
tier: 'Basic'
}
}
resource azfunc 'Microsoft.Web/sites@2021-03-01' = if (deployResources) {
identity: {
type: 'SystemAssigned, UserAssigned'
userAssignedIdentities: {
'${deployResources ? usermgdid.id : ''}': {}
}
}
kind: 'functionapp'
location: location
name: '${baseName}func'
properties: {
enabled: true
httpsOnly: true
keyVaultReferenceIdentity: 'SystemAssigned'
serverFarmId: farm.id
siteConfig: {
alwaysOn: true
appSettings: [
{
name: 'AZIDENTITY_STORAGE_NAME'
value: deployResources ? sa.name : null
}
{
name: 'AZIDENTITY_STORAGE_NAME_USER_ASSIGNED'
value: deployResources ? saUserAssigned.name : null
}
{
name: 'AZIDENTITY_USER_ASSIGNED_IDENTITY'
value: deployResources ? usermgdid.id : null
}
{
name: 'AzureWebJobsStorage'
value: 'DefaultEndpointsProtocol=https;AccountName=${deployResources ? sa.name : ''};EndpointSuffix=${deployResources ? environment().suffixes.storage : ''};AccountKey=${deployResources ? sa.listKeys().keys[0].value : ''}'
}
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~4'
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: 'custom'
}
{
name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
value: 'DefaultEndpointsProtocol=https;AccountName=${deployResources ? sa.name : ''};EndpointSuffix=${deployResources ? environment().suffixes.storage : ''};AccountKey=${deployResources ? sa.listKeys().keys[0].value : ''}'
}
{
name: 'WEBSITE_CONTENTSHARE'
value: toLower('${baseName}-func')
}
]
http20Enabled: true
minTlsVersion: '1.2'
}
}
}
resource aks 'Microsoft.ContainerService/managedClusters@2023-06-01' = if (deployResources) {
name: baseName
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
agentPoolProfiles: [
{
count: 1
enableAutoScaling: false
kubeletDiskType: 'OS'
mode: 'System'
name: 'agentpool'
osDiskSizeGB: 128
osDiskType: 'Managed'
osSKU: 'Ubuntu'
osType: 'Linux'
type: 'VirtualMachineScaleSets'
vmSize: 'Standard_D2s_v3'
}
]
dnsPrefix: 'identitytest'
enableRBAC: true
linuxProfile: {
adminUsername: adminUser
ssh: {
publicKeys: [
{
keyData: sshPubKey
}
]
}
}
oidcIssuerProfile: {
enabled: true
}
securityProfile: {
workloadIdentity: {
enabled: true
}
}
}
}
output AZIDENTITY_ACR_LOGIN_SERVER string = deployResources ? containerRegistry.properties.loginServer : ''
output AZIDENTITY_ACR_NAME string = deployResources ? containerRegistry.name : ''
output AZIDENTITY_AKS_NAME string = deployResources ? aks.name : ''
output AZIDENTITY_FUNCTION_NAME string = deployResources ? azfunc.name : ''
output AZIDENTITY_STORAGE_ID string = deployResources ? sa.id : ''
output AZIDENTITY_STORAGE_NAME string = deployResources ? sa.name : ''
output AZIDENTITY_STORAGE_NAME_USER_ASSIGNED string = deployResources ? saUserAssigned.name : ''
output AZIDENTITY_USER_ASSIGNED_IDENTITY string = deployResources ? usermgdid.id : ''
output AZIDENTITY_USER_ASSIGNED_IDENTITY_CLIENT_ID string = deployResources ? usermgdid.properties.clientId : ''
output AZIDENTITY_USER_ASSIGNED_IDENTITY_NAME string = deployResources ? usermgdid.name : ''

View File

@ -14,5 +14,5 @@ const (
module = "github.com/Azure/azure-sdk-for-go/sdk/" + component module = "github.com/Azure/azure-sdk-for-go/sdk/" + component
// Version is the semantic version (see http://semver.org) of this module. // Version is the semantic version (see http://semver.org) of this module.
version = "v1.5.1" version = "v1.6.0"
) )

View File

@ -39,6 +39,11 @@ type PayloadOptions struct {
// Subsequent reads will access the cached value. // Subsequent reads will access the cached value.
// Exported as runtime.Payload() WITHOUT the opts parameter. // Exported as runtime.Payload() WITHOUT the opts parameter.
func Payload(resp *http.Response, opts *PayloadOptions) ([]byte, error) { func Payload(resp *http.Response, opts *PayloadOptions) ([]byte, error) {
if resp.Body == nil {
// this shouldn't happen in real-world scenarios as a
// response with no body should set it to http.NoBody
return nil, nil
}
modifyBytes := func(b []byte) []byte { return b } modifyBytes := func(b []byte) []byte { return b }
if opts != nil && opts.BytesModifier != nil { if opts != nil && opts.BytesModifier != nil {
modifyBytes = opts.BytesModifier modifyBytes = opts.BytesModifier

View File

@ -48,8 +48,8 @@ duplication.
.Net People, Take note on X509: .Net People, Take note on X509:
This uses x509.Certificates and private keys. x509 does not store private keys. .Net This uses x509.Certificates and private keys. x509 does not store private keys. .Net
has some x509.Certificate2 thing that has private keys, but that is just some bullcrap that .Net has a x509.Certificate2 abstraction that has private keys, but that just a strange invention.
added, it doesn't exist in real life. As such I've put a PEM decoder into here. As such I've put a PEM decoder into here.
*/ */
// TODO(msal): This should have example code for each method on client using Go's example doc framework. // TODO(msal): This should have example code for each method on client using Go's example doc framework.

View File

@ -217,11 +217,13 @@ func WithClaims(claims string) interface {
func WithAuthenticationScheme(authnScheme AuthenticationScheme) interface { func WithAuthenticationScheme(authnScheme AuthenticationScheme) interface {
AcquireSilentOption AcquireSilentOption
AcquireInteractiveOption AcquireInteractiveOption
AcquireByUsernamePasswordOption
options.CallOption options.CallOption
} { } {
return struct { return struct {
AcquireSilentOption AcquireSilentOption
AcquireInteractiveOption AcquireInteractiveOption
AcquireByUsernamePasswordOption
options.CallOption options.CallOption
}{ }{
CallOption: options.NewCallOption( CallOption: options.NewCallOption(
@ -231,6 +233,8 @@ func WithAuthenticationScheme(authnScheme AuthenticationScheme) interface {
t.authnScheme = authnScheme t.authnScheme = authnScheme
case *interactiveAuthOptions: case *interactiveAuthOptions:
t.authnScheme = authnScheme t.authnScheme = authnScheme
case *acquireTokenByUsernamePasswordOptions:
t.authnScheme = authnScheme
default: default:
return fmt.Errorf("unexpected options type %T", a) return fmt.Errorf("unexpected options type %T", a)
} }
@ -349,6 +353,7 @@ func (pca Client) AcquireTokenSilent(ctx context.Context, scopes []string, opts
// acquireTokenByUsernamePasswordOptions contains optional configuration for AcquireTokenByUsernamePassword // acquireTokenByUsernamePasswordOptions contains optional configuration for AcquireTokenByUsernamePassword
type acquireTokenByUsernamePasswordOptions struct { type acquireTokenByUsernamePasswordOptions struct {
claims, tenantID string claims, tenantID string
authnScheme AuthenticationScheme
} }
// AcquireByUsernamePasswordOption is implemented by options for AcquireTokenByUsernamePassword // AcquireByUsernamePasswordOption is implemented by options for AcquireTokenByUsernamePassword
@ -374,6 +379,9 @@ func (pca Client) AcquireTokenByUsernamePassword(ctx context.Context, scopes []s
authParams.Claims = o.claims authParams.Claims = o.claims
authParams.Username = username authParams.Username = username
authParams.Password = password authParams.Password = password
if o.authnScheme != nil {
authParams.AuthnScheme = o.authnScheme
}
token, err := pca.base.Token.UsernamePassword(ctx, authParams) token, err := pca.base.Token.UsernamePassword(ctx, authParams)
if err != nil { if err != nil {

View File

@ -62,7 +62,7 @@ func (m *SigningMethodECDSA) Verify(signingString string, sig []byte, key interf
case *ecdsa.PublicKey: case *ecdsa.PublicKey:
ecdsaKey = k ecdsaKey = k
default: default:
return newError("ECDSA verify expects *ecsda.PublicKey", ErrInvalidKeyType) return newError("ECDSA verify expects *ecdsa.PublicKey", ErrInvalidKeyType)
} }
if len(sig) != 2*m.KeySize { if len(sig) != 2*m.KeySize {
@ -96,7 +96,7 @@ func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) ([]byte
case *ecdsa.PrivateKey: case *ecdsa.PrivateKey:
ecdsaKey = k ecdsaKey = k
default: default:
return nil, newError("ECDSA sign expects *ecsda.PrivateKey", ErrInvalidKeyType) return nil, newError("ECDSA sign expects *ecdsa.PrivateKey", ErrInvalidKeyType)
} }
// Create the hasher // Create the hasher

Some files were not shown because too many files have changed in this diff Show More