diff --git a/Makefile b/Makefile
index b5a9b9a5c5..d301a80249 100644
--- a/Makefile
+++ b/Makefile
@@ -61,6 +61,7 @@ OPERATOR_IMG=$(DOCKER_HUB_REPO)/$(DOCKER_HUB_OPERATOR_IMG):$(DOCKER_HUB_OPERATOR
OPERATOR_TEST_IMG=$(DOCKER_HUB_REPO)/$(DOCKER_HUB_OPERATOR_TEST_IMG):$(DOCKER_HUB_OPERATOR_TEST_TAG)
BUNDLE_IMG=$(DOCKER_HUB_REPO)/$(DOCKER_HUB_BUNDLE_IMG):$(RELEASE_VER)
REGISTRY_IMG=$(DOCKER_HUB_REPO)/$(DOCKER_HUB_REGISTRY_IMG):$(RELEASE_VER)
+
PX_DOC_HOST ?= https://docs.portworx.com
PX_INSTALLER_HOST ?= https://install.portworx.com
PROMETHEUS_OPERATOR_HELM_CHARTS_TAG ?= kube-prometheus-stack-42.1.0
@@ -78,7 +79,8 @@ BUILD_OPTIONS := -ldflags=$(LDFLAGS)
.DEFAULT_GOAL=all
.PHONY: operator deploy clean vendor vendor-update test generate manifests tools-check
-all: operator pretest downloads
+all: operator resource-gateway pretest downloads
+dev: operator resource-gateway container deploy
vendor-update:
go mod download
@@ -174,11 +176,16 @@ codegen:
@echo "Generating CRD"
(GOFLAGS="" hack/update-codegen.sh)
-operator:
+resource-gateway:
+ @echo "Building the resource-gateway binary"
+ @cd cmd/resource-gateway && CGO_ENABLED=0 go build $(BUILD_OPTIONS) -o $(BIN)/resource-gateway
+
+operator: resource-gateway
@echo "Building the cluster operator binary"
@cd cmd/operator && CGO_ENABLED=0 go build $(BUILD_OPTIONS) -o $(BIN)/operator
@cd cmd/dryrun && CGO_ENABLED=0 go build $(BUILD_OPTIONS) -o $(BIN)/dryrun
+
container:
@echo "Building operator image $(OPERATOR_IMG)"
docker build --pull --tag $(OPERATOR_IMG) -f build/Dockerfile .
@@ -298,3 +305,6 @@ clean: clean-release-manifest clean-bundle
@go clean -i $(PKGS)
@echo "Deleting image "$(OPERATOR_IMG)
@docker rmi -f $(OPERATOR_IMG) registry.access.redhat.com/ubi9-minimal:latest
+
+resource-gateway-proto:
+ $(MAKE) -C proto docker-proto
\ No newline at end of file
diff --git a/build/Dockerfile b/build/Dockerfile
index f9e5cd7272..721276584a 100644
--- a/build/Dockerfile
+++ b/build/Dockerfile
@@ -22,3 +22,4 @@ COPY manifests /manifests
COPY bin/configs /configs
COPY bin/operator /
COPY bin/dryrun /
+COPY bin/resource-gateway /
diff --git a/cmd/resource-gateway/resource_gateway.go b/cmd/resource-gateway/resource_gateway.go
new file mode 100644
index 0000000000..ab296845bf
--- /dev/null
+++ b/cmd/resource-gateway/resource_gateway.go
@@ -0,0 +1,153 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ _ "net/http/pprof"
+ "os"
+ "strings"
+
+ "github.com/libopenstorage/grpc-framework/pkg/auth"
+ grpcFramework "github.com/libopenstorage/grpc-framework/server"
+
+ pxutil "github.com/libopenstorage/operator/drivers/storage/portworx/util"
+ resourceGateway "github.com/libopenstorage/operator/pkg/resource-gateway"
+ "github.com/libopenstorage/operator/pkg/version"
+ "github.com/sirupsen/logrus"
+ "github.com/urfave/cli"
+)
+
+func main() {
+ app := cli.NewApp()
+ app.Name = "resource-gateway"
+ app.Usage = "gRPC service for managing resources"
+ app.Version = version.Version
+ app.Action = run
+
+ app.Flags = []cli.Flag{
+ cli.StringFlag{
+ Name: "serverHost",
+ Usage: "Host for resource-gateway gRPC server",
+ },
+ cli.StringFlag{
+ Name: "serverPort",
+ Usage: "Port for resource-gateway gRPC server",
+ },
+ cli.StringFlag{
+ Name: "namespace",
+ Usage: "Name of the configmap to use for semaphore",
+ },
+ cli.StringFlag{
+ Name: "configMapName",
+ Usage: "Name of the configmap to use for semaphore",
+ },
+ cli.StringFlag{
+ Name: "configMapLabels",
+ Usage: "Labels to use for the configmap",
+ },
+ cli.DurationFlag{
+ Name: "configMapUpdatePeriod",
+ Usage: "Time period between configmap updates",
+ },
+ cli.DurationFlag{
+ Name: "deadClientTimeout",
+ Usage: "Time period after which a node is considered dead",
+ },
+ cli.BoolFlag{
+ Name: "debug",
+ Usage: "Set log level to debug",
+ },
+ }
+
+ if err := app.Run(os.Args); err != nil {
+ log.Fatalf("Error starting resource gateway gRPC server: %v", err)
+ }
+}
+
+// run is the main function for resource-gateway gRPC server
+// it initializes the k8s client, creates the gRPC server, and runs the server...
+func run(c *cli.Context) {
+ if c.Bool("debug") {
+ logrus.SetLevel(logrus.DebugLevel)
+ }
+
+ resourceGatewayServer := resourceGateway.NewResourceGatewayServer(
+ newResourceGatewayServerConfig(c),
+ newSemaphoreConfig(c))
+ err := resourceGatewayServer.SetupSigIntHandler()
+ if err != nil {
+ logrus.Fatalf("Failed to setup signal handler: %v", err)
+ }
+ err = resourceGatewayServer.Start()
+ if err != nil {
+ logrus.Fatalf("Failed to start resource-gateway server: %v", err)
+ }
+}
+
+// newResourceGatewayServerConfig creates the config for resource-gateway gRPC server
+func newResourceGatewayServerConfig(c *cli.Context) *grpcFramework.ServerConfig {
+ resourceGatewayServerConfig := resourceGateway.NewResourceGatewayServerConfig()
+
+ serverName := c.String("serverName")
+ if serverName == "" {
+ resourceGatewayServerConfig.Name = serverName
+ }
+
+ serverHost := c.String("serverHost")
+ serverPort := c.String("serverPort")
+ if serverHost != "" && serverPort != "" {
+ serverAddress := fmt.Sprintf("%s:%s", serverHost, serverPort)
+ resourceGatewayServerConfig.Address = serverAddress
+ }
+
+ // if Px security is enabled, then Issuer and SharedSecret will be set in the environment
+ authIssuer := os.Getenv(pxutil.EnvKeyPortworxAuthJwtIssuer)
+ authSharedSecret := os.Getenv(pxutil.EnvKeyPortworxAuthJwtSharedSecret)
+ if authIssuer != "" && authSharedSecret != "" {
+ security := &grpcFramework.SecurityConfig{}
+ authenticator, err := auth.NewJwtAuthenticator(
+ &auth.JwtAuthConfig{
+ SharedSecret: []byte(authSharedSecret),
+ })
+ if err != nil {
+ log.Fatalf("unable to create shared key authenticator")
+ }
+ security.Authenticators = map[string]auth.Authenticator{
+ authIssuer: authenticator,
+ }
+ resourceGatewayServerConfig.Security = security
+ }
+
+ return resourceGatewayServerConfig
+}
+
+// newSemaphoreConfig creates a SemaphoreConfig object with provided
+// cli arguments to initialize a new semaphore server
+func newSemaphoreConfig(c *cli.Context) *resourceGateway.SemaphoreConfig {
+ semaphoreConfig := resourceGateway.NewSemaphoreConfig()
+ if c.String("configMapName") != "" {
+ semaphoreConfig.ConfigMapName = c.String("configMapName")
+ }
+ if c.String("namespace") != "" {
+ semaphoreConfig.ConfigMapNamespace = c.String("namespace")
+ }
+ if c.String("configMapLabels") != "" {
+ configMapLabels := make(map[string]string)
+ for _, kv := range strings.Split(c.String("configMapLabels"), ",") {
+ kvSplit := strings.Split(kv, "=")
+ if len(kvSplit) != 2 {
+ logrus.Errorf("Invalid configMapLabels: %s", kvSplit)
+ continue
+ }
+ configMapLabels[kvSplit[0]] = kvSplit[1]
+ }
+ semaphoreConfig.ConfigMapLabels = configMapLabels
+ }
+ if c.Duration("configMapUpdatePeriod") != 0 {
+ semaphoreConfig.ConfigMapUpdatePeriod = c.Duration("configMapUpdatePeriod")
+ }
+ if c.Duration("deadClientTimeout") != 0 {
+ semaphoreConfig.DeadClientTimeout = c.Duration("deadClientTimeout")
+ }
+ return semaphoreConfig
+}
diff --git a/deploy/crds/core_v1_storagecluster_crd.yaml b/deploy/crds/core_v1_storagecluster_crd.yaml
index 021be0af90..4009485109 100644
--- a/deploy/crds/core_v1_storagecluster_crd.yaml
+++ b/deploy/crds/core_v1_storagecluster_crd.yaml
@@ -931,6 +931,45 @@ spec:
cpu:
type: string
description: CPU limit.
+ resourceGateway:
+ type: object
+ description: Contains spec of resource-gateway component for storage driver.
+ properties:
+ enabled:
+ type: boolean
+ description: Flag indicating whether resource-gateway needs to be enabled.
+ image:
+ type: string
+ description: Docker image of the resource-gateway container.
+ args:
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ description: >-
+ It is a map of arguments provided to resource-gateway. Example: log-level: debug
+ resources:
+ type: object
+ description: Specifies the resource requirements for the resource-gateway pod.
+ properties:
+ requests:
+ type: object
+ description: Requested resources.
+ properties:
+ memory:
+ type: string
+ description: Requested memory.
+ cpu:
+ type: string
+ description: Requested cpu.
+ limits:
+ type: object
+ description: Resource limit.
+ properties:
+ memory:
+ type: string
+ description: Memory limit.
+ cpu:
+ type: string
+ description: CPU limit.
monitoring:
type: object
description: Contains monitoring configuration for the storage cluster.
@@ -3902,6 +3941,9 @@ spec:
dynamicPluginProxy:
type: string
description: Desired image for nginx proxy image.
+ resourceGateway:
+ type: string
+ description: Desired image for px resource gateway.
conditions:
type: array
description: Contains details for the current condition of this cluster.
diff --git a/drivers/storage/portworx/component/component.go b/drivers/storage/portworx/component/component.go
index a5fe79dd6d..7b80b972cb 100644
--- a/drivers/storage/portworx/component/component.go
+++ b/drivers/storage/portworx/component/component.go
@@ -59,6 +59,7 @@ func Register(name string, c PortworxComponent) {
registerLock.Lock()
defer registerLock.Unlock()
components[name] = c
+
}
// Get returns a PortworxComponent if present else returns (nil, false)
diff --git a/drivers/storage/portworx/component/resource_gateway.go b/drivers/storage/portworx/component/resource_gateway.go
new file mode 100644
index 0000000000..5624130976
--- /dev/null
+++ b/drivers/storage/portworx/component/resource_gateway.go
@@ -0,0 +1,421 @@
+package component
+
+import (
+ "context"
+ "fmt"
+ "sort"
+ "strconv"
+ "strings"
+
+ "github.com/hashicorp/go-version"
+ pxutil "github.com/libopenstorage/operator/drivers/storage/portworx/util"
+ corev1 "github.com/libopenstorage/operator/pkg/apis/core/v1"
+ "github.com/libopenstorage/operator/pkg/util"
+ k8sutil "github.com/libopenstorage/operator/pkg/util/k8s"
+ appsv1 "k8s.io/api/apps/v1"
+ v1 "k8s.io/api/core/v1"
+ rbacv1 "k8s.io/api/rbac/v1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/apimachinery/pkg/util/intstr"
+ "k8s.io/client-go/tools/record"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+const (
+ // ResourceGatewayComponentName name of the ResourceGateway component
+ ResourceGatewayComponentName = "ResourceGateway"
+ // resourceGatewayStr common string value for resourceGateway k8s subparts
+ resourceGatewayStr = "resource-gateway"
+
+ // names for resource-gateway kubernetes resources
+ //
+ // ResourceGatewayServiceAccountName name of the resourceGateway service account
+ ResourceGatewayServiceAccountName = resourceGatewayStr
+ // ResourceGatewayRoleName name of the resourceGateway cluster role
+ ResourceGatewayRoleName = resourceGatewayStr
+ // ResourceGatewayRoleBindingName name of the resourceGateway cluster role binding
+ ResourceGatewayRoleBindingName = resourceGatewayStr
+ // ResourceGatewayDeploymentName name of the resourceGateway deployment
+ ResourceGatewayDeploymentName = resourceGatewayStr
+ // ResourceGatewayServiceName name of the resourceGateway service
+ ResourceGatewayServiceName = resourceGatewayStr
+ // ResourceGatewayContainerName name of the resourceGateway container
+ ResourceGatewayContainerName = resourceGatewayStr
+ // ResourceGatewayLabelName label name for resourceGateway
+ ResourceGatewayLabelName = resourceGatewayStr
+
+ // configuration values for resource-gateway deployment and service
+ //
+ // resourceGatewayPortName name of the resourceGateway port
+ resourceGatewayPortName = "resource-gate" // must be no more than 15 characters
+ // resourceGatewayDeploymentHost is the host resourceGateway deployment listens on
+ resourceGatewayDeploymentHost = "0.0.0.0"
+ // resourceGatewayPort common port for resourceGateway components
+ resourceGatewayPort = 50051
+ // resourceGatewayDeploymentPort is the port resourceGateway deployment listens on
+ resourceGatewayDeploymentPort = resourceGatewayPort
+ // resourceGatewayServicePort is the port resourceGateway service listens on
+ resourceGatewayServicePort = resourceGatewayPort
+
+ // configuration for resource-gateway deployment liveliness probe
+ //
+ // resourceGatewayLivenessProbeInitialDelaySeconds is the initial delay for the resource-gateway liveness probe
+ resourceGatewayLivenessProbeInitialDelaySeconds = 10
+ // resourceGatewayLivenessProbePeriodSeconds is the period for the resource-gateway liveness probe
+ resourceGatewayLivenessProbePeriodSeconds = 2
+)
+
+var defaultResourceGatewayCommandArgs = map[string]string{
+ "serverHost": resourceGatewayDeploymentHost,
+ "serverPort": strconv.Itoa(resourceGatewayPort),
+}
+
+// resourceGateway is the PortworxComponent implementation for the resource-gateway component
+type resourceGateway struct {
+ k8sClient client.Client
+}
+
+func (r *resourceGateway) Name() string {
+ return ResourceGatewayComponentName
+}
+
+func (r *resourceGateway) Priority() int32 {
+ return DefaultComponentPriority
+}
+
+func (r *resourceGateway) Initialize(
+ k8sClient client.Client,
+ _ version.Version,
+ _ *runtime.Scheme,
+ _ record.EventRecorder,
+) {
+ r.k8sClient = k8sClient
+}
+
+func (r *resourceGateway) IsPausedForMigration(cluster *corev1.StorageCluster) bool {
+ return util.ComponentsPausedForMigration(cluster)
+}
+
+func (r *resourceGateway) IsEnabled(cluster *corev1.StorageCluster) bool {
+ return cluster.Spec.ResourceGateway != nil && cluster.Spec.ResourceGateway.Enabled
+}
+
+func (r *resourceGateway) Reconcile(cluster *corev1.StorageCluster) error {
+ ownerRef := metav1.NewControllerRef(cluster, pxutil.StorageClusterKind())
+ if err := r.createServiceAccount(cluster.Namespace, ownerRef); err != nil {
+ return err
+ }
+ if err := r.createRole(cluster.Namespace, ownerRef); err != nil {
+ return err
+ }
+ if err := r.createRoleBinding(cluster.Namespace, ownerRef); err != nil {
+ return err
+ }
+ if err := r.createDeployment(cluster, ownerRef); err != nil {
+ return err
+ }
+ if err := r.createService(cluster, ownerRef); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (r *resourceGateway) Delete(cluster *corev1.StorageCluster) error {
+ ownerRef := metav1.NewControllerRef(cluster, pxutil.StorageClusterKind())
+ if err := k8sutil.DeleteServiceAccount(r.k8sClient, ResourceGatewayServiceAccountName, cluster.Namespace, *ownerRef); err != nil {
+ return err
+ }
+ if err := k8sutil.DeleteRole(r.k8sClient, ResourceGatewayRoleName, cluster.Namespace, *ownerRef); err != nil {
+ return err
+ }
+ if err := k8sutil.DeleteRoleBinding(r.k8sClient, ResourceGatewayRoleBindingName, cluster.Namespace, *ownerRef); err != nil {
+ return err
+ }
+ if err := k8sutil.DeleteDeployment(r.k8sClient, ResourceGatewayDeploymentName, cluster.Namespace, *ownerRef); err != nil {
+ return err
+ }
+ if err := k8sutil.DeleteService(r.k8sClient, ResourceGatewayServiceName, cluster.Namespace, *ownerRef); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (r *resourceGateway) MarkDeleted() {}
+
+func (r *resourceGateway) createServiceAccount(
+ clusterNamespace string,
+ ownerRef *metav1.OwnerReference,
+) error {
+ return k8sutil.CreateOrUpdateServiceAccount(
+ r.k8sClient,
+ &v1.ServiceAccount{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: ResourceGatewayServiceAccountName,
+ Namespace: clusterNamespace,
+ OwnerReferences: []metav1.OwnerReference{*ownerRef},
+ },
+ },
+ ownerRef,
+ )
+}
+
+func (r *resourceGateway) createRole(
+ clusterNamespace string,
+ ownerRef *metav1.OwnerReference,
+) error {
+ return k8sutil.CreateOrUpdateRole(
+ r.k8sClient,
+ &rbacv1.Role{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: ResourceGatewayRoleName,
+ Namespace: clusterNamespace,
+ },
+ Rules: []rbacv1.PolicyRule{
+ {
+ APIGroups: []string{""},
+ Resources: []string{"configmaps"},
+ Verbs: []string{"create", "get", "list", "patch", "update", "watch"},
+ },
+ },
+ },
+ ownerRef,
+ )
+}
+
+func (r *resourceGateway) createRoleBinding(
+ clusterNamespace string,
+ ownerRef *metav1.OwnerReference,
+) error {
+ return k8sutil.CreateOrUpdateRoleBinding(
+ r.k8sClient,
+ &rbacv1.RoleBinding{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: ResourceGatewayRoleBindingName,
+ Namespace: clusterNamespace,
+ },
+ Subjects: []rbacv1.Subject{
+ {
+ Kind: "ServiceAccount",
+ Name: ResourceGatewayServiceAccountName,
+ },
+ },
+ RoleRef: rbacv1.RoleRef{
+ Kind: "Role",
+ Name: ResourceGatewayRoleName,
+ APIGroup: "rbac.authorization.k8s.io",
+ },
+ },
+ ownerRef,
+ )
+}
+
+func (r *resourceGateway) getImage(cluster *corev1.StorageCluster) string {
+ image := cluster.Spec.ResourceGateway.Image
+ if image == "" {
+ image = cluster.Status.DesiredImages.ResourceGateway
+ }
+ if image != "" {
+ image = util.GetImageURN(cluster, image)
+ }
+ return image
+}
+
+// getCommand returns the command to run the resource-gateway server with custom configuration
+// it uses the configuration values from the StorageCluster spec and sets default values if not present
+func (r *resourceGateway) getCommand(cluster *corev1.StorageCluster) []string {
+ args := map[string]string{}
+
+ // parse user arguments from the StorageCluster spec
+ for k, v := range cluster.Spec.ResourceGateway.Args {
+ key := strings.TrimLeft(k, "-")
+ if len(key) > 0 && len(v) > 0 {
+ args[key] = v
+ }
+ }
+
+ // fill in the missing arguments with default values
+ defaultResourceGatewayCommandArgs["namespace"] = cluster.Namespace // set namespace
+ for k, v := range defaultResourceGatewayCommandArgs {
+ if _, ok := args[k]; !ok {
+ args[k] = v
+ }
+ }
+
+ argList := make([]string, 0)
+ for k, v := range args {
+ argList = append(argList, fmt.Sprintf("--%s=%s", k, v))
+ }
+ sort.Strings(argList)
+
+ command := append([]string{"/resource-gateway"}, argList...)
+ return command
+}
+
+func (r *resourceGateway) createDeployment(
+ cluster *corev1.StorageCluster,
+ ownerRef *metav1.OwnerReference,
+) error {
+ // get the existing deployment
+ existingDeployment := &appsv1.Deployment{}
+ err := r.k8sClient.Get(
+ context.TODO(),
+ types.NamespacedName{
+ Name: ResourceGatewayDeploymentName,
+ Namespace: cluster.Namespace,
+ },
+ existingDeployment,
+ )
+ if err != nil && !errors.IsNotFound(err) {
+ return err
+ }
+
+ // get the target deployment
+ imageName := r.getImage(cluster)
+ if imageName == "" {
+ return fmt.Errorf("px reosurce gateway image cannot be empty")
+ }
+ command := r.getCommand(cluster)
+ targetDeployment := r.getResourceGatewayDeploymentSpec(cluster, ownerRef, imageName, command)
+
+ // compare existing and target deployments and create/update the deployment if necessary
+ isPodTemplateEqual, _ := util.DeepEqualPodTemplate(&targetDeployment.Spec.Template, &existingDeployment.Spec.Template)
+ if !isPodTemplateEqual {
+ if err = k8sutil.CreateOrUpdateDeployment(r.k8sClient, targetDeployment, ownerRef); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (r *resourceGateway) createService(
+ cluster *corev1.StorageCluster,
+ ownerRef *metav1.OwnerReference,
+) error {
+ return k8sutil.CreateOrUpdateService(
+ r.k8sClient,
+ &v1.Service{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: ResourceGatewayServiceName,
+ Namespace: cluster.Namespace,
+ OwnerReferences: []metav1.OwnerReference{*ownerRef},
+ },
+ Spec: v1.ServiceSpec{
+ Type: v1.ServiceTypeClusterIP,
+ Selector: map[string]string{
+ "name": ResourceGatewayLabelName,
+ },
+ Ports: []v1.ServicePort{
+ {
+ Name: resourceGatewayPortName,
+ Port: resourceGatewayServicePort,
+ TargetPort: intstr.FromInt(resourceGatewayDeploymentPort),
+ Protocol: v1.ProtocolTCP,
+ },
+ },
+ },
+ },
+ ownerRef,
+ )
+}
+
+func (r *resourceGateway) getResourceGatewayDeploymentSpec(
+ cluster *corev1.StorageCluster,
+ ownerRef *metav1.OwnerReference,
+ imageName string,
+ command []string,
+) *appsv1.Deployment {
+ commonLabels := map[string]string{
+ "name": ResourceGatewayLabelName,
+ "tier": "control-plane",
+ }
+
+ replicas := int32(1)
+ imagePullPolicy := pxutil.ImagePullPolicy(cluster)
+
+ // get the security environment variables
+ envMap := map[string]*v1.EnvVar{}
+ pxutil.PopulateSecurityEnvironmentVariables(cluster, envMap)
+ envList := make([]v1.EnvVar, 0)
+ for _, env := range envMap {
+ envList = append(envList, *env)
+ }
+
+ deployment := &appsv1.Deployment{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: ResourceGatewayDeploymentName,
+ Namespace: cluster.Namespace,
+ Labels: commonLabels,
+ OwnerReferences: []metav1.OwnerReference{*ownerRef},
+ },
+ Spec: appsv1.DeploymentSpec{
+ Selector: &metav1.LabelSelector{
+ MatchLabels: commonLabels,
+ },
+ Replicas: &replicas,
+ Template: v1.PodTemplateSpec{
+ ObjectMeta: metav1.ObjectMeta{
+ Labels: commonLabels,
+ },
+ Spec: v1.PodSpec{
+ ServiceAccountName: ResourceGatewayServiceAccountName,
+ Containers: []v1.Container{
+ {
+ Name: ResourceGatewayContainerName,
+ Image: imageName,
+ ImagePullPolicy: imagePullPolicy,
+ Env: envList,
+ Command: command,
+ SecurityContext: &v1.SecurityContext{
+ AllowPrivilegeEscalation: boolPtr(false),
+ Privileged: boolPtr(false),
+ },
+ Ports: []v1.ContainerPort{
+ {
+ Name: resourceGatewayPortName,
+ ContainerPort: resourceGatewayDeploymentPort,
+ Protocol: v1.ProtocolTCP,
+ },
+ },
+ LivenessProbe: &v1.Probe{
+ ProbeHandler: v1.ProbeHandler{
+ GRPC: &v1.GRPCAction{
+ Port: resourceGatewayServicePort,
+ },
+ },
+ InitialDelaySeconds: resourceGatewayLivenessProbeInitialDelaySeconds,
+ PeriodSeconds: resourceGatewayLivenessProbePeriodSeconds,
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ if cluster.Spec.ImagePullSecret != nil && *cluster.Spec.ImagePullSecret != "" {
+ deployment.Spec.Template.Spec.ImagePullSecrets = append(
+ []v1.LocalObjectReference{},
+ v1.LocalObjectReference{
+ Name: *cluster.Spec.ImagePullSecret,
+ },
+ )
+ }
+
+ if cluster.Spec.ResourceGateway.Resources != nil {
+ deployment.Spec.Template.Spec.Containers[0].Resources = *cluster.Spec.ResourceGateway.Resources
+ }
+
+ return deployment
+}
+
+// RegisterResourceGatewayComponent registers the ResourceGateway component
+func RegisterResourceGatewayComponent() {
+ Register(ResourceGatewayComponentName, &resourceGateway{})
+}
+
+func init() {
+ RegisterResourceGatewayComponent()
+}
diff --git a/drivers/storage/portworx/components_test.go b/drivers/storage/portworx/components_test.go
index 6a2de2ced1..766f74c4b3 100644
--- a/drivers/storage/portworx/components_test.go
+++ b/drivers/storage/portworx/components_test.go
@@ -3345,6 +3345,221 @@ func TestLighthouseSidecarsOverrideWithEnv(t *testing.T) {
require.Equal(t, "docker.io/test/stork-connector:t2", image)
}
+func setupTestEnv(t *testing.T) (portworx, client.Client) {
+ // Start test with newer version (1.25 beyond) of Kubernetes first, on which PodSecurityPolicy is no longer existing
+ mockCtrl := gomock.NewController(t)
+ versionClient := fakek8sclient.NewSimpleClientset()
+ versionClient.Discovery().(*fakediscovery.FakeDiscovery).FakedServerVersion = &version.Info{
+ GitVersion: "v1.25.0",
+ }
+ setUpMockCoreOps(mockCtrl, versionClient)
+ fakeExtClient := fakeextclient.NewSimpleClientset()
+ apiextensionsops.SetInstance(apiextensionsops.New(fakeExtClient))
+ reregisterComponents()
+ k8sClient := testutil.FakeK8sClient()
+ driver := portworx{}
+ err := driver.Init(k8sClient, runtime.NewScheme(), record.NewFakeRecorder(0))
+ require.NoError(t, err)
+ return driver, k8sClient
+}
+
+func createStorageClusterWithResourceGateway() *corev1.StorageCluster {
+ return &corev1.StorageCluster{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "px-cluster",
+ Namespace: "kube-test",
+ },
+ Spec: corev1.StorageClusterSpec{
+ Monitoring: &corev1.MonitoringSpec{Telemetry: &corev1.TelemetrySpec{}}, // required to run test
+ ResourceGateway: &corev1.ResourceGatewaySpec{
+ Enabled: true,
+ Image: "portworx/operator:1.1.1",
+ },
+ },
+ }
+}
+
+func TestResourceGatewayBasicInstall(t *testing.T) {
+ logrus.SetLevel(logrus.DebugLevel)
+ driver, k8sClient := setupTestEnv(t)
+
+ // create StorageCluster with ResourceGateway enabled
+ cluster := createStorageClusterWithResourceGateway()
+
+ err := driver.SetDefaultsOnStorageCluster(cluster)
+ require.NoError(t, err)
+
+ err = driver.PreInstall(cluster)
+ require.NoError(t, err)
+
+ // Resource Gateway ServiceAccount
+ serviceAccount := &v1.ServiceAccount{}
+ err = testutil.Get(k8sClient, serviceAccount, component.ResourceGatewayServiceAccountName, cluster.Namespace)
+ require.NoError(t, err)
+ require.Equal(t, component.ResourceGatewayServiceAccountName, serviceAccount.Name)
+ require.Equal(t, cluster.Namespace, serviceAccount.Namespace)
+ require.Len(t, serviceAccount.OwnerReferences, 1)
+ require.Equal(t, cluster.Name, serviceAccount.OwnerReferences[0].Name)
+
+ // Resource Gateway Role
+ expectedRole := testutil.GetExpectedRole(t, "resourceGatewayRole.yaml")
+ actualRole := &rbacv1.Role{}
+ err = testutil.Get(k8sClient, actualRole, component.ResourceGatewayRoleName, cluster.Namespace)
+ require.NoError(t, err)
+ require.Equal(t, expectedRole.Name, actualRole.Name)
+ require.Empty(t, actualRole.OwnerReferences)
+ require.ElementsMatch(t, expectedRole.Rules, actualRole.Rules)
+
+ // Resource Gateway RoleBinding
+ expectedRoleBinding := testutil.GetExpectedRoleBinding(t, "resourceGatewayRoleBinding.yaml")
+ actualRoleBinding := &rbacv1.RoleBinding{}
+ err = testutil.Get(k8sClient, actualRoleBinding, component.ResourceGatewayRoleBindingName, cluster.Namespace)
+ require.NoError(t, err)
+ require.Equal(t, expectedRoleBinding.Name, actualRoleBinding.Name)
+ require.Empty(t, actualRoleBinding.OwnerReferences)
+ require.ElementsMatch(t, expectedRoleBinding.Subjects, actualRoleBinding.Subjects)
+ require.Equal(t, expectedRoleBinding.RoleRef, actualRoleBinding.RoleRef)
+
+ // Resource Gateway Deployment
+ expectedDeployment := testutil.GetExpectedDeployment(t, "resourceGatewayDeployment.yaml")
+ resourceGatewayDeployment := &appsv1.Deployment{}
+ err = testutil.Get(k8sClient, resourceGatewayDeployment, component.ResourceGatewayDeploymentName, cluster.Namespace)
+ require.NoError(t, err)
+ resourceGatewayDeployment.ResourceVersion = ""
+ require.Equal(t, expectedDeployment, resourceGatewayDeployment)
+
+ // Resource Gateway Service
+ expectedService := testutil.GetExpectedService(t, "resourceGatewayService.yaml")
+ actualService := &v1.Service{}
+ err = testutil.Get(k8sClient, actualService, component.ResourceGatewayServiceName, cluster.Namespace)
+ require.NoError(t, err)
+ require.Equal(t, expectedService.Name, actualService.Name)
+ require.Equal(t, expectedService.Namespace, actualService.Namespace)
+ require.Len(t, actualService.OwnerReferences, 1)
+ require.Equal(t, cluster.Name, actualService.OwnerReferences[0].Name)
+ require.Equal(t, expectedService.Labels, actualService.Labels)
+ require.Equal(t, expectedService.Spec, actualService.Spec)
+}
+
+func TestResourceGatewayWithSecurity(t *testing.T) {
+ logrus.SetLevel(logrus.DebugLevel)
+ driver, k8sClient := setupTestEnv(t)
+
+ // create StorageCluster with ResourceGateway and Security enabled
+ cluster := createStorageClusterWithResourceGateway()
+ cluster.Spec.Security = &corev1.SecuritySpec{
+ Enabled: true,
+ Auth: &corev1.AuthSpec{
+ SelfSigned: &corev1.SelfSignedSpec{
+ Issuer: stringPtr(defaultSelfSignedIssuer),
+ TokenLifetime: stringPtr(defaultTokenLifetime),
+ SharedSecret: stringPtr(pxutil.SecurityPXSharedSecretSecretName),
+ },
+ },
+ }
+
+ err := driver.SetDefaultsOnStorageCluster(cluster)
+ require.NoError(t, err)
+
+ err = driver.PreInstall(cluster)
+ require.NoError(t, err)
+
+ // Resource Gateway Deployment
+ resourceGatewayDeployment := &appsv1.Deployment{}
+ err = testutil.Get(k8sClient, resourceGatewayDeployment, component.ResourceGatewayDeploymentName, cluster.Namespace)
+ require.NoError(t, err)
+
+ jwtIssuer := ""
+ var sharedSecretSet, appsSecretSet, systemSecretSet bool
+ actualContainer := resourceGatewayDeployment.Spec.Template.Spec.Containers[0]
+
+ for _, env := range actualContainer.Env {
+ if env.Name == pxutil.EnvKeyPortworxAuthJwtIssuer {
+ jwtIssuer = env.Value
+ }
+ if env.Name == pxutil.EnvKeyPortworxAuthJwtSharedSecret {
+ sharedSecretSet = true
+ }
+ if env.Name == pxutil.EnvKeyPortworxAuthSystemKey {
+ systemSecretSet = true
+ }
+ if env.Name == pxutil.EnvKeyPortworxAuthSystemAppsKey {
+ appsSecretSet = true
+ }
+ }
+ assert.Equal(t, defaultSelfSignedIssuer, jwtIssuer)
+ assert.True(t, sharedSecretSet)
+ assert.True(t, systemSecretSet)
+ assert.True(t, appsSecretSet)
+}
+
+func TestResourceGatewayWithCustomArguments(t *testing.T) {
+ logrus.SetLevel(logrus.DebugLevel)
+ driver, k8sClient := setupTestEnv(t)
+
+ // create StorageCluster with ResourceGateway with custom arguments provided
+ cluster := createStorageClusterWithResourceGateway()
+ cluster.Spec.ResourceGateway.Args = map[string]string{
+ "configMapUpdatePeriod": "10s",
+ "deadClientTimeout": "30s",
+ }
+
+ err := driver.SetDefaultsOnStorageCluster(cluster)
+ require.NoError(t, err)
+
+ err = driver.PreInstall(cluster)
+ require.NoError(t, err)
+
+ // Resource Gateway Deployment
+ resourceGatewayDeployment := &appsv1.Deployment{}
+ err = testutil.Get(k8sClient, resourceGatewayDeployment, component.ResourceGatewayDeploymentName, cluster.Namespace)
+ require.NoError(t, err)
+
+ expectedCommand := []string{
+ "/resource-gateway",
+ "--configMapUpdatePeriod=10s",
+ "--deadClientTimeout=30s",
+ "--namespace=kube-test",
+ "--serverHost=0.0.0.0",
+ "--serverPort=50051",
+ }
+
+ actualCommand := resourceGatewayDeployment.Spec.Template.Spec.Containers[0].Command
+ require.Equal(t, expectedCommand, actualCommand)
+}
+
+func TestResourceGatewayWithCustomResources(t *testing.T) {
+ logrus.SetLevel(logrus.DebugLevel)
+ driver, k8sClient := setupTestEnv(t)
+
+ // create StorageCluster with ResourceGateway with custom resources limits and requests
+ cluster := createStorageClusterWithResourceGateway()
+ cluster.Spec.ResourceGateway.Resources = &v1.ResourceRequirements{
+ Limits: v1.ResourceList{
+ v1.ResourceCPU: resource.MustParse("2"),
+ v1.ResourceMemory: resource.MustParse("2Gi"),
+ },
+ Requests: v1.ResourceList{
+ v1.ResourceCPU: resource.MustParse("1"),
+ v1.ResourceMemory: resource.MustParse("1Gi"),
+ },
+ }
+
+ err := driver.SetDefaultsOnStorageCluster(cluster)
+ require.NoError(t, err)
+
+ err = driver.PreInstall(cluster)
+ require.NoError(t, err)
+
+ // Resource Gateway Deployment
+ resourceGatewayDeployment := &appsv1.Deployment{}
+ err = testutil.Get(k8sClient, resourceGatewayDeployment, component.ResourceGatewayDeploymentName, cluster.Namespace)
+ require.NoError(t, err)
+
+ require.Equal(t, *cluster.Spec.ResourceGateway.Resources, resourceGatewayDeployment.Spec.Template.Spec.Containers[0].Resources)
+
+}
+
func TestAutopilotInstall(t *testing.T) {
// Start test with newer version (1.25 beyond) of Kubernetes first, on which PodSecurityPolicy is no longer existing
mockCtrl := gomock.NewController(t)
@@ -3496,7 +3711,6 @@ func TestAutopilotInstall(t *testing.T) {
require.Equal(t, expectedCR.Name, actualCR.Name)
require.Empty(t, actualCR.OwnerReferences)
require.ElementsMatch(t, expectedCR.Rules, actualCR.Rules)
-
}
func TestAutopilotWithoutImage(t *testing.T) {
@@ -17816,6 +18030,7 @@ func reregisterComponents() {
component.RegisterTelemetryComponent()
component.RegisterSCCComponent()
component.RegisterPortworxPluginComponent()
+ component.RegisterResourceGatewayComponent()
pxutil.SpecsBaseDir = func() string {
return "../../../bin/configs"
}
diff --git a/drivers/storage/portworx/testspec/prometheusRule.yaml b/drivers/storage/portworx/testspec/prometheusRule.yaml
index b956758cb2..24a1cd1b2e 100644
--- a/drivers/storage/portworx/testspec/prometheusRule.yaml
+++ b/drivers/storage/portworx/testspec/prometheusRule.yaml
@@ -205,7 +205,7 @@ spec:
expr: px_alerts_poolexpandsuccessful > 1
labels:
issue: Portworx pool expand successful.
- severity: warning
+ severity: info
resource_type: portworx-node
resource_name: "{{$labels.node}}"
scrape_target_type: portworx-node
@@ -231,7 +231,7 @@ spec:
expr: px_alerts_volumeresizesuccessful == 1
labels:
issue: Portworx volume resize successful.
- severity: warning
+ severity: info
resource_type: portworx-volume
resource_name: "{{$labels.volumeid}}"
scrape_target_type: portworx-node
diff --git a/drivers/storage/portworx/testspec/resourceGatewayDeployment.yaml b/drivers/storage/portworx/testspec/resourceGatewayDeployment.yaml
new file mode 100644
index 0000000000..599fed2e08
--- /dev/null
+++ b/drivers/storage/portworx/testspec/resourceGatewayDeployment.yaml
@@ -0,0 +1,48 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ labels:
+ name: resource-gateway
+ tier: control-plane
+ name: resource-gateway
+ namespace: kube-test
+ ownerReferences:
+ - apiVersion: core.libopenstorage.org/v1
+ blockOwnerDeletion: true
+ controller: true
+ kind: StorageCluster
+ name: px-cluster
+spec:
+ selector:
+ matchLabels:
+ name: resource-gateway
+ tier: control-plane
+ replicas: 1
+ template:
+ metadata:
+ labels:
+ name: resource-gateway
+ tier: control-plane
+ spec:
+ serviceAccountName: resource-gateway
+ containers:
+ - name: resource-gateway
+ image: docker.io/portworx/operator:1.1.1
+ imagePullPolicy: Always
+ command:
+ - /resource-gateway
+ - --namespace=kube-test
+ - --serverHost=0.0.0.0
+ - --serverPort=50051
+ securityContext:
+ allowPrivilegeEscalation: false
+ privileged: false
+ livenessProbe:
+ grpc:
+ port: 50051
+ initialDelaySeconds: 10
+ periodSeconds: 2
+ ports:
+ - containerPort: 50051
+ name: resource-gate
+ protocol: TCP
\ No newline at end of file
diff --git a/drivers/storage/portworx/testspec/resourceGatewayRole.yaml b/drivers/storage/portworx/testspec/resourceGatewayRole.yaml
new file mode 100644
index 0000000000..48b9ea883c
--- /dev/null
+++ b/drivers/storage/portworx/testspec/resourceGatewayRole.yaml
@@ -0,0 +1,9 @@
+kind: Role
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+ name: resource-gateway
+ namespace: kube-test
+rules:
+ - apiGroups: [""]
+ resources: ["configmaps"]
+ verbs: ["create", "get", "list", "patch", "update", "watch"]
diff --git a/drivers/storage/portworx/testspec/resourceGatewayRoleBinding.yaml b/drivers/storage/portworx/testspec/resourceGatewayRoleBinding.yaml
new file mode 100644
index 0000000000..3b81e8dfec
--- /dev/null
+++ b/drivers/storage/portworx/testspec/resourceGatewayRoleBinding.yaml
@@ -0,0 +1,12 @@
+kind: RoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+ name: resource-gateway
+ namespace: kube-test
+subjects:
+- kind: ServiceAccount
+ name: resource-gateway
+roleRef:
+ kind: Role
+ name: resource-gateway
+ apiGroup: rbac.authorization.k8s.io
diff --git a/drivers/storage/portworx/testspec/resourceGatewayService.yaml b/drivers/storage/portworx/testspec/resourceGatewayService.yaml
new file mode 100644
index 0000000000..e4003f0933
--- /dev/null
+++ b/drivers/storage/portworx/testspec/resourceGatewayService.yaml
@@ -0,0 +1,14 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: resource-gateway
+ namespace: kube-test
+spec:
+ type: ClusterIP
+ ports:
+ - name: resource-gate
+ port: 50051
+ targetPort: 50051
+ protocol: TCP
+ selector:
+ name: resource-gateway
diff --git a/drivers/storage/portworx/util/util.go b/drivers/storage/portworx/util/util.go
index 15cf2c9c09..c69e2f69aa 100644
--- a/drivers/storage/portworx/util/util.go
+++ b/drivers/storage/portworx/util/util.go
@@ -1533,3 +1533,75 @@ func ShouldUseClusterDomain(node *api.StorageNode) (bool, error) {
}
return true, nil
}
+
+func PopulateSecurityEnvironmentVariables(
+ cluster *corev1.StorageCluster,
+ envMap map[string]*v1.EnvVar,
+) {
+ if !SecurityEnabled(cluster) {
+ return
+ }
+ envMap[EnvKeyPortworxAuthJwtSharedSecret] = &v1.EnvVar{
+ Name: EnvKeyPortworxAuthJwtSharedSecret,
+ ValueFrom: &v1.EnvVarSource{
+ SecretKeyRef: &v1.SecretKeySelector{
+ LocalObjectReference: v1.LocalObjectReference{
+ Name: *cluster.Spec.Security.Auth.SelfSigned.SharedSecret,
+ },
+ Key: SecuritySharedSecretKey,
+ },
+ },
+ }
+ envMap[EnvKeyPortworxAuthSystemKey] = &v1.EnvVar{
+ Name: EnvKeyPortworxAuthSystemKey,
+ ValueFrom: &v1.EnvVarSource{
+ SecretKeyRef: &v1.SecretKeySelector{
+ LocalObjectReference: v1.LocalObjectReference{
+ Name: SecurityPXSystemSecretsSecretName,
+ },
+ Key: SecuritySystemSecretKey,
+ },
+ },
+ }
+ envMap[EnvKeyPortworxAuthJwtIssuer] = &v1.EnvVar{
+ Name: EnvKeyPortworxAuthJwtIssuer,
+ Value: *cluster.Spec.Security.Auth.SelfSigned.Issuer,
+ }
+ pxVersion := GetPortworxVersion(cluster)
+ storkVersion := GetStorkVersion(cluster)
+ pxAppsIssuerVersion, err := version.NewVersion("2.6.0")
+ if err != nil {
+ logrus.Errorf("failed to create PX version variable 2.6.0: %s", err.Error())
+ }
+ storkIssuerVersion, err := version.NewVersion("2.5.0")
+ if err != nil {
+ logrus.Errorf("failed to create Stork version variable 2.5.0: %s", err.Error())
+ }
+ // apps issuer was added in PX version 2.6.0
+ if pxVersion.GreaterThanOrEqual(pxAppsIssuerVersion) && storkVersion.GreaterThanOrEqual(storkIssuerVersion) {
+ envMap[EnvKeyPortworxAuthSystemAppsKey] = &v1.EnvVar{
+ Name: EnvKeyPortworxAuthSystemAppsKey,
+ ValueFrom: &v1.EnvVarSource{
+ SecretKeyRef: &v1.SecretKeySelector{
+ LocalObjectReference: v1.LocalObjectReference{
+ Name: SecurityPXSystemSecretsSecretName,
+ },
+ Key: SecurityAppsSecretKey,
+ },
+ },
+ }
+ } else {
+ // otherwise, use the stork issuer for pre-2.6 support
+ envMap[EnvKeyPortworxAuthStorkKey] = &v1.EnvVar{
+ Name: EnvKeyPortworxAuthStorkKey,
+ ValueFrom: &v1.EnvVarSource{
+ SecretKeyRef: &v1.SecretKeySelector{
+ LocalObjectReference: v1.LocalObjectReference{
+ Name: SecurityPXSystemSecretsSecretName,
+ },
+ Key: SecurityAppsSecretKey,
+ },
+ },
+ }
+ }
+}
diff --git a/go.mod b/go.mod
index 12de8bca47..0693f7daf8 100644
--- a/go.mod
+++ b/go.mod
@@ -7,10 +7,12 @@ require (
github.com/go-logr/logr v1.3.0
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/mock v1.6.0
+ github.com/golang/protobuf v1.5.4
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
- github.com/google/uuid v1.5.0
+ github.com/google/uuid v1.6.0
github.com/hashicorp/go-version v1.6.0
github.com/libopenstorage/cloudops v0.0.0-20221107233229-3fa4664e96b1
+ github.com/libopenstorage/grpc-framework v0.1.4
github.com/libopenstorage/openstorage v9.4.47+incompatible
github.com/openshift/api v0.0.0-20230503133300-8bbcb7ca7183
github.com/pborman/uuid v1.2.1
@@ -19,12 +21,13 @@ require (
github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.63.0
github.com/prometheus-operator/prometheus-operator/pkg/client v0.46.0
github.com/sirupsen/logrus v1.9.3
- github.com/stretchr/testify v1.8.4
+ github.com/stretchr/testify v1.9.0
github.com/urfave/cli v1.22.12
- golang.org/x/sys v0.16.0
- google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0
- google.golang.org/grpc v1.60.1
- google.golang.org/protobuf v1.32.0
+ golang.org/x/net v0.27.0
+ golang.org/x/sys v0.22.0
+ google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d
+ google.golang.org/grpc v1.65.0
+ google.golang.org/protobuf v1.34.2
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.27.1
k8s.io/apiextensions-apiserver v0.26.5
@@ -45,11 +48,12 @@ require (
require (
github.com/armon/go-metrics v0.4.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
- github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect
github.com/codeskyblue/go-sh v0.0.0-20170112005953-b097669b1569 // indirect
github.com/coreos/etcd v3.3.13+incompatible // indirect
github.com/coreos/go-oidc v2.2.1+incompatible // indirect
+ github.com/coreos/go-oidc/v3 v3.11.0 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
@@ -59,17 +63,19 @@ require (
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
+ github.com/go-jose/go-jose/v4 v4.0.3 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
- github.com/golang/protobuf v1.5.3 // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
+ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/hashicorp/consul/api v1.20.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.4.0 // indirect
@@ -85,7 +91,6 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
- github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/spdystream v0.2.0 // indirect
@@ -97,25 +102,24 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pquerna/cachecontrol v0.1.0 // indirect
- github.com/prometheus/client_golang v1.15.1 // indirect
- github.com/prometheus/client_model v0.4.0 // indirect
- github.com/prometheus/common v0.42.0 // indirect
- github.com/prometheus/procfs v0.9.0 // indirect
+ github.com/prometheus/client_golang v1.19.1 // indirect
+ github.com/prometheus/client_model v0.6.1 // indirect
+ github.com/prometheus/common v0.55.0 // indirect
+ github.com/prometheus/procfs v0.15.1 // indirect
+ github.com/rs/cors v1.11.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
- github.com/stretchr/objx v0.5.0 // indirect
- golang.org/x/crypto v0.18.0 // indirect
- golang.org/x/mod v0.12.0 // indirect
- golang.org/x/net v0.20.0 // indirect
- golang.org/x/oauth2 v0.16.0 // indirect
- golang.org/x/term v0.16.0 // indirect
- golang.org/x/text v0.14.0 // indirect
+ github.com/stretchr/objx v0.5.2 // indirect
+ golang.org/x/crypto v0.25.0 // indirect
+ golang.org/x/mod v0.17.0 // indirect
+ golang.org/x/oauth2 v0.21.0 // indirect
+ golang.org/x/term v0.22.0 // indirect
+ golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
- golang.org/x/tools v0.13.0 // indirect
+ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
- google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/go.sum b/go.sum
index 7f810ceff3..aece394f5a 100644
--- a/go.sum
+++ b/go.sum
@@ -62,6 +62,7 @@ cloud.google.com/go v0.110.7/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5x
cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk=
cloud.google.com/go v0.110.9/go.mod h1:rpxevX/0Lqvlbc88b7Sc1SPNdyK1riNBTUU6JXhYNpM=
cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic=
+cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM=
cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU=
cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
@@ -330,11 +331,14 @@ cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdi
cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78=
cloud.google.com/go/compute v1.23.2/go.mod h1:JJ0atRC0J/oWYiiVBmsSsrRnh92DhZPG4hFDcR04Rns=
+cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU=
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
+cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
+cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY=
cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck=
cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w=
@@ -1763,8 +1767,9 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6prSM8ap1UCpNKtgg=
github.com/chavacava/garif v0.0.0-20220630083739-93517212f375/go.mod h1:4m1Rv7xfuwWPNKXlThldNuJvutYM6J95wNuuVmn55To=
@@ -1995,6 +2000,8 @@ github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjs
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-oidc/v3 v3.5.0/go.mod h1:ecXRtV4romGPeO6ieExAsUK9cb/3fp9hXNz1tlv8PIM=
github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4=
+github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
+github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@@ -2340,6 +2347,8 @@ github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3I
github.com/go-ini/ini v1.66.6/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
+github.com/go-jose/go-jose/v4 v4.0.3 h1:o8aphO8Hv6RPmH+GfzVuyf7YXSBibp+8YyHdOoDESGo=
+github.com/go-jose/go-jose/v4 v4.0.3/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
@@ -2702,8 +2711,9 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@@ -2858,8 +2868,9 @@ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
github.com/googleapis/cloud-bigtable-clients-test v0.0.0-20221104150409-300c96f7b1f5/go.mod h1:Udm7et5Lt9Xtzd4n07/kKP80IdlR4zVDjtlUZEO2Dd8=
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
@@ -2983,6 +2994,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4Zs
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2/go.mod h1:7pdNwVWBBHGiCxa9lAszqCJMbfTISJ7oMftp8+UGV08=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/consul-template v0.25.0/go.mod h1:/vUsrJvDuuQHcxEw0zik+YXTS7ZKWZjQeaQhshBmfH0=
@@ -3500,6 +3513,8 @@ github.com/libopenstorage/external-storage v5.1.1-0.20190919185747-9394ee8dd536+
github.com/libopenstorage/gossip v0.0.0-20190507031959-c26073a01952/go.mod h1:TjXt2Iz2bTkpfc4Q6xN0ttiNipTVwEEYoZSMZHlfPek=
github.com/libopenstorage/gossip v0.0.0-20200808224301-d5287c7c8b24/go.mod h1:TjXt2Iz2bTkpfc4Q6xN0ttiNipTVwEEYoZSMZHlfPek=
github.com/libopenstorage/gossip v0.0.0-20220309192431-44c895e0923e/go.mod h1:TjXt2Iz2bTkpfc4Q6xN0ttiNipTVwEEYoZSMZHlfPek=
+github.com/libopenstorage/grpc-framework v0.1.4 h1:0IEQ73UE5kc3GukW0qwL0aCp7QKPEM+PJgau81NIGZo=
+github.com/libopenstorage/grpc-framework v0.1.4/go.mod h1:ifKXukHbd75tqTb4NXgnofNlyr5CHeIg/NI7bSHG9aU=
github.com/libopenstorage/openstorage v1.0.1-0.20240221210452-7757fdc2b8ff h1:9uognDSvafpcrNICT8I5OJRt9TlLeV61cF6nIl9KwBQ=
github.com/libopenstorage/openstorage v1.0.1-0.20240221210452-7757fdc2b8ff/go.mod h1:8E8ueY3NJV+tcOr1BQBvyNU9FRtDfcRGB7+trr07+rA=
github.com/libopenstorage/openstorage-sdk-clients v0.109.0/go.mod h1:vo0c/nLG2HIyQva4Avwx61U1kWcw4HGQh3sjzV2DEEs=
@@ -3642,7 +3657,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.0/go.mod h1:D8He9yQNgCq6Z5
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
-github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=
github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc=
@@ -4140,8 +4154,9 @@ github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrb
github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
-github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
+github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
+github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.0.0-20170216185247-6f3806018612/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
@@ -4151,8 +4166,9 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
-github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
+github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
@@ -4180,8 +4196,9 @@ github.com/prometheus/common v0.34.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRL
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/common v0.38.0/go.mod h1:MBXfmBQZrK5XpbCkjofnXs96LD2QQ7fEq4C0xjC/yec=
github.com/prometheus/common v0.41.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
-github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
+github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/common/assets v0.1.0/go.mod h1:D17UVUE12bHbim7HzwUvtqm6gwBEaDQ0F+hIGbFbccI=
github.com/prometheus/common/assets v0.2.0/go.mod h1:D17UVUE12bHbim7HzwUvtqm6gwBEaDQ0F+hIGbFbccI=
github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI=
@@ -4210,8 +4227,9 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
-github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
+github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
+github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/prometheus v0.0.0-20180315085919-58e2a31db8de/go.mod h1:oAIUtOny2rjMX0OWN5vPR5/q/twIROJvdqnQKDdil/s=
github.com/prometheus/prometheus v0.0.0-20190818123050-43acd0e2e93f/go.mod h1:rMTlmxGCvukf2KMu3fClMDKLLoJ5hl61MhcJ7xKakf0=
github.com/prometheus/prometheus v0.35.0/go.mod h1:7HaLx5kEPKJ0GDgbODG0fZgXbQ8K/XjZNJXQmbmgQlY=
@@ -4288,6 +4306,8 @@ github.com/rs/cors v1.6.1-0.20190116175910-76f58f330d76/go.mod h1:gFx+x8UowdsKA9
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
+github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
+github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
@@ -4481,8 +4501,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -4502,8 +4523,9 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs=
github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo=
@@ -5017,8 +5039,9 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
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/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
+golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
+golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
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-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -5097,8 +5120,9 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+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-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20171107184841-a337091b0525/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -5223,8 +5247,9 @@ golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
-golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
+golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
+golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/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-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -5271,8 +5296,9 @@ golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQ
golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
-golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
+golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
+golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -5515,8 +5541,9 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
+golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -5539,8 +5566,9 @@ 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.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
-golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
+golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
+golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/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.1.1-0.20171102192421-88f656faf3f3/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -5565,8 +5593,9 @@ golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
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.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+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-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -5713,7 +5742,6 @@ google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk=
google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@@ -5936,8 +5964,9 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405/go.
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4=
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI=
google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3/go.mod h1:k2dtGpRrbsSyKcNPKKI5sstZkrNCZwpU/ns96JoHbGg=
-google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 h1:s1w3X6gQxwrLEpxnLd/qXTVLgQE2yXwaOaoa6IlY/+o=
google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c=
+google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY=
+google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0=
google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA=
google.golang.org/genproto/googleapis/bytestream v0.0.0-20230807174057-1744710a1577/go.mod h1:NjCQG/D8JandXxM57PZbAJL1DCNL6EypA0vPPwfsc7c=
google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw=
@@ -5961,8 +5990,9 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231211222908-989df2bf70f3/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d h1:JU0iKnSg02Gmb5ZdV8nYsKEKsP6o/FGVWTrw4i1DA9A=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
@@ -5988,8 +6018,9 @@ google.golang.org/protobuf v1.29.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/alexcesaro/statsd.v2 v2.0.0/go.mod h1:i0ubccKGzBVNBpdGV5MocxyA/XlLUJzA7SLonnE4drU=
diff --git a/pkg/apis/core/v1/storagecluster.go b/pkg/apis/core/v1/storagecluster.go
index 9ac028bf00..8cc5486df5 100644
--- a/pkg/apis/core/v1/storagecluster.go
+++ b/pkg/apis/core/v1/storagecluster.go
@@ -103,6 +103,8 @@ type StorageClusterSpec struct {
// to the storage driver. The autopilot component could augment the storage
// driver to take intelligent actions based on the current state of the cluster.
Autopilot *AutopilotSpec `json:"autopilot,omitempty"`
+ // ResourceGateway limits Px access to external resources to prevent throttling
+ ResourceGateway *ResourceGatewaySpec `json:"resourceGateway,omitempty"`
// Monitoring contains monitoring configuration for the storage cluster.
Monitoring *MonitoringSpec `json:"monitoring,omitempty"`
// Security configurations for setting up an auth enabled or disabled cluster
@@ -486,6 +488,18 @@ type AutopilotSpec struct {
Resources *v1.ResourceRequirements `json:"resources,omitempty"`
}
+// ResourceGatewaySpec contains details of an ResourceGateway component
+type ResourceGatewaySpec struct {
+ // Enabled decides whether ResourceGateway needs to be enabled
+ Enabled bool `json:"enabled,omitempty"`
+ // Image is docker image of the ResourceGateway
+ Image string `json:"image,omitempty"`
+ // Args is a map of arguments given to ResourceGateway
+ Args map[string]string `json:"args,omitempty"`
+ // Resources requests and limits (like CPU and memory) for ResourceGateway pod
+ Resources *v1.ResourceRequirements `json:"resources,omitempty"`
+}
+
// DataProviderSpec contains the details for data providers for components like autopilot
type DataProviderSpec struct {
// Name is the unique name for the provider
@@ -622,6 +636,7 @@ type ComponentImages struct {
Pause string `json:"pause,omitempty"`
DynamicPlugin string `json:"dynamicPlugin,omitempty"`
DynamicPluginProxy string `json:"dynamicPluginProxy,omitempty"`
+ ResourceGateway string `json:"resourceGateway,omitempty"`
}
// Storage represents cluster storage details
diff --git a/pkg/apis/core/v1/zz_generated.deepcopy.go b/pkg/apis/core/v1/zz_generated.deepcopy.go
index d9dcb973e6..292e89552c 100644
--- a/pkg/apis/core/v1/zz_generated.deepcopy.go
+++ b/pkg/apis/core/v1/zz_generated.deepcopy.go
@@ -793,6 +793,34 @@ func (in *PrometheusSpec) DeepCopy() *PrometheusSpec {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ResourceGatewaySpec) DeepCopyInto(out *ResourceGatewaySpec) {
+ *out = *in
+ if in.Args != nil {
+ in, out := &in.Args, &out.Args
+ *out = make(map[string]string, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
+ if in.Resources != nil {
+ in, out := &in.Resources, &out.Resources
+ *out = new(corev1.ResourceRequirements)
+ (*in).DeepCopyInto(*out)
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceGatewaySpec.
+func (in *ResourceGatewaySpec) DeepCopy() *ResourceGatewaySpec {
+ if in == nil {
+ return nil
+ }
+ out := new(ResourceGatewaySpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RollingUpdateStorageCluster) DeepCopyInto(out *RollingUpdateStorageCluster) {
*out = *in
@@ -1036,6 +1064,11 @@ func (in *StorageClusterSpec) DeepCopyInto(out *StorageClusterSpec) {
*out = new(AutopilotSpec)
(*in).DeepCopyInto(*out)
}
+ if in.ResourceGateway != nil {
+ in, out := &in.ResourceGateway, &out.ResourceGateway
+ *out = new(ResourceGatewaySpec)
+ (*in).DeepCopyInto(*out)
+ }
if in.Monitoring != nil {
in, out := &in.Monitoring, &out.Monitoring
*out = new(MonitoringSpec)
diff --git a/pkg/constants/metadata.go b/pkg/constants/metadata.go
index e00083086a..3f2d2cdf87 100644
--- a/pkg/constants/metadata.go
+++ b/pkg/constants/metadata.go
@@ -50,8 +50,10 @@ const (
// OperatorLabelManagedByKey is a label key that is added to any object that is
// managed the Portworx operator.
OperatorLabelManagedByKey = OperatorPrefix + "/managed-by"
- // OperatorLabelManagedByValue indicates that the object is managed by portworx.
- OperatorLabelManagedByValue = "portworx"
+ // OperatorLabelManagedByValuePortworx indicates that the object is managed by portworx.
+ OperatorLabelManagedByValuePortworx = "portworx"
+ // OperatorLabelManagedByValueResourceGateway indicates that the object is managed by Px Resource Gateway.
+ OperatorLabelManagedByValueResourceGateway = "resource-gateway"
)
const (
diff --git a/pkg/controller/storagecluster/controller_test.go b/pkg/controller/storagecluster/controller_test.go
index 8dfc2b381a..6aa922eb0e 100644
--- a/pkg/controller/storagecluster/controller_test.go
+++ b/pkg/controller/storagecluster/controller_test.go
@@ -1829,7 +1829,7 @@ func TestStoragePodGetsScheduled(t *testing.T) {
Labels: map[string]string{
constants.LabelKeyClusterName: cluster.Name,
constants.LabelKeyDriverName: driverName,
- constants.OperatorLabelManagedByKey: constants.OperatorLabelManagedByValue,
+ constants.OperatorLabelManagedByKey: constants.OperatorLabelManagedByValuePortworx,
},
Annotations: make(map[string]string),
},
@@ -1947,7 +1947,7 @@ func TestStoragePodGetsScheduledK8s1_24(t *testing.T) {
Labels: map[string]string{
constants.LabelKeyClusterName: cluster.Name,
constants.LabelKeyDriverName: driverName,
- constants.OperatorLabelManagedByKey: constants.OperatorLabelManagedByValue,
+ constants.OperatorLabelManagedByKey: constants.OperatorLabelManagedByValuePortworx,
},
Annotations: make(map[string]string),
},
@@ -2338,7 +2338,7 @@ func TestStoragePodGetsScheduledWithCustomNodeSpecs(t *testing.T) {
Labels: map[string]string{
constants.LabelKeyClusterName: cluster.Name,
constants.LabelKeyDriverName: driverName,
- constants.OperatorLabelManagedByKey: constants.OperatorLabelManagedByValue,
+ constants.OperatorLabelManagedByKey: constants.OperatorLabelManagedByValuePortworx,
},
},
Spec: expectedPodSpec,
diff --git a/pkg/controller/storagecluster/kubevirt.go b/pkg/controller/storagecluster/kubevirt.go
index c4a09b3f92..15df66a628 100644
--- a/pkg/controller/storagecluster/kubevirt.go
+++ b/pkg/controller/storagecluster/kubevirt.go
@@ -148,7 +148,7 @@ OUTER:
}
// All checks passed. Start the live-migration.
labels := map[string]string{
- constants.OperatorLabelManagedByKey: constants.OperatorLabelManagedByValue,
+ constants.OperatorLabelManagedByKey: constants.OperatorLabelManagedByValuePortworx,
}
annotations := map[string]string{
constants.AnnotationControllerRevisionHashKey: controllerRevisionHash,
diff --git a/pkg/controller/storagecluster/kubevirt_test.go b/pkg/controller/storagecluster/kubevirt_test.go
index b904d73870..35ee10e9c4 100644
--- a/pkg/controller/storagecluster/kubevirt_test.go
+++ b/pkg/controller/storagecluster/kubevirt_test.go
@@ -202,7 +202,7 @@ func TestStartEvictingVMPods(t *testing.T) {
constants.AnnotationControllerRevisionHashKey: hash,
}
expectedLabels := map[string]string{
- constants.OperatorLabelManagedByKey: constants.OperatorLabelManagedByValue,
+ constants.OperatorLabelManagedByKey: constants.OperatorLabelManagedByValuePortworx,
}
// Test case: no migration exists
virtLauncherPod, vmi := getTestVirtLauncherPodAndVMI("virt-launcher-1", "node1")
@@ -308,7 +308,7 @@ func TestStartEvictingVMPods(t *testing.T) {
constants.AnnotationControllerRevisionHashKey: "different-hash",
},
Labels: map[string]string{
- constants.OperatorLabelManagedByKey: constants.OperatorLabelManagedByValue,
+ constants.OperatorLabelManagedByKey: constants.OperatorLabelManagedByValuePortworx,
},
SourceNode: virtLauncherPod.Spec.NodeName,
},
diff --git a/pkg/controller/storagecluster/storagecluster.go b/pkg/controller/storagecluster/storagecluster.go
index 5e3c8af315..d5f1ea03a6 100644
--- a/pkg/controller/storagecluster/storagecluster.go
+++ b/pkg/controller/storagecluster/storagecluster.go
@@ -1395,7 +1395,7 @@ func (c *Controller) CreatePodTemplate(
addPxServiceAccountTokenSecretIfNotExist(&podSpec)
podSpec.NodeName = node.Name
labels := c.StorageClusterSelectorLabels(cluster)
- labels[constants.OperatorLabelManagedByKey] = constants.OperatorLabelManagedByValue
+ labels[constants.OperatorLabelManagedByKey] = constants.OperatorLabelManagedByValuePortworx
newTemplate := v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Namespace: cluster.Namespace,
diff --git a/pkg/controller/storagenode/storagenode.go b/pkg/controller/storagenode/storagenode.go
index 31143bbc2a..6e9c560e05 100644
--- a/pkg/controller/storagenode/storagenode.go
+++ b/pkg/controller/storagenode/storagenode.go
@@ -3,11 +3,12 @@ package storagenode
import (
"context"
"fmt"
+ "strings"
+ "time"
+
"github.com/libopenstorage/openstorage/api"
pxutil "github.com/libopenstorage/operator/drivers/storage/portworx/util"
"google.golang.org/grpc"
- "strings"
- "time"
"github.com/hashicorp/go-version"
apiextensionsops "github.com/portworx/sched-ops/k8s/apiextensions"
@@ -379,11 +380,11 @@ func (c *Controller) syncStorage(
}
value, present := podCopy.Labels[constants.OperatorLabelManagedByKey]
- if !present || value != constants.OperatorLabelManagedByValue {
+ if !present || value != constants.OperatorLabelManagedByValuePortworx {
if podCopy.Labels == nil {
podCopy.Labels = make(map[string]string)
}
- podCopy.Labels[constants.OperatorLabelManagedByKey] = constants.OperatorLabelManagedByValue
+ podCopy.Labels[constants.OperatorLabelManagedByKey] = constants.OperatorLabelManagedByValuePortworx
updateNeeded = true
}
@@ -424,7 +425,7 @@ func (c *Controller) createKVDBPod(
func (c *Controller) kvdbPodLabels(cluster *corev1.StorageCluster) map[string]string {
podLabels := map[string]string{
- constants.OperatorLabelManagedByKey: constants.OperatorLabelManagedByValue,
+ constants.OperatorLabelManagedByKey: constants.OperatorLabelManagedByValuePortworx,
}
for k, v := range c.kvdbOldPodLabels(cluster) {
podLabels[k] = v
diff --git a/pkg/resource-gateway/priority_queue.go b/pkg/resource-gateway/priority_queue.go
new file mode 100644
index 0000000000..76700c8573
--- /dev/null
+++ b/pkg/resource-gateway/priority_queue.go
@@ -0,0 +1,141 @@
+// Implements a priority queue with priorities high, medium, and low defined in proto.
+// The queue supports the following operations: Enqueue, Dequeue, Front, and Remove.
+//
+// It is implemented with a separate list for each priority.
+// When a client is enqueued, it is added to the list corresponding to its priority.
+//
+// When the front of the queue is requested,
+// the queue will return the client at the front of the highest priority non-empty list.
+//
+// The queue is configured with a maximum size to prevent unbounded growth.
+// When the queue is full, Enqueue will return an error.
+package resource_gateway
+
+import (
+ "container/list"
+ "fmt"
+ "sync"
+
+ pb "github.com/libopenstorage/operator/proto"
+)
+
+var (
+ // priorityDescendingOrder is the order of priorities in descending order.
+ // It is used to iterate through the queues in order of priority.
+ priorityDescendingOrder = []pb.AccessPriority_Type{
+ pb.AccessPriority_HIGH,
+ pb.AccessPriority_MEDIUM,
+ pb.AccessPriority_LOW,
+ }
+)
+
+// PriorityQueue is an interface for a priority queue.
+type PriorityQueue interface {
+ // Enqueue adds a client to the queue with the given priority.
+ Enqueue(clientId string, priority pb.AccessPriority_Type) error
+ // Dequeue removes the client at the front of the queue with the given priority.
+ Dequeue(priority pb.AccessPriority_Type) error
+ // Front returns the client at the front of the non-empty queue with the highest priority.
+ Front() (clientId string, accessPriority pb.AccessPriority_Type)
+ // Remove removes the client from the priority queue.
+ Remove(clientId string) error
+}
+
+// priorityQueue is an implementation of the PriorityQueue interface.
+type priorityQueue struct {
+ // sync.Mutex protects Q operations from concurrent access
+ sync.Mutex
+ // queues for each priority
+ // uses list for O(1) implementation of Q operations
+ queues map[pb.AccessPriority_Type]*list.List
+ // maxQueueSize is the maximum number of elements across all queues
+ maxQueueSize uint
+}
+
+// NewPriorityQueue creates a new priority queue with the given maximum size
+// and returns a PriorityQueue interface.
+func NewPriorityQueue(maxQueueSize uint) PriorityQueue {
+ queues := make(map[pb.AccessPriority_Type]*list.List)
+ for _, priority := range priorityDescendingOrder {
+ queues[priority] = list.New()
+ }
+ return &priorityQueue{
+ queues: queues,
+ maxQueueSize: maxQueueSize,
+ }
+}
+
+// Enqueue adds a client to the queue with the given priority.
+//
+// It returns an error if the queue is full that is
+// total number of elements is equal to maxQueueSize.
+func (pq *priorityQueue) Enqueue(clientId string, priority pb.AccessPriority_Type) error {
+ pq.Lock()
+ defer pq.Unlock()
+
+ if pq.len() == pq.maxQueueSize {
+ return fmt.Errorf("queue is full")
+ }
+
+ pq.queues[priority].PushBack(clientId)
+ return nil
+}
+
+// Dequeue removes the client at the front of the queue with the given priority.
+func (pq *priorityQueue) Dequeue(priority pb.AccessPriority_Type) error {
+ pq.Lock()
+ defer pq.Unlock()
+
+ if pq.queues[priority].Front() == nil {
+ return fmt.Errorf("queue %v is empty", priority)
+ }
+
+ pq.queues[priority].Remove(pq.queues[priority].Front())
+ return nil
+}
+
+// Front returns the client at the front of the priority queue.
+//
+// It iterates through the queues in descending order of priority
+// and returns the front of the first non-empty queue.
+func (pq *priorityQueue) Front() (string, pb.AccessPriority_Type) {
+ pq.Lock()
+ defer pq.Unlock()
+
+ for _, priority := range priorityDescendingOrder {
+ if pq.queues[priority].Front() != nil {
+ return pq.queues[priority].Front().Value.(string), priority
+ }
+ }
+ return "", pb.AccessPriority_LOW
+}
+
+// Remove removes the client from the priority queue.
+//
+// It iterates through all queues to find the client and removes it.
+func (pq *priorityQueue) Remove(clientId string) error {
+ pq.Lock()
+ defer pq.Unlock()
+
+ for _, q := range pq.queues {
+ for e := q.Front(); e != nil; e = e.Next() {
+ if e.Value.(string) == clientId {
+ q.Remove(e)
+ return nil
+ }
+ }
+ }
+ return fmt.Errorf("client %v not found in the queue", clientId)
+}
+
+// len returns the total number of elements across all queues
+//
+// It iterates through all queues and returns the sum of their lengths.
+// It should be called with the mutex locked.
+func (pq *priorityQueue) len() uint {
+ var length uint
+ for _, q := range pq.queues {
+ length += uint(q.Len())
+ }
+ return length
+}
diff --git a/pkg/resource-gateway/resource_gateway_server.go b/pkg/resource-gateway/resource_gateway_server.go
new file mode 100644
index 0000000000..f4bf318d36
--- /dev/null
+++ b/pkg/resource-gateway/resource_gateway_server.go
@@ -0,0 +1,116 @@
+package resource_gateway
+
+import (
+ "fmt"
+ "os"
+ "time"
+
+ "github.com/libopenstorage/grpc-framework/pkg/util"
+ grpcFramework "github.com/libopenstorage/grpc-framework/server"
+ "github.com/libopenstorage/openstorage/pkg/sched"
+ pb "github.com/libopenstorage/operator/proto"
+ "github.com/portworx/sched-ops/k8s/core"
+ "github.com/sirupsen/logrus"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/health"
+ healthpb "google.golang.org/grpc/health/grpc_health_v1"
+)
+
+const (
+ // defaultServerName is the default server name for resource-gateway
+ defaultServerName = "resource-gateway"
+ // defaultServerHost is the default server host for resource-gateway
+ defaultServerHost = "127.0.0.1"
+ // defaultServerPort is the default server port for resource-gateway
+ defaultServerPort = "50051"
+)
+
+// ResourceGatewayServer is the main struct for resource-gateway gRPC server
+//
+// It contains the gRPC server instance and various component servers like semaphore and health check
+type ResourceGatewayServer struct {
+ server *grpcFramework.Server
+
+ semaphoreServer *semaphoreServer
+ healthCheckServer *health.Server
+}
+
+// NewResourceGatewayServer creates a new resource-gateway gRPC server instance
+func NewResourceGatewayServer(
+ resourceGatewayConfig *grpcFramework.ServerConfig,
+ semaphoreConfig *SemaphoreConfig,
+) *ResourceGatewayServer {
+ // create component servers
+ healthCheckServer := health.NewServer()
+ semaphoreServer := NewSemaphoreServer(semaphoreConfig)
+
+ // register the component servers with the gRPC server config
+ resourceGatewayConfig.
+ RegisterGrpcServers(func(gs *grpc.Server) {
+ pb.RegisterSemaphoreServiceServer(gs, semaphoreServer)
+ }).
+ RegisterGrpcServers(func(gs *grpc.Server) {
+ healthpb.RegisterHealthServer(gs, healthCheckServer)
+ }).
+ WithDefaultGenericRoleManager()
+
+ // create the gRPC server instance
+ resourceGatewayGRPCServer, err := grpcFramework.New(resourceGatewayConfig)
+ if err != nil {
+ fmt.Printf("Unable to create server: %v", err)
+ os.Exit(1)
+ }
+
+ resourceGatewayServer := &ResourceGatewayServer{
+ server: resourceGatewayGRPCServer,
+ semaphoreServer: semaphoreServer,
+ healthCheckServer: healthCheckServer,
+ }
+ return resourceGatewayServer
+}
+
+func NewResourceGatewayServerConfig() *grpcFramework.ServerConfig {
+ defaultServerAddress := fmt.Sprintf("%s:%s", defaultServerHost, defaultServerPort)
+ resourceGatewayServerConfig := &grpcFramework.ServerConfig{
+ Name: defaultServerHost,
+ Address: defaultServerAddress,
+ AuditOutput: os.Stdout,
+ AccessOutput: os.Stdout,
+ }
+ return resourceGatewayServerConfig
+}
+
+// SetupSigIntHandler sets up a signal handler to stop the server
+func (r *ResourceGatewayServer) SetupSigIntHandler() error {
+ signal_handler := util.NewSigIntManager(func() {
+ r.server.Stop()
+ r.healthCheckServer.SetServingStatus("", healthpb.HealthCheckResponse_NOT_SERVING)
+ r.server.Address()
+ os.Exit(0)
+ })
+
+ return signal_handler.Start()
+}
+
+// Start starts the resource-gateway gRPC server
+func (r *ResourceGatewayServer) Start() error {
+ // Initialize the k8s client
+ _, err := core.Instance().GetVersion()
+ if err != nil {
+ return fmt.Errorf("Unable to get k8s version: %v", err)
+ }
+
+ if sched.Instance() == nil {
+ sched.Init(time.Second)
+ }
+
+ err = r.server.Start()
+ if err != nil {
+ return err
+ }
+ r.healthCheckServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
+
+ // Wait. The signal handler will exit cleanly
+ logrus.Info("Px gRPC server running")
+ select {}
+}
diff --git a/pkg/resource-gateway/semaphore.go b/pkg/resource-gateway/semaphore.go
new file mode 100644
index 0000000000..0824e8e2ac
--- /dev/null
+++ b/pkg/resource-gateway/semaphore.go
@@ -0,0 +1,399 @@
+// Package resource_gateway provides a semaphore implementation backed by priority queue
+package resource_gateway
+
+import (
+ "sync"
+ "time"
+
+ "github.com/libopenstorage/openstorage/pkg/sched"
+ pb "github.com/libopenstorage/operator/proto"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ nullPermit uint32 = 0
+)
+
+var (
+ testOverride = false
+)
+
+type SemaphorePriorityQueue interface {
+ Acquire(clientId string, priority pb.AccessPriority_Type) (pb.AccessStatus_Type, error)
+ Release(clientId string) error
+ Heartbeat(clientId string) pb.AccessStatus_Type
+
+ Update(config *SemaphoreConfig)
+}
+
+type activeLeasesMap map[string]int64
+
+// semaphorePriorityQueue implements the SemaphorePriorityQueue interface
+type semaphorePriorityQueue struct {
+ // configurations
+ config *SemaphoreConfig
+
+ // thread safety
+ mutex sync.Mutex
+
+ // internal state
+ priorityQueue PriorityQueue
+ activeLeases activeLeasesMap
+ availablePermits uint
+ heartbeats map[string]int64
+
+ // persistent state
+ configMap *configMap
+ configMapUpdateDone chan struct{}
+}
+
+// NewSemaphorePriorityQueue creates a new or loads an existing semaphore priority queue
+// if the backing configmap does not exist then it creates a new one
+func NewSemaphorePriorityQueueWithConfig(config *SemaphoreConfig) *semaphorePriorityQueue {
+ // create or update the configmap
+ configMap, err := createOrUpdateConfigMap(config)
+ if err != nil {
+ panic(err)
+ }
+
+ semPQ := &semaphorePriorityQueue{
+ priorityQueue: NewPriorityQueue(config.MaxQueueSize),
+ heartbeats: map[string]int64{},
+ configMapUpdateDone: make(chan struct{}),
+ config: config,
+ configMap: configMap,
+ }
+
+ semPQ.populateSemaphoreConfig()
+ semPQ.initPermitsAndLeases()
+ // TODO cancel and restart background tasks
+ semPQ.startBackgroundTasks()
+
+ return semPQ
+}
+
+// NewSemaphorePriorityQueue creates a new or loads an existing semaphore priority queue
+// if the backing configmap does not exist then it creates a new one
+func NewSemaphorePriorityQueueWithConfigMap(config *SemaphoreConfig) *semaphorePriorityQueue {
+ // create or update the configmap
+ configMap, err := getConfigMap(config)
+ if err != nil {
+ panic(err)
+ }
+
+ semPQ := &semaphorePriorityQueue{
+ priorityQueue: NewPriorityQueue(config.MaxQueueSize),
+ heartbeats: map[string]int64{},
+ configMapUpdateDone: make(chan struct{}),
+ config: config,
+ configMap: configMap,
+ }
+
+ semPQ.populateSemaphoreConfig()
+ semPQ.initPermitsAndLeases()
+ semPQ.startBackgroundTasks()
+
+ return semPQ
+}
+
+func (s *semaphorePriorityQueue) Update(config *SemaphoreConfig) {
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+
+ logrus.Infof("Update semaphore config: %v", config)
+ s.config = config
+ s.configMap.update(config)
+
+ // TODO: update the internal state based on the new config
+ s.populateSemaphoreConfig()
+ s.initPermitsAndLeases()
+}
+
+// populateSemaphoreConfig fetches the latest copy of configmap from kubernetes
+// and updates the values in the semaphore config
+func (s *semaphorePriorityQueue) populateSemaphoreConfig() {
+ s.config.ConfigMapName = s.configMap.Name()
+ s.config.ConfigMapNamespace = s.configMap.Namespace()
+ s.config.ConfigMapLabels = s.configMap.Labels()
+
+ nPermits, err := s.configMap.NPermits()
+ if err != nil {
+ panic(err)
+ }
+ s.config.NPermits = nPermits
+
+ leaseTimeout, err := s.configMap.LeaseTimeout()
+ if err != nil {
+ panic(err)
+ }
+ s.config.LeaseTimeout = leaseTimeout
+}
+
+// populate structures for active leases and available permits
+func (s *semaphorePriorityQueue) initPermitsAndLeases() {
+ activeLeases, err := s.configMap.ActiveLeases()
+ if err != nil {
+ panic(err)
+ }
+ s.activeLeases = activeLeases
+ s.availablePermits = uint(s.config.NPermits) - uint(len(activeLeases))
+}
+
+// startBackgroundTasks starts background workers for updating the configmap,
+// cleaning up dead clients and reclaiming expired leases
+func (s *semaphorePriorityQueue) startBackgroundTasks() {
+ // TODO: cancel and restart background tasks
+ bgTasks := []struct {
+ name string
+ f func()
+ interval time.Duration
+ }{
+ {"updateConfigMap", s.updateConfigMap, s.config.ConfigMapUpdatePeriod},
+ {"cleanupDeadClients", s.cleanupDeadClients, s.config.DeadClientTimeout / 2},
+ {"reclaimExpiredLeases", s.reclaimExpiredLeases, s.config.LeaseTimeout / 2},
+ }
+
+ for _, bgTask := range bgTasks {
+ f := bgTask.f
+ intv := bgTask.interval
+ if testOverride {
+ intv = time.Second
+ }
+ taskID, err := sched.Instance().Schedule(
+ func(_ sched.Interval) { f() },
+ sched.Periodic(intv),
+ time.Now(), false,
+ )
+ if err != nil {
+ panic(err)
+ }
+ logrus.Debugf("Scheduled task %v with interval %v and Id %v",
+ bgTask.name, bgTask.interval, taskID)
+ }
+}
+
+// Acquire acquires a lease for the client with the given priority
+func (s *semaphorePriorityQueue) Acquire(clientId string, priority pb.AccessPriority_Type) (pb.AccessStatus_Type, error) {
+ s.mutex.Lock()
+ updateDone := false
+ defer func() {
+ s.mutex.Unlock()
+ if updateDone {
+ <-s.configMapUpdateDone
+ }
+ }()
+ logrus.Debugf("Received Acquire request for client %v", clientId)
+
+ // check if the client already has a lease, if yes return
+ if s.hasActiveLease(clientId) {
+ logrus.Debugf("Already acquired lease for client %v", clientId)
+ return pb.AccessStatus_LEASED, nil
+ }
+
+ // check if the client is already in the queue, if not add it
+ // no heartbeat = new client
+ if _, isQueued := s.heartbeats[clientId]; !isQueued {
+ logrus.Debugf("Enqueueing client %v with priority %v", clientId, priority)
+ err := s.priorityQueue.Enqueue(clientId, priority)
+ if err != nil {
+ return pb.AccessStatus_TYPE_UNSPECIFIED, err
+ }
+ }
+
+ // update the heartbeat of the client
+ s.heartbeats[clientId] = time.Now().Unix()
+
+ // try to acquire the lease
+ if hasAcquiredLease := s.tryAcquire(clientId); hasAcquiredLease {
+ logrus.Infof("Acquired lease for client %v", clientId)
+ updateDone = true
+ return pb.AccessStatus_LEASED, nil
+ }
+
+ logrus.Debugf("Client %v is waiting in queue", clientId)
+ return pb.AccessStatus_QUEUED, nil
+}
+
+// removeActiveLease removes the client from the heartbeats and activeLeases map
+// and adds the respective permit back to the available permits list
+//
+// removeActiveLease should be called with mutex locked
+func (s *semaphorePriorityQueue) removeActiveLease(clientId string) {
+ delete(s.heartbeats, clientId)
+ delete(s.activeLeases, clientId)
+ s.availablePermits++
+}
+
+// Release releases the lease held by the client
+func (s *semaphorePriorityQueue) Release(clientId string) error {
+ s.mutex.Lock()
+ updateDone := false
+ defer func() {
+ s.mutex.Unlock()
+ if updateDone {
+ <-s.configMapUpdateDone
+ }
+ }()
+ logrus.Debugf("Received Release request for client %v", clientId)
+
+ // check if the client has an active lease, if not return
+ if !s.hasActiveLease(clientId) {
+ logrus.Warnf("Did NOT find an active lease for the client %v!", clientId)
+ return nil
+ }
+
+ s.removeActiveLease(clientId)
+ updateDone = true
+
+ return nil
+}
+
+// Heartbeat updates the heartbeat of the client and returns the status of the client
+func (s *semaphorePriorityQueue) Heartbeat(clientId string) pb.AccessStatus_Type {
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+
+ logrus.Debugf("Received Heartbeat request for client %v", clientId)
+ _, exists := s.heartbeats[clientId]
+ if !exists {
+ return pb.AccessStatus_TYPE_UNSPECIFIED
+ }
+ s.heartbeats[clientId] = time.Now().Unix()
+
+ if s.hasActiveLease(clientId) {
+ return pb.AccessStatus_LEASED
+ }
+ return pb.AccessStatus_QUEUED
+}
+
+// tryAcquire checks if a given client is at the front of the queue and if there is an available permit,
+// if true, it assigns the permit to the client
+//
+// tryAcquire should be called with mutex locked
+func (s *semaphorePriorityQueue) tryAcquire(clientId string) bool {
+ // check if the client is at the front of the queue
+ nextResouceId, priority := s.priorityQueue.Front()
+ if nextResouceId == "" {
+ panic("Queue is empty")
+ }
+ if nextResouceId != clientId {
+ return false
+ }
+ logrus.Debugf("Next resource in queue: %v", nextResouceId)
+
+ // check if any permit is available
+ if s.availablePermits == 0 {
+ return false
+ }
+ s.availablePermits--
+
+ // remove the client from the queue and assign it the permit
+ err := s.priorityQueue.Dequeue(priority)
+ if err != nil {
+ panic(err)
+ }
+ s.activeLeases[clientId] = time.Now().Unix()
+
+ return true
+}
+
+// updateConfigMap updates the configmap with the active lease data in memory
+// and notifies all the goroutines waiting on the configMapUpdateDone channel
+//
+// updateConfigMap is scheduled as a background task
+func (s *semaphorePriorityQueue) updateConfigMap() {
+
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+ logrus.Debugf("Running updateConfigMap background task")
+
+ err := s.configMap.UpdateLeases(s.activeLeases)
+ if err != nil {
+ logrus.Fatalf("Failed to update configmap: %v", err)
+ }
+
+ // notify all the goroutines waiting that the update is done
+ close(s.configMapUpdateDone)
+ // reset the channel for the next batch of waiters
+ s.configMapUpdateDone = make(chan struct{})
+}
+
+// cleanupDeadClients removes the dead clients from the priority queue
+// and releases the leases held by them
+//
+// A client is considered dead when it has not sent a heartbeat
+// for more than DeadClientTimeout duration
+//
+// cleanupDeadClients is scheduled as a background task
+func (s *semaphorePriorityQueue) cleanupDeadClients() {
+ deadClients := []string{}
+
+ s.mutex.Lock()
+ defer func() {
+ s.mutex.Unlock()
+ if len(deadClients) != 0 {
+ <-s.configMapUpdateDone // wait for the next update to complete
+ }
+ }()
+ logrus.Debugf("Running cleanupDeadClients background task")
+
+ for clientId, lastHeartbeat := range s.heartbeats {
+ if time.Since(time.Unix(lastHeartbeat, 0)) > s.config.DeadClientTimeout {
+ deadClients = append(deadClients, clientId)
+ }
+ }
+ if len(deadClients) == 0 {
+ return
+ }
+
+ logrus.Warnf("Cleaning up dead clients: %v", deadClients)
+ for _, clientId := range deadClients {
+ if s.hasActiveLease(clientId) {
+ s.removeActiveLease(clientId)
+ } else {
+ delete(s.heartbeats, clientId)
+ err := s.priorityQueue.Remove(clientId)
+ if err != nil {
+ logrus.Errorf("Failed to remove dead client %v from the queue: %v", clientId, err)
+ }
+ }
+ }
+}
+
+// reclaimExpiredLeases releases the leases that have been held
+// for more than LeaseTimeout duration
+//
+// reclaimExpiredLeases is scheduled as a background task
+func (s *semaphorePriorityQueue) reclaimExpiredLeases() {
+ expiredLeases := []string{}
+
+ s.mutex.Lock()
+ defer func() {
+ s.mutex.Unlock()
+ if len(expiredLeases) != 0 {
+ <-s.configMapUpdateDone // wait for the next update to complete
+ }
+ }()
+
+ logrus.Debugf("Running reclaimExpiredLeases background task")
+
+ for clientId, leaseTimeAcquired := range s.activeLeases {
+ timeSinceAcquire := time.Since(time.Unix(leaseTimeAcquired, 0))
+ if timeSinceAcquire > s.config.LeaseTimeout {
+ expiredLeases = append(expiredLeases, clientId)
+ }
+ }
+ if len(expiredLeases) == 0 {
+ return
+ }
+
+ logrus.Warnf("Reclaiming expired leases: %v", expiredLeases)
+ for _, clientId := range expiredLeases {
+ s.removeActiveLease(clientId)
+ }
+}
+
+func (s *semaphorePriorityQueue) hasActiveLease(clientId string) bool {
+ _, hasLease := s.activeLeases[clientId]
+ return hasLease
+}
diff --git a/pkg/resource-gateway/semaphore_config.go b/pkg/resource-gateway/semaphore_config.go
new file mode 100644
index 0000000000..d4897da3bf
--- /dev/null
+++ b/pkg/resource-gateway/semaphore_config.go
@@ -0,0 +1,63 @@
+package resource_gateway
+
+import "time"
+
+const (
+ // ResourceGatewayStr is the common string for resource-gateway components
+ resourceGatewayStr = "resource-gateway"
+ // defaultNamespace is the default namespace to create semaphore configmap
+ defaultNamespace = "kube-system"
+ // defaultConfigMapName is the default name for semaphore configmap
+ defaultConfigMapName = resourceGatewayStr
+ // defaultConfigMapLabels are the default labels applied to semaphore configmap
+ defaultConfigMapLabels = "name=resource-gateway"
+ // defaultConfigMapUpdatePeriod is the default time period between configmap updates
+ defaultConfigMapUpdatePeriod = 1 * time.Second
+ // defaultLeaseTimeout is the default time period after which a lease will force expire
+ defaultLeaseTimeout = 30 * time.Second
+ // defaultDeadClientTimeout is the default time period after which a node
+ // is considered dead if no heartbeats were received in this duration
+ defaultDeadClientTimeout = 10 * time.Second
+ // defaultMaxQueueSize is the default max queue size for semaphore server
+ defaultMaxQueueSize = 5000
+)
+
+type SemaphoreConfig struct {
+ NPermits uint32
+ ConfigMapName string
+ ConfigMapNamespace string
+ ConfigMapLabels map[string]string
+ ConfigMapUpdatePeriod time.Duration
+ LeaseTimeout time.Duration
+ DeadClientTimeout time.Duration
+ MaxQueueSize uint
+}
+
+func NewSemaphoreConfig() *SemaphoreConfig {
+ return &SemaphoreConfig{
+ ConfigMapNamespace: defaultNamespace,
+ ConfigMapName: defaultConfigMapName,
+ ConfigMapLabels: map[string]string{"name": resourceGatewayStr},
+ ConfigMapUpdatePeriod: defaultConfigMapUpdatePeriod,
+ LeaseTimeout: defaultLeaseTimeout,
+ DeadClientTimeout: defaultDeadClientTimeout,
+ MaxQueueSize: defaultMaxQueueSize,
+ }
+}
+
+func copySemaphoreConfig(semaphoreConfig *SemaphoreConfig) *SemaphoreConfig {
+ configMapLabels := make(map[string]string)
+ for k, v := range semaphoreConfig.ConfigMapLabels {
+ configMapLabels[k] = v
+ }
+ return &SemaphoreConfig{
+ NPermits: semaphoreConfig.NPermits,
+ ConfigMapName: semaphoreConfig.ConfigMapName,
+ ConfigMapNamespace: semaphoreConfig.ConfigMapNamespace,
+ ConfigMapLabels: configMapLabels,
+ ConfigMapUpdatePeriod: semaphoreConfig.ConfigMapUpdatePeriod,
+ LeaseTimeout: semaphoreConfig.LeaseTimeout,
+ DeadClientTimeout: semaphoreConfig.DeadClientTimeout,
+ MaxQueueSize: semaphoreConfig.MaxQueueSize,
+ }
+}
diff --git a/pkg/resource-gateway/semaphore_configmap.go b/pkg/resource-gateway/semaphore_configmap.go
new file mode 100644
index 0000000000..da8f639c0b
--- /dev/null
+++ b/pkg/resource-gateway/semaphore_configmap.go
@@ -0,0 +1,249 @@
+package resource_gateway
+
+import (
+ "encoding/json"
+ "fmt"
+ "strconv"
+ "time"
+
+ "github.com/portworx/sched-ops/k8s/core"
+ "github.com/sirupsen/logrus"
+ corev1 "k8s.io/api/core/v1"
+ k8s_errors "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+const (
+ activeLeasesKey = "activeLeases"
+ nPermitsKey = "nPermits"
+ configMapUpdatePeriodKey = "configMapUpdatePeriod"
+ leaseTimeoutKey = "leaseTimeout"
+ deadClientTimeoutKey = "deadClientTimeout"
+ maxQueueSizeKey = "maxQueueSize"
+)
+
+type configMap struct {
+ // activeLeases is the cache for fetching active leases persisted in configmap
+ activeLeases activeLeasesMap
+ cm *corev1.ConfigMap
+}
+
+func createOrUpdateConfigMap(config *SemaphoreConfig) (*configMap, error) {
+ remoteConfigMap, err := updateConfigMap(config)
+ if err != nil && !k8s_errors.IsNotFound(err) {
+ return nil, err
+ }
+
+ if k8s_errors.IsNotFound(err) {
+ // create a new configmap
+ remoteConfigMap = &corev1.ConfigMap{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: config.ConfigMapName,
+ Namespace: config.ConfigMapNamespace,
+ Labels: config.ConfigMapLabels,
+ },
+ Data: map[string]string{
+ activeLeasesKey: "",
+ nPermitsKey: fmt.Sprintf("%d", config.NPermits),
+ configMapUpdatePeriodKey: config.ConfigMapUpdatePeriod.String(),
+ leaseTimeoutKey: config.LeaseTimeout.String(),
+ deadClientTimeoutKey: config.DeadClientTimeout.String(),
+ maxQueueSizeKey: fmt.Sprintf("%d", config.MaxQueueSize),
+ },
+ }
+
+ remoteConfigMap, err = core.Instance().CreateConfigMap(remoteConfigMap)
+ if err != nil && !k8s_errors.IsAlreadyExists(err) {
+ return nil, err
+ }
+ }
+
+ cm := &configMap{
+ cm: remoteConfigMap,
+ }
+ cm.activeLeases, err = cm.ActiveLeases()
+ if err != nil {
+ return nil, err
+ }
+ return cm, nil
+}
+
+func updateRemoteConfigMap(remoteConfigMap *corev1.ConfigMap, config *SemaphoreConfig) {
+ // update the configmap with the latest values
+ remoteConfigMap.Data[nPermitsKey] = fmt.Sprintf("%d", config.NPermits)
+ remoteConfigMap.Data[configMapUpdatePeriodKey] = config.ConfigMapUpdatePeriod.String()
+ remoteConfigMap.Data[leaseTimeoutKey] = config.LeaseTimeout.String()
+ remoteConfigMap.Data[deadClientTimeoutKey] = config.DeadClientTimeout.String()
+ remoteConfigMap.Data[maxQueueSizeKey] = strconv.FormatUint(uint64(config.MaxQueueSize), 10)
+}
+
+// create the configmap if it doesn't exist then, fetch the latest copy of configmap and,
+// update semaphore config values (nPermits, leaseTimeout)
+func updateConfigMap(config *SemaphoreConfig) (*corev1.ConfigMap, error) {
+ remoteConfigMap, err := core.Instance().GetConfigMap(config.ConfigMapName, config.ConfigMapNamespace)
+ if err != nil {
+ return nil, err
+ }
+
+ // update the configmap with the latest values
+ updateRemoteConfigMap(remoteConfigMap, config)
+ remoteConfigMap, err = core.Instance().UpdateConfigMap(remoteConfigMap)
+ if err != nil {
+ return nil, err
+ }
+ return remoteConfigMap, nil
+}
+
+func getConfigMap(config *SemaphoreConfig) (*configMap, error) {
+ remoteConfigMap, err := core.Instance().GetConfigMap(config.ConfigMapName, config.ConfigMapNamespace)
+ if err != nil {
+ return nil, err
+ }
+
+ configMapUpdatePeriod, err := time.ParseDuration(remoteConfigMap.Data[configMapUpdatePeriodKey])
+ if err != nil {
+ return nil, err
+ }
+
+ leaseTimeout, err := time.ParseDuration(remoteConfigMap.Data[leaseTimeoutKey])
+ if err != nil {
+ return nil, err
+ }
+
+ deadClientTimeout, err := time.ParseDuration(remoteConfigMap.Data[deadClientTimeoutKey])
+ if err != nil {
+ return nil, err
+ }
+
+ maxQueueSize, err := strconv.ParseUint(remoteConfigMap.Data[maxQueueSizeKey], 10, 32)
+ if err != nil {
+ return nil, err
+ }
+
+ config.ConfigMapLabels = remoteConfigMap.Labels
+ config.ConfigMapUpdatePeriod = configMapUpdatePeriod
+ config.LeaseTimeout = leaseTimeout
+ config.DeadClientTimeout = deadClientTimeout
+ config.MaxQueueSize = uint(maxQueueSize)
+
+ cm := &configMap{
+ cm: remoteConfigMap,
+ }
+ cm.activeLeases, err = cm.ActiveLeases()
+ if err != nil {
+ return nil, err
+ }
+ return cm, nil
+}
+
+func (c *configMap) update(config *SemaphoreConfig) error {
+ updateRemoteConfigMap(c.cm, config)
+ remoteConfigMap, err := core.Instance().UpdateConfigMap(c.cm)
+ if err != nil {
+ return err
+ }
+ c.cm = remoteConfigMap
+ return nil
+}
+
+func (c *configMap) Name() string {
+ return c.cm.Name
+}
+
+func (c *configMap) Namespace() string {
+ return c.cm.Namespace
+}
+
+func (c *configMap) Labels() map[string]string {
+ return c.cm.Labels
+}
+
+func (c *configMap) NPermits() (uint32, error) {
+ nPermits, err := strconv.Atoi(c.cm.Data[nPermitsKey])
+ if err != nil {
+ return 0, err
+ }
+ return uint32(nPermits), nil
+}
+
+func (c *configMap) LeaseTimeout() (time.Duration, error) {
+ leaseTimeout, err := time.ParseDuration(c.cm.Data[leaseTimeoutKey])
+ if err != nil {
+ return 0, err
+ }
+ return leaseTimeout, nil
+}
+
+func (c *configMap) DeadClientTimeout() (time.Duration, error) {
+ deadClientTimeout, err := time.ParseDuration(c.cm.Data[deadClientTimeoutKey])
+ if err != nil {
+ return 0, err
+ }
+ return deadClientTimeout, nil
+}
+
+func (c *configMap) ActiveLeases() (activeLeasesMap, error) {
+ if c.activeLeases == nil {
+ c.activeLeases = activeLeasesMap{}
+ activeLeasesValue := c.cm.Data[activeLeasesKey]
+ if activeLeasesValue != "" {
+ err := json.Unmarshal([]byte(activeLeasesValue), &c.activeLeases)
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ returnActiveLeases := activeLeasesMap{}
+ for key, val := range c.activeLeases {
+ returnActiveLeases[key] = val
+ }
+ return returnActiveLeases, nil
+}
+
+// isConfigMapUpdateRequired compares two maps and returns true if they are different
+func isConfigMapUpdateRequired(map1, map2 activeLeasesMap) bool {
+ if len(map1) != len(map2) {
+ return true
+ }
+ for key1, val1 := range map1 {
+ // TODO how are two structs compared
+ if val2, ok := map2[key1]; !ok || val1 != val2 {
+ logrus.Infof("Lease %s: %v != %v", key1, val1, val2)
+ return true
+ }
+ }
+ return false
+}
+
+// Update replaces active leases in the configmap with the provided active leases
+// it only makes an update call if the active leases have changed
+func (c *configMap) UpdateLeases(newActiveLeases activeLeasesMap) error {
+ currentActiveLeases, err := c.ActiveLeases()
+ if err != nil {
+ panic(err)
+ }
+ if !isConfigMapUpdateRequired(newActiveLeases, currentActiveLeases) {
+ return nil
+ }
+
+ logrus.Infof("Updating configmap: %v", newActiveLeases)
+
+ // update the cache
+ c.activeLeases = activeLeasesMap{}
+ for key, val := range newActiveLeases {
+ c.activeLeases[key] = val
+ }
+
+ activeLeasesValue, err := json.Marshal(newActiveLeases)
+ if err != nil {
+ return err
+ }
+ c.cm.Data[activeLeasesKey] = string(activeLeasesValue)
+
+ c.cm, err = core.Instance().UpdateConfigMap(c.cm)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/pkg/resource-gateway/semaphore_server.go b/pkg/resource-gateway/semaphore_server.go
new file mode 100644
index 0000000000..dbee501fc4
--- /dev/null
+++ b/pkg/resource-gateway/semaphore_server.go
@@ -0,0 +1,209 @@
+package resource_gateway
+
+import (
+ "context"
+ "fmt"
+ "sync"
+ "time"
+
+ "github.com/libopenstorage/operator/pkg/constants"
+ pb "github.com/libopenstorage/operator/proto"
+ "github.com/portworx/sched-ops/k8s/core"
+ "github.com/sirupsen/logrus"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+)
+
+type semaphoreServer struct {
+ semaphoreMap sync.Map
+ semaphoreConfig *SemaphoreConfig
+}
+
+// NewSemaphoreServer creates a new semaphore server instance with the provided config.
+func NewSemaphoreServer(semaphoreConfig *SemaphoreConfig) *semaphoreServer {
+ if semaphoreConfig.ConfigMapLabels == nil {
+ semaphoreConfig.ConfigMapLabels = make(map[string]string)
+ }
+ semaphoreConfig.ConfigMapLabels[constants.OperatorLabelManagedByKey] = constants.OperatorLabelManagedByValueResourceGateway
+ return &semaphoreServer{
+ semaphoreConfig: semaphoreConfig,
+ }
+}
+
+func (s *semaphoreServer) newSemaphoreConfig(req *pb.CreateRequest) *SemaphoreConfig {
+ // create a copy of the common semaphore config
+ semaphoreConfig := copySemaphoreConfig(s.semaphoreConfig)
+
+ // update the config with the required request parameters
+ semaphoreConfig.NPermits = req.GetNPermits()
+ semaphoreConfig.ConfigMapName = fmt.Sprintf("%s-%s", semaphoreConfig.ConfigMapName, req.GetResourceId())
+
+ // override config values if optional request parameters are provided
+ // if req.GetConfigMapUpdatePeriod() > 0 {
+ // semaphoreConfig.ConfigMapUpdatePeriod = time.Duration(req.GetConfigMapUpdatePeriod()) * time.Second
+ // }
+ if req.GetLeaseTimeout() > 0 {
+ semaphoreConfig.LeaseTimeout = time.Duration(req.GetLeaseTimeout()) * time.Second
+ }
+ if req.GetDeadNodeTimeout() > 0 {
+ semaphoreConfig.DeadClientTimeout = time.Duration(req.GetDeadNodeTimeout()) * time.Second
+ }
+ // if req.GetMaxQueueSize() > 0 {
+ // semaphoreConfig.MaxQueueSize = req.GetMaxQueueSize()
+ // }
+
+ return semaphoreConfig
+}
+
+// Create implements the Create RPC method of the SemaphoreService.
+//
+// Create is used to create a new semaphore instance and its backing configmap.
+// It validates that required fields are provided in the request, and an instance
+// with the same resource ID does not already exist.
+func (s *semaphoreServer) Create(ctx context.Context, req *pb.CreateRequest) (*pb.CreateResponse, error) {
+ // validate request
+ if req.GetResourceId() == "" {
+ return &pb.CreateResponse{}, status.Error(codes.InvalidArgument, "Resource ID is required")
+ }
+ if req.GetNPermits() == 0 {
+ return &pb.CreateResponse{}, status.Error(codes.InvalidArgument, "Number of leases should be greater than 0")
+ }
+ semaphoreConfig := s.newSemaphoreConfig(req)
+
+ // check if semaphore instance already exists
+ item, ok := s.semaphoreMap.Load(req.GetResourceId())
+ if !ok {
+ logrus.Infof("Create semaphore with config: %v", semaphoreConfig)
+ semaphore := NewSemaphorePriorityQueueWithConfig(semaphoreConfig)
+ s.semaphoreMap.Store(req.GetResourceId(), semaphore)
+ return &pb.CreateResponse{}, nil
+ }
+
+ semaphorePQ := item.(SemaphorePriorityQueue)
+ semaphorePQ.Update(semaphoreConfig)
+ return &pb.CreateResponse{}, status.Error(codes.AlreadyExists, "Resource already exists")
+
+}
+
+// Load loads the semaphore instances that are already created in the system.
+//
+// It fetches all the configmaps with the label managed by resource-gateway
+// and creates semaphore instances for each of them.
+func (s *semaphoreServer) Load() error {
+ configMapList, err := core.Instance().ListConfigMap(
+ s.semaphoreConfig.ConfigMapNamespace,
+ metav1.ListOptions{
+ LabelSelector: fmt.Sprintf("%s=%s",
+ constants.OperatorLabelManagedByKey, constants.OperatorLabelManagedByValueResourceGateway),
+ })
+ if err != nil {
+ return err
+ }
+ for _, remoteConfigMap := range configMapList.Items {
+ resourceId := remoteConfigMap.Name[len(s.semaphoreConfig.ConfigMapName+"-"):]
+ logrus.Infof("Loading semaphore for configmap %s and resource Id %s", remoteConfigMap.Name, resourceId)
+ // other config values will be populated later from the configMap data
+ semaphoreConfig := &SemaphoreConfig{
+ ConfigMapName: remoteConfigMap.Name,
+ ConfigMapNamespace: remoteConfigMap.Namespace,
+ }
+ semaphore := NewSemaphorePriorityQueueWithConfigMap(semaphoreConfig)
+ s.semaphoreMap.Store(resourceId, semaphore)
+ }
+ return nil
+}
+
+// Acquire implements the Acquire RPC method of the SemaphoreService.
+//
+// Acquire is used to acquire a lease on a resource. It validates that required
+// fields are provided in the request, and the resource exists. It returns the
+// status of the lease acquisition.
+func (s *semaphoreServer) Acquire(ctx context.Context, req *pb.AcquireRequest) (*pb.AcquireResponse, error) {
+ // validate request
+ if req.GetResourceId() == "" {
+ return &pb.AcquireResponse{}, status.Error(codes.InvalidArgument, "Resource ID is required")
+ }
+ if req.GetClientId() == "" {
+ return &pb.AcquireResponse{}, status.Error(codes.InvalidArgument, "Client ID is required")
+ }
+ if req.GetAccessPriority() == pb.AccessPriority_TYPE_UNSPECIFIED {
+ return &pb.AcquireResponse{}, status.Error(codes.InvalidArgument, "Access Priority is required")
+ }
+
+ // get the semaphore instance
+ item, ok := s.semaphoreMap.Load(req.GetResourceId())
+ if !ok {
+ return &pb.AcquireResponse{}, status.Error(codes.NotFound, "Resource not found")
+ }
+ semaphorePQ := item.(SemaphorePriorityQueue)
+
+ // process request to acquire lease
+ resourceState, err := semaphorePQ.Acquire(req.ClientId, req.AccessPriority)
+ if err != nil {
+ return &pb.AcquireResponse{}, status.Error(codes.Internal, err.Error())
+ }
+ response := &pb.AcquireResponse{
+ AccessStatus: resourceState,
+ }
+ return response, nil
+}
+
+// Release implements the Release RPC method of the SemaphoreService.
+//
+// Release is used to release a lease on a resource. It validates that required
+// fields are provided in the request, and the resource exists.
+// It returns an empty response.
+func (s *semaphoreServer) Release(ctx context.Context, req *pb.ReleaseRequest) (*pb.ReleaseResponse, error) {
+ // validate request
+ if req.GetResourceId() == "" {
+ return &pb.ReleaseResponse{}, status.Error(codes.InvalidArgument, "Resource ID is required")
+ }
+ if req.GetClientId() == "" {
+ return &pb.ReleaseResponse{}, status.Error(codes.InvalidArgument, "Client ID is required")
+ }
+
+ // get the semaphore instance
+ item, ok := s.semaphoreMap.Load(req.GetResourceId())
+ if !ok {
+ return &pb.ReleaseResponse{}, status.Error(codes.NotFound, "Resource not found")
+ }
+ semaphorePQ := item.(SemaphorePriorityQueue)
+
+ // process request to release lease
+ err := semaphorePQ.Release(req.ClientId)
+ if err != nil {
+ return &pb.ReleaseResponse{}, status.Error(codes.Internal, err.Error())
+ }
+ return &pb.ReleaseResponse{}, nil
+}
+
+// Heartbeat implements the Heartbeat RPC method of the SemaphoreService.
+//
+// Heartbeat is used to keep the lease alive. It validates that required fields
+// are provided in the request, and the resource exists. It returns the status
+// of the lease.
+func (s *semaphoreServer) Heartbeat(ctx context.Context, req *pb.HeartbeatRequest) (*pb.HeartbeatResponse, error) {
+ // validate request
+ if req.GetResourceId() == "" {
+ return &pb.HeartbeatResponse{}, status.Error(codes.InvalidArgument, "Resource ID is required")
+ }
+ if req.GetClientId() == "" {
+ return &pb.HeartbeatResponse{}, status.Error(codes.InvalidArgument, "Client ID is required")
+ }
+
+ // get the semaphore instance
+ item, ok := s.semaphoreMap.Load(req.GetResourceId())
+ if !ok {
+ return &pb.HeartbeatResponse{}, status.Error(codes.NotFound, "Resource not found")
+ }
+ semaphorePQ := item.(SemaphorePriorityQueue)
+
+ // process client heartbeat
+ accessStatus := semaphorePQ.Heartbeat(req.ClientId)
+ response := &pb.HeartbeatResponse{
+ AccessStatus: accessStatus,
+ }
+ return response, nil
+}
diff --git a/pkg/resource-gateway/semaphore_server_test.go b/pkg/resource-gateway/semaphore_server_test.go
new file mode 100644
index 0000000000..3ee5d73699
--- /dev/null
+++ b/pkg/resource-gateway/semaphore_server_test.go
@@ -0,0 +1,117 @@
+package resource_gateway
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/libopenstorage/operator/pkg/constants"
+ pb "github.com/libopenstorage/operator/proto"
+ "github.com/portworx/sched-ops/k8s/core"
+ "github.com/stretchr/testify/require"
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func newSemaphoreServerTest() *semaphoreServer {
+ semaphoreConfig := &SemaphoreConfig{
+ ConfigMapName: testConfigMapName,
+ ConfigMapNamespace: testConfigMapNamespace,
+ ConfigMapLabels: map[string]string{},
+ ConfigMapUpdatePeriod: testConfigMapUpdatePeriod,
+ LeaseTimeout: testLeaseTimeout,
+ DeadClientTimeout: testDeadClientTimeout,
+ MaxQueueSize: 1000,
+ }
+ return NewSemaphoreServer(semaphoreConfig)
+}
+
+func createTestConfigMap(t *testing.T, suffix string) {
+ configMapName := fmt.Sprintf("%s-%s", testConfigMapName, suffix)
+ _, err := core.Instance().CreateConfigMap(&corev1.ConfigMap{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: configMapName,
+ Namespace: testConfigMapNamespace,
+ Labels: map[string]string{
+ constants.OperatorLabelManagedByKey: constants.OperatorLabelManagedByValueResourceGateway,
+ },
+ },
+ Data: map[string]string{
+ activeLeasesKey: "",
+ nPermitsKey: "1",
+ configMapUpdatePeriodKey: testConfigMapUpdatePeriod.String(),
+ leaseTimeoutKey: testLeaseTimeout.String(),
+ deadClientTimeoutKey: testDeadClientTimeout.String(),
+ maxQueueSizeKey: "1000",
+ },
+ })
+ require.NoError(t, err)
+}
+
+func TestSemaphoreServer_Create(t *testing.T) {
+ setup(t)
+
+ testResourceId := "resource-x"
+ deleteConfigMap(t, testResourceId)
+ s := newSemaphoreServerTest()
+
+ // First create request for a semaphore will create a new configmap
+ configMapName := fmt.Sprintf("%s-%s", testConfigMapName, testResourceId)
+ req := &pb.CreateRequest{
+ ResourceId: testResourceId,
+ NPermits: 1,
+ LeaseTimeout: 10,
+ }
+ _, err := s.Create(context.Background(), req)
+ require.NoError(t, err)
+ remoteConfigMap, err := core.Instance().GetConfigMap(configMapName, testConfigMapNamespace)
+ require.NoError(t, err)
+ require.Equal(t, "1", remoteConfigMap.Data[nPermitsKey])
+
+ // Second request for the same semaphore will update the existing configmap
+ req = &pb.CreateRequest{
+ ResourceId: testResourceId,
+ NPermits: 2,
+ LeaseTimeout: 10,
+ }
+ _, err = s.Create(context.Background(), req)
+ require.Error(t, err)
+ require.Contains(t, err.Error(), "Resource already exists")
+ remoteConfigMap, err = core.Instance().GetConfigMap(configMapName, testConfigMapNamespace)
+ require.NoError(t, err)
+ require.Equal(t, "2", remoteConfigMap.Data[nPermitsKey])
+}
+
+func TestSemaphoreServer_Load(t *testing.T) {
+ setup(t)
+
+ s := newSemaphoreServerTest()
+ deleteConfigMap(t, "resource-1")
+ deleteConfigMap(t, "resource-2")
+
+ // Create configmaps for semaphore that should be loaded
+ createTestConfigMap(t, "resource-1")
+ createTestConfigMap(t, "resource-2")
+
+ // Load the semaphore
+ err := s.Load()
+ require.NoError(t, err)
+
+ req := &pb.AcquireRequest{
+ ResourceId: "resource-1",
+ ClientId: "client-1",
+ AccessPriority: pb.AccessPriority_HIGH,
+ }
+ resp, err := s.Acquire(context.Background(), req)
+ require.NoError(t, err, "Unexpected error on Acquire")
+ require.Equal(t, pb.AccessStatus_LEASED, resp.GetAccessStatus())
+
+ req = &pb.AcquireRequest{
+ ResourceId: "resource-2",
+ ClientId: "client-2",
+ AccessPriority: pb.AccessPriority_LOW,
+ }
+ resp, err = s.Acquire(context.Background(), req)
+ require.NoError(t, err, "Unexpected error on Acquire")
+ require.Equal(t, pb.AccessStatus_LEASED, resp.GetAccessStatus())
+}
diff --git a/pkg/resource-gateway/semaphore_test.go b/pkg/resource-gateway/semaphore_test.go
new file mode 100644
index 0000000000..b3a2885832
--- /dev/null
+++ b/pkg/resource-gateway/semaphore_test.go
@@ -0,0 +1,224 @@
+package resource_gateway
+
+import (
+ "fmt"
+ "os"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/libopenstorage/openstorage/pkg/sched"
+ pb "github.com/libopenstorage/operator/proto"
+ "github.com/portworx/sched-ops/k8s/core"
+ "github.com/sirupsen/logrus"
+ "github.com/stretchr/testify/require"
+ k8s_errors "k8s.io/apimachinery/pkg/api/errors"
+)
+
+const (
+ // methods
+ noaction = "NoAction"
+ acquire = "Acquire"
+ release = "Release"
+ heartbeat = "Heartbeat"
+
+ // priorities
+ nopriority = pb.AccessPriority_TYPE_UNSPECIFIED
+ low = pb.AccessPriority_LOW
+ med = pb.AccessPriority_MEDIUM
+ high = pb.AccessPriority_HIGH
+
+ // access statuses
+ nostatus = pb.AccessStatus_TYPE_UNSPECIFIED
+ leased = pb.AccessStatus_LEASED
+ queued = pb.AccessStatus_QUEUED
+)
+
+var (
+ once sync.Once
+
+ testConfigMapName = "resource-gateway-test"
+ testConfigMapNamespace = "kube-system"
+ testConfigMapUpdatePeriod = 1 * time.Second
+ testLeaseTimeout = 25 * time.Second
+ testDeadClientTimeout = 15 * time.Second
+)
+
+func newSemaphorePriorityQueueTest() SemaphorePriorityQueue {
+ semaphoreConfig := &SemaphoreConfig{
+ NPermits: uint32(2),
+ ConfigMapName: testConfigMapName,
+ ConfigMapNamespace: testConfigMapNamespace,
+ ConfigMapLabels: map[string]string{
+ "name": testConfigMapName,
+ },
+ ConfigMapUpdatePeriod: testConfigMapUpdatePeriod,
+ LeaseTimeout: testLeaseTimeout,
+ DeadClientTimeout: testDeadClientTimeout,
+ MaxQueueSize: 1000,
+ }
+ return NewSemaphorePriorityQueueWithConfig(semaphoreConfig)
+}
+
+func deleteConfigMap(t *testing.T, suffix string) (err error) {
+ defer require.NoError(t, err, "Unable to delete configmap")
+ configMapName := testConfigMapName
+ if suffix != "" {
+ configMapName = fmt.Sprintf("%s-%s", testConfigMapName, suffix)
+ }
+ err = core.Instance().DeleteConfigMap(configMapName, testConfigMapNamespace)
+ if err != nil && !k8s_errors.IsNotFound(err) {
+ return err
+ }
+
+ // wait for the configmap to be deleted and check every second upto 5s
+ for i := 0; i < 5; i++ {
+ _, err = core.Instance().GetConfigMap(configMapName, testConfigMapNamespace)
+ if k8s_errors.IsNotFound(err) {
+ return nil
+ }
+ time.Sleep(time.Second * 1)
+ }
+ return err
+}
+
+func setup(t *testing.T) {
+ // init
+ once.Do(func() {
+ if sched.Instance() == nil {
+ sched.Init(time.Second)
+ }
+
+ // validate kubeconfig is set
+ if os.Getenv("KUBECONFIG") == "" {
+ fmt.Println("KUBECONFIG not set. Cannot run Semaphore UT.")
+ return
+ }
+ logrus.SetLevel(logrus.DebugLevel)
+ testOverride = true
+ })
+
+ deleteConfigMap(t, "") // cleanup
+}
+
+func validateConfigMap(t *testing.T, activeLeasesList []string) {
+ remoteConfigMap, err := core.Instance().GetConfigMap(testConfigMapName, testConfigMapNamespace)
+ cm := configMap{
+ cm: remoteConfigMap,
+ }
+ require.NoError(t, err, "Unexpected error on get configmap")
+ activeLeases, err := cm.ActiveLeases()
+ require.NoError(t, err, "Unexpected error on fetch active leases from configmap")
+ require.Equal(t, len(activeLeasesList), len(activeLeases))
+ for _, expectedClient := range activeLeasesList {
+ _, exists := activeLeases[expectedClient]
+ require.True(t, exists, "Client %s not found in configmap", expectedClient)
+ }
+}
+
+func TestSemaphore_AcquireAndRelease(t *testing.T) {
+
+ testCases := []struct {
+ name string
+ method string
+ delay time.Duration
+ client string
+ priority pb.AccessPriority_Type
+ status pb.AccessStatus_Type
+ activeLeasesList []string
+ }{
+ // configMapUpdatePeriod = 1s, leaseTimeout = 25s, deadClientTimeout = 15s
+ // test acquire and release
+ // tests a client can take the first lease
+ {"client-1 is granted lease", acquire, 0, "client-1", low, leased, []string{"client-1"}},
+ // 2 seconds elapsed
+ // tests a client can take the second / last lease
+ {"client-2 is granted lease", acquire, 0, "client-2", low, leased, []string{"client-1", "client-2"}},
+ // 4 seconds elapsed
+ // tests a client can be queued to low priority
+ {"client-3 is queued to low", acquire, 0, "client-3", low, queued, []string{"client-1", "client-2"}},
+ // tests a client can be queued to medium priority
+ {"client-4 is queued to med", acquire, 0, "client-4", med, queued, []string{"client-1", "client-2"}},
+ // tests a client can be queued to high priority
+ {"client-5 is queued to high", acquire, 0, "client-5", high, queued, []string{"client-1", "client-2"}},
+ // tests a client can be queued to high priority behind another high priority client
+ {"client-6 is queued to high", acquire, 0, "client-6", high, queued, []string{"client-1", "client-2"}},
+ // tests the acquire will be noop and return correct status if the client already has the lease
+ {"client-1 is already leased", acquire, 0, "client-1", low, leased, []string{"client-1", "client-2"}},
+ // tests the acquire will be noop if the client is already queued and no lease is available
+ {"client-5 cannot take the lease", acquire, 0, "client-5", high, queued, []string{"client-1", "client-2"}},
+ // tests client is able to release the lease
+ {"client-1 releases the lease", release, 0, "client-1", nopriority, nostatus, []string{"client-2"}},
+ // 6 seconds elapsed
+ // tests that heartbeats return the correct status
+ // heartbeat to keep client-2 lease alive till 21 seconds elapsed
+ {"client-2 heartbeats within lease timeout", heartbeat, 0, "client-2", nopriority, leased, []string{"client-2"}},
+ // do acquire poll for clients in queue for implicit heartbeat
+ // keeps client 3 and 4 alive till 21 seconds elapsed
+ // tests the client with lower priority will NOT get the lease if a client with higher priority is in the queue
+ {"client-3 cannot take the lease", acquire, 0, "client-3", low, queued, []string{"client-2"}},
+ {"client-4 cannot take the lease", acquire, 0, "client-4", med, queued, []string{"client-2"}},
+ // tests the client with highest priority at the front of the queue will get the lease
+ {"client-5 is granted the lease", acquire, 0, "client-5", high, leased, []string{"client-5", "client-2"}},
+ // 8 seconds elapsed
+ // keep client 6 alive till 23 seconds elapsed
+ {"client-6 cannot take the lease", acquire, 0, "client-6", high, queued, []string{"client-2", "client-5"}},
+ {"client-5 releases the lease", release, 0, "client-5", nopriority, nostatus, []string{"client-2"}},
+ // 10 seconds elapsed
+ {"client-6 is granted the lease", acquire, 0, "client-6", high, leased, []string{"client-2", "client-6"}},
+ // 12 seconds elapsed
+ // tests that heartbeats return the correct status
+ {"client-2 heartbeats within lease timeout", heartbeat, 0, "client-2", nopriority, leased, []string{"client-2", "client-6"}},
+ {"client-6 heartbeats", heartbeat, 0, "client-6", nopriority, leased, []string{"client-2", "client-6"}},
+ {"client-6 releases the lease", release, 0, "client-6", nopriority, nostatus, []string{"client-2"}},
+ // 14 seconds elapsed
+ // tests the client with medium priority will get the lease
+ {"client-4 is granted the lease", acquire, 0, "client-4", med, leased, []string{"client-2", "client-4"}},
+ // 16 seconds elasped
+ // tests that client in queue that has not polled within dead client timeout will be removed the queue
+ // sleep 7 seconds
+ {"client-3 heartbeats outside dead client timeout", heartbeat, time.Second * 7, "client-3", nopriority, nostatus, []string{"client-2", "client-4"}},
+ // 23 seconds elapsed
+ // tests that client that has held the lease for longer than the lease timeout will lose the lease
+ // sleep 5 seconds
+ {"client-2 heartbeats outside lease timeout", heartbeat, time.Second * 7, "client-2", nopriority, nostatus, []string{"client-4"}},
+ // 30 seconds elapsed
+ // tests that client that has a lease and has not heartbeated within dead client timeout will lose the lease
+ // sleep 5 seconds
+ {"client-4 heartbeats outside dead client timeout", heartbeat, time.Second * 5, "client-4", nopriority, nostatus, []string{}},
+ // 35 seconds elapsed
+ }
+
+ setup(t)
+ semPQ := newSemaphorePriorityQueueTest()
+
+ startTime := time.Now()
+
+ for _, tc := range testCases {
+ if tc.delay > 0 {
+ time.Sleep(tc.delay)
+ fmt.Println("Elapsed time (after sleep): ", time.Since(startTime).Seconds())
+ }
+ if tc.method == acquire {
+ t.Run(tc.name, func(t *testing.T) {
+ status, err := semPQ.Acquire(tc.client, tc.priority)
+ require.NoError(t, err, "Unexpected error on Acquire")
+ require.Equal(t, tc.status, status)
+ })
+ } else if tc.method == release {
+ t.Run(tc.name, func(t *testing.T) {
+ err := semPQ.Release(tc.client)
+ require.NoError(t, err, "Unexpected error on Release")
+ })
+ } else if tc.method == heartbeat {
+ t.Run(tc.name, func(t *testing.T) {
+ status := semPQ.Heartbeat(tc.client)
+ require.Equal(t, tc.status, status)
+ })
+ }
+
+ validateConfigMap(t, tc.activeLeasesList)
+
+ fmt.Println("Elapsed time (after exec): ", time.Since(startTime).Seconds())
+ }
+}
diff --git a/pkg/util/k8s/k8s.go b/pkg/util/k8s/k8s.go
index d8e31391c5..900c2d75ed 100644
--- a/pkg/util/k8s/k8s.go
+++ b/pkg/util/k8s/k8s.go
@@ -1578,7 +1578,7 @@ func CreateOrUpdatePrometheus(
if prometheus.Spec.PodMetadata.Labels == nil {
prometheus.Spec.PodMetadata.Labels = make(map[string]string)
}
- prometheus.Spec.PodMetadata.Labels[constants.OperatorLabelManagedByKey] = constants.OperatorLabelManagedByValue
+ prometheus.Spec.PodMetadata.Labels[constants.OperatorLabelManagedByKey] = constants.OperatorLabelManagedByValuePortworx
if modified || len(prometheus.OwnerReferences) > len(existingPrometheus.OwnerReferences) {
prometheus.ResourceVersion = existingPrometheus.ResourceVersion
logrus.Infof("Updating Prometheus %s/%s", prometheus.Namespace, prometheus.Name)
@@ -1660,7 +1660,7 @@ func CreateOrUpdateAlertManager(
if alertManager.Spec.PodMetadata.Labels == nil {
alertManager.Spec.PodMetadata.Labels = make(map[string]string)
}
- alertManager.Spec.PodMetadata.Labels[constants.OperatorLabelManagedByKey] = constants.OperatorLabelManagedByValue
+ alertManager.Spec.PodMetadata.Labels[constants.OperatorLabelManagedByKey] = constants.OperatorLabelManagedByValuePortworx
if modified || len(alertManager.OwnerReferences) > len(existingAlertManager.OwnerReferences) {
alertManager.ResourceVersion = existingAlertManager.ResourceVersion
logrus.Infof("Updating AlertManager %s/%s", alertManager.Namespace, alertManager.Name)
@@ -2330,7 +2330,7 @@ func AddManagedByOperatorLabel(om metav1.ObjectMeta) metav1.ObjectMeta {
if om.Labels == nil {
om.Labels = make(map[string]string)
}
- om.Labels[constants.OperatorLabelManagedByKey] = constants.OperatorLabelManagedByValue
+ om.Labels[constants.OperatorLabelManagedByKey] = constants.OperatorLabelManagedByValuePortworx
return om
}
diff --git a/proto/Dockerfile b/proto/Dockerfile
new file mode 100644
index 0000000000..ca40545140
--- /dev/null
+++ b/proto/Dockerfile
@@ -0,0 +1,10 @@
+# This Dockerfile is used to build base image for Resource Gateway proto files
+# The image is based on osd-proto image provided by OpenStorage
+
+FROM quay.io/openstorage/osd-proto-clients
+
+LABEL author="dgoel"
+
+RUN mkdir -p /go/src/github.com/libopenstorage/operator/proto
+
+WORKDIR /go/src/github.com/libopenstorage/operator/proto
diff --git a/proto/Makefile b/proto/Makefile
new file mode 100644
index 0000000000..e87179a0c8
--- /dev/null
+++ b/proto/Makefile
@@ -0,0 +1,53 @@
+# set defaults
+ifndef DOCKER_HUB_RESOURCE_GATEWAY_PROTO_IMG
+ DOCKER_HUB_RESOURCE_GATEWAY_PROTO_IMG := resource-gateway-proto
+ $(warning DOCKER_HUB_RESOURCE_GATEWAY_PROTO_IMG not defined, using '$(DOCKER_HUB_RESOURCE_GATEWAY_PROTO_IMG)' instead)
+endif
+ifndef DOCKER_HUB_RESOURCE_GATEWAY_PROTO_TAG
+ DOCKER_HUB_RESOURCE_GATEWAY_PROTO_TAG := latest
+ $(warning DOCKER_HUB_RESOURCE_GATEWAY_PROTO_TAG not defined, using '$(DOCKER_HUB_RESOURCE_GATEWAY_PROTO_TAG)' instead)
+endif
+
+ifndef PROTOC
+PROTOC = protoc
+endif
+
+ifndef PROTOS_PATH
+PROTOS_PATH = $(GOPATH)/src
+endif
+
+ifndef PROTOSRC_PATH
+PROTOSRC_PATH = $(PROTOS_PATH)/github.com/libopenstorage/operator/proto
+endif
+
+RESOURCE_GATEWAY_PROTO_IMG=$(DOCKER_HUB_REPO)/$(DOCKER_HUB_RESOURCE_GATEWAY_PROTO_IMG):$(DOCKER_HUB_RESOURCE_GATEWAY_PROTO_TAG)
+
+# builds the container to compile proto files
+build-docker:
+ docker build -t $(RESOURCE_GATEWAY_PROTO_IMG) .
+
+# builds the proto target inside the build container
+docker-proto:
+ docker run \
+ --privileged --rm -it \
+ -v $(shell pwd):/go/src/github.com/libopenstorage/operator/proto \
+ -e "GOPATH=/go" \
+ -e "DOCKER_PROTO=yes" \
+ -e "PATH=/bin:/usr/bin:/usr/local/bin:/go/bin:/usr/local/go/bin" \
+ $(RESOURCE_GATEWAY_PROTO_IMG) \
+ make proto
+
+# compiles the proto files - should be run inside the build container
+proto: $(GOPATH)/bin/protoc-gen-go $(GOPATH)/bin/protoc-gen-grpc-gateway $(GOPATH)/bin/protoc-gen-swagger
+ifndef DOCKER_PROTO
+ $(error Do not run directly. Run 'make docker-proto' instead.)
+endif
+ @echo ">>> Generating protobuf definitions from api/api.proto"
+ $(PROTOC) -I $(PROTOSRC_PATH) \
+ -I /usr/local/include \
+ -I $(PROTOS_PATH)/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
+ --go_out=plugins=grpc:. \
+ $(PROTOSRC_PATH)/resource_gateway.proto
+
+clean:
+ rm -f resource_gateway.pb.go
\ No newline at end of file
diff --git a/proto/resource_gateway.pb.go b/proto/resource_gateway.pb.go
new file mode 100644
index 0000000000..dab5676b39
--- /dev/null
+++ b/proto/resource_gateway.pb.go
@@ -0,0 +1,826 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: resource_gateway.proto
+
+package resourcegateway
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+import (
+ context "golang.org/x/net/context"
+ grpc "google.golang.org/grpc"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+
+// Type of priority
+type AccessPriority_Type int32
+
+const (
+ // Unspecified, do NOT use
+ AccessPriority_TYPE_UNSPECIFIED AccessPriority_Type = 0
+ // Enqueued to low priority queue
+ AccessPriority_LOW AccessPriority_Type = 1
+ // Enqueued to medium priority queue
+ AccessPriority_MEDIUM AccessPriority_Type = 2
+ // Enqueued to high priority queue
+ AccessPriority_HIGH AccessPriority_Type = 3
+)
+
+var AccessPriority_Type_name = map[int32]string{
+ 0: "TYPE_UNSPECIFIED",
+ 1: "LOW",
+ 2: "MEDIUM",
+ 3: "HIGH",
+}
+var AccessPriority_Type_value = map[string]int32{
+ "TYPE_UNSPECIFIED": 0,
+ "LOW": 1,
+ "MEDIUM": 2,
+ "HIGH": 3,
+}
+
+func (x AccessPriority_Type) String() string {
+ return proto.EnumName(AccessPriority_Type_name, int32(x))
+}
+func (AccessPriority_Type) EnumDescriptor() ([]byte, []int) {
+ return fileDescriptor_resource_gateway_1e8a27e27414e1c7, []int{0, 0}
+}
+
+// Type of status
+type AccessStatus_Type int32
+
+const (
+ // Unspecified, do NOT use
+ AccessStatus_TYPE_UNSPECIFIED AccessStatus_Type = 0
+ // Enqueued for access to the resource
+ AccessStatus_QUEUED AccessStatus_Type = 1
+ // Lease acquired for the resource
+ AccessStatus_LEASED AccessStatus_Type = 2
+)
+
+var AccessStatus_Type_name = map[int32]string{
+ 0: "TYPE_UNSPECIFIED",
+ 1: "QUEUED",
+ 2: "LEASED",
+}
+var AccessStatus_Type_value = map[string]int32{
+ "TYPE_UNSPECIFIED": 0,
+ "QUEUED": 1,
+ "LEASED": 2,
+}
+
+func (x AccessStatus_Type) String() string {
+ return proto.EnumName(AccessStatus_Type_name, int32(x))
+}
+func (AccessStatus_Type) EnumDescriptor() ([]byte, []int) {
+ return fileDescriptor_resource_gateway_1e8a27e27414e1c7, []int{1, 0}
+}
+
+// AccessPriority specifies the priority of a client's access to a semaphore resource
+type AccessPriority struct {
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *AccessPriority) Reset() { *m = AccessPriority{} }
+func (m *AccessPriority) String() string { return proto.CompactTextString(m) }
+func (*AccessPriority) ProtoMessage() {}
+func (*AccessPriority) Descriptor() ([]byte, []int) {
+ return fileDescriptor_resource_gateway_1e8a27e27414e1c7, []int{0}
+}
+func (m *AccessPriority) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_AccessPriority.Unmarshal(m, b)
+}
+func (m *AccessPriority) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_AccessPriority.Marshal(b, m, deterministic)
+}
+func (dst *AccessPriority) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_AccessPriority.Merge(dst, src)
+}
+func (m *AccessPriority) XXX_Size() int {
+ return xxx_messageInfo_AccessPriority.Size(m)
+}
+func (m *AccessPriority) XXX_DiscardUnknown() {
+ xxx_messageInfo_AccessPriority.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AccessPriority proto.InternalMessageInfo
+
+// AccessStatus specifies the status of a client's access to a semaphore resource
+type AccessStatus struct {
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *AccessStatus) Reset() { *m = AccessStatus{} }
+func (m *AccessStatus) String() string { return proto.CompactTextString(m) }
+func (*AccessStatus) ProtoMessage() {}
+func (*AccessStatus) Descriptor() ([]byte, []int) {
+ return fileDescriptor_resource_gateway_1e8a27e27414e1c7, []int{1}
+}
+func (m *AccessStatus) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_AccessStatus.Unmarshal(m, b)
+}
+func (m *AccessStatus) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_AccessStatus.Marshal(b, m, deterministic)
+}
+func (dst *AccessStatus) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_AccessStatus.Merge(dst, src)
+}
+func (m *AccessStatus) XXX_Size() int {
+ return xxx_messageInfo_AccessStatus.Size(m)
+}
+func (m *AccessStatus) XXX_DiscardUnknown() {
+ xxx_messageInfo_AccessStatus.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AccessStatus proto.InternalMessageInfo
+
+// CreateRequest is the request to create a new semaphore resource
+//
+// resource_id and n_permits are required fields
+// the other fields are optional and if skipped will be set to default values
+type CreateRequest struct {
+ // Resource Id of the new semaphore resource
+ ResourceId string `protobuf:"bytes,1,opt,name=resource_id,json=resourceId" json:"resource_id,omitempty"`
+ // Number of permits that can be leased out for a resource
+ NPermits uint32 `protobuf:"varint,2,opt,name=n_permits,json=nPermits" json:"n_permits,omitempty"`
+ // Max duration for which a lease can be held
+ LeaseTimeout uint64 `protobuf:"varint,3,opt,name=lease_timeout,json=leaseTimeout" json:"lease_timeout,omitempty"`
+ // Max duration after which a client is considered dead if there is no heartbeat
+ DeadNodeTimeout uint64 `protobuf:"varint,4,opt,name=dead_node_timeout,json=deadNodeTimeout" json:"dead_node_timeout,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *CreateRequest) Reset() { *m = CreateRequest{} }
+func (m *CreateRequest) String() string { return proto.CompactTextString(m) }
+func (*CreateRequest) ProtoMessage() {}
+func (*CreateRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_resource_gateway_1e8a27e27414e1c7, []int{2}
+}
+func (m *CreateRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_CreateRequest.Unmarshal(m, b)
+}
+func (m *CreateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_CreateRequest.Marshal(b, m, deterministic)
+}
+func (dst *CreateRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_CreateRequest.Merge(dst, src)
+}
+func (m *CreateRequest) XXX_Size() int {
+ return xxx_messageInfo_CreateRequest.Size(m)
+}
+func (m *CreateRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_CreateRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CreateRequest proto.InternalMessageInfo
+
+func (m *CreateRequest) GetResourceId() string {
+ if m != nil {
+ return m.ResourceId
+ }
+ return ""
+}
+
+func (m *CreateRequest) GetNPermits() uint32 {
+ if m != nil {
+ return m.NPermits
+ }
+ return 0
+}
+
+func (m *CreateRequest) GetLeaseTimeout() uint64 {
+ if m != nil {
+ return m.LeaseTimeout
+ }
+ return 0
+}
+
+func (m *CreateRequest) GetDeadNodeTimeout() uint64 {
+ if m != nil {
+ return m.DeadNodeTimeout
+ }
+ return 0
+}
+
+// CreateResponse is the response to create a new semaphore resource
+type CreateResponse struct {
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *CreateResponse) Reset() { *m = CreateResponse{} }
+func (m *CreateResponse) String() string { return proto.CompactTextString(m) }
+func (*CreateResponse) ProtoMessage() {}
+func (*CreateResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_resource_gateway_1e8a27e27414e1c7, []int{3}
+}
+func (m *CreateResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_CreateResponse.Unmarshal(m, b)
+}
+func (m *CreateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_CreateResponse.Marshal(b, m, deterministic)
+}
+func (dst *CreateResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_CreateResponse.Merge(dst, src)
+}
+func (m *CreateResponse) XXX_Size() int {
+ return xxx_messageInfo_CreateResponse.Size(m)
+}
+func (m *CreateResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_CreateResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CreateResponse proto.InternalMessageInfo
+
+// AcquireRequest is the request to acquire a lease for a semaphore resource
+type AcquireRequest struct {
+ // Resource Id to acquire the lease for
+ // It should be the same as the resource_id in the CreateRequest
+ ResourceId string `protobuf:"bytes,1,opt,name=resource_id,json=resourceId" json:"resource_id,omitempty"`
+ // Client Id who is acquiring the lease
+ // client_id can be any unique identifier for the client
+ ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId" json:"client_id,omitempty"`
+ // Priority of the client's access to the resource
+ AccessPriority AccessPriority_Type `protobuf:"varint,3,opt,name=access_priority,json=accessPriority,enum=operator.resourcegateway.AccessPriority_Type" json:"access_priority,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *AcquireRequest) Reset() { *m = AcquireRequest{} }
+func (m *AcquireRequest) String() string { return proto.CompactTextString(m) }
+func (*AcquireRequest) ProtoMessage() {}
+func (*AcquireRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_resource_gateway_1e8a27e27414e1c7, []int{4}
+}
+func (m *AcquireRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_AcquireRequest.Unmarshal(m, b)
+}
+func (m *AcquireRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_AcquireRequest.Marshal(b, m, deterministic)
+}
+func (dst *AcquireRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_AcquireRequest.Merge(dst, src)
+}
+func (m *AcquireRequest) XXX_Size() int {
+ return xxx_messageInfo_AcquireRequest.Size(m)
+}
+func (m *AcquireRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_AcquireRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AcquireRequest proto.InternalMessageInfo
+
+func (m *AcquireRequest) GetResourceId() string {
+ if m != nil {
+ return m.ResourceId
+ }
+ return ""
+}
+
+func (m *AcquireRequest) GetClientId() string {
+ if m != nil {
+ return m.ClientId
+ }
+ return ""
+}
+
+func (m *AcquireRequest) GetAccessPriority() AccessPriority_Type {
+ if m != nil {
+ return m.AccessPriority
+ }
+ return AccessPriority_TYPE_UNSPECIFIED
+}
+
+// AcquireResponse is the response to acquire a semaphore lock
+type AcquireResponse struct {
+ // Status of the client's access to the resource
+ AccessStatus AccessStatus_Type `protobuf:"varint,1,opt,name=access_status,json=accessStatus,enum=operator.resourcegateway.AccessStatus_Type" json:"access_status,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *AcquireResponse) Reset() { *m = AcquireResponse{} }
+func (m *AcquireResponse) String() string { return proto.CompactTextString(m) }
+func (*AcquireResponse) ProtoMessage() {}
+func (*AcquireResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_resource_gateway_1e8a27e27414e1c7, []int{5}
+}
+func (m *AcquireResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_AcquireResponse.Unmarshal(m, b)
+}
+func (m *AcquireResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_AcquireResponse.Marshal(b, m, deterministic)
+}
+func (dst *AcquireResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_AcquireResponse.Merge(dst, src)
+}
+func (m *AcquireResponse) XXX_Size() int {
+ return xxx_messageInfo_AcquireResponse.Size(m)
+}
+func (m *AcquireResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_AcquireResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AcquireResponse proto.InternalMessageInfo
+
+func (m *AcquireResponse) GetAccessStatus() AccessStatus_Type {
+ if m != nil {
+ return m.AccessStatus
+ }
+ return AccessStatus_TYPE_UNSPECIFIED
+}
+
+// ReleaseRequest is the request to release a semaphore lock
+type ReleaseRequest struct {
+ // Resource Id to release the lease for
+ ResourceId string `protobuf:"bytes,1,opt,name=resource_id,json=resourceId" json:"resource_id,omitempty"`
+ // Client Id who is releasing the lease
+ ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId" json:"client_id,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ReleaseRequest) Reset() { *m = ReleaseRequest{} }
+func (m *ReleaseRequest) String() string { return proto.CompactTextString(m) }
+func (*ReleaseRequest) ProtoMessage() {}
+func (*ReleaseRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_resource_gateway_1e8a27e27414e1c7, []int{6}
+}
+func (m *ReleaseRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ReleaseRequest.Unmarshal(m, b)
+}
+func (m *ReleaseRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ReleaseRequest.Marshal(b, m, deterministic)
+}
+func (dst *ReleaseRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ReleaseRequest.Merge(dst, src)
+}
+func (m *ReleaseRequest) XXX_Size() int {
+ return xxx_messageInfo_ReleaseRequest.Size(m)
+}
+func (m *ReleaseRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_ReleaseRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ReleaseRequest proto.InternalMessageInfo
+
+func (m *ReleaseRequest) GetResourceId() string {
+ if m != nil {
+ return m.ResourceId
+ }
+ return ""
+}
+
+func (m *ReleaseRequest) GetClientId() string {
+ if m != nil {
+ return m.ClientId
+ }
+ return ""
+}
+
+// ReleaseResponse is the response to release a semaphore lock
+type ReleaseResponse struct {
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ReleaseResponse) Reset() { *m = ReleaseResponse{} }
+func (m *ReleaseResponse) String() string { return proto.CompactTextString(m) }
+func (*ReleaseResponse) ProtoMessage() {}
+func (*ReleaseResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_resource_gateway_1e8a27e27414e1c7, []int{7}
+}
+func (m *ReleaseResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ReleaseResponse.Unmarshal(m, b)
+}
+func (m *ReleaseResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ReleaseResponse.Marshal(b, m, deterministic)
+}
+func (dst *ReleaseResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ReleaseResponse.Merge(dst, src)
+}
+func (m *ReleaseResponse) XXX_Size() int {
+ return xxx_messageInfo_ReleaseResponse.Size(m)
+}
+func (m *ReleaseResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_ReleaseResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ReleaseResponse proto.InternalMessageInfo
+
+// HeartbeatRequest is the request to send a heartbeat to the semaphore service
+type HeartbeatRequest struct {
+ // Resource ID to keep the lease alive for
+ ResourceId string `protobuf:"bytes,1,opt,name=resource_id,json=resourceId" json:"resource_id,omitempty"`
+ // Client ID to keep the lease alive for
+ ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId" json:"client_id,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *HeartbeatRequest) Reset() { *m = HeartbeatRequest{} }
+func (m *HeartbeatRequest) String() string { return proto.CompactTextString(m) }
+func (*HeartbeatRequest) ProtoMessage() {}
+func (*HeartbeatRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_resource_gateway_1e8a27e27414e1c7, []int{8}
+}
+func (m *HeartbeatRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_HeartbeatRequest.Unmarshal(m, b)
+}
+func (m *HeartbeatRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_HeartbeatRequest.Marshal(b, m, deterministic)
+}
+func (dst *HeartbeatRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_HeartbeatRequest.Merge(dst, src)
+}
+func (m *HeartbeatRequest) XXX_Size() int {
+ return xxx_messageInfo_HeartbeatRequest.Size(m)
+}
+func (m *HeartbeatRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_HeartbeatRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_HeartbeatRequest proto.InternalMessageInfo
+
+func (m *HeartbeatRequest) GetResourceId() string {
+ if m != nil {
+ return m.ResourceId
+ }
+ return ""
+}
+
+func (m *HeartbeatRequest) GetClientId() string {
+ if m != nil {
+ return m.ClientId
+ }
+ return ""
+}
+
+// HeartbeatLockResponse is the response to keep a semaphore lock alive
+type HeartbeatResponse struct {
+ // Status of the client's access to the resource
+ AccessStatus AccessStatus_Type `protobuf:"varint,1,opt,name=access_status,json=accessStatus,enum=operator.resourcegateway.AccessStatus_Type" json:"access_status,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *HeartbeatResponse) Reset() { *m = HeartbeatResponse{} }
+func (m *HeartbeatResponse) String() string { return proto.CompactTextString(m) }
+func (*HeartbeatResponse) ProtoMessage() {}
+func (*HeartbeatResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_resource_gateway_1e8a27e27414e1c7, []int{9}
+}
+func (m *HeartbeatResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_HeartbeatResponse.Unmarshal(m, b)
+}
+func (m *HeartbeatResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_HeartbeatResponse.Marshal(b, m, deterministic)
+}
+func (dst *HeartbeatResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_HeartbeatResponse.Merge(dst, src)
+}
+func (m *HeartbeatResponse) XXX_Size() int {
+ return xxx_messageInfo_HeartbeatResponse.Size(m)
+}
+func (m *HeartbeatResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_HeartbeatResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_HeartbeatResponse proto.InternalMessageInfo
+
+func (m *HeartbeatResponse) GetAccessStatus() AccessStatus_Type {
+ if m != nil {
+ return m.AccessStatus
+ }
+ return AccessStatus_TYPE_UNSPECIFIED
+}
+
+func init() {
+ proto.RegisterType((*AccessPriority)(nil), "operator.resourcegateway.AccessPriority")
+ proto.RegisterType((*AccessStatus)(nil), "operator.resourcegateway.AccessStatus")
+ proto.RegisterType((*CreateRequest)(nil), "operator.resourcegateway.CreateRequest")
+ proto.RegisterType((*CreateResponse)(nil), "operator.resourcegateway.CreateResponse")
+ proto.RegisterType((*AcquireRequest)(nil), "operator.resourcegateway.AcquireRequest")
+ proto.RegisterType((*AcquireResponse)(nil), "operator.resourcegateway.AcquireResponse")
+ proto.RegisterType((*ReleaseRequest)(nil), "operator.resourcegateway.ReleaseRequest")
+ proto.RegisterType((*ReleaseResponse)(nil), "operator.resourcegateway.ReleaseResponse")
+ proto.RegisterType((*HeartbeatRequest)(nil), "operator.resourcegateway.HeartbeatRequest")
+ proto.RegisterType((*HeartbeatResponse)(nil), "operator.resourcegateway.HeartbeatResponse")
+ proto.RegisterEnum("operator.resourcegateway.AccessPriority_Type", AccessPriority_Type_name, AccessPriority_Type_value)
+ proto.RegisterEnum("operator.resourcegateway.AccessStatus_Type", AccessStatus_Type_name, AccessStatus_Type_value)
+}
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ context.Context
+var _ grpc.ClientConn
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+const _ = grpc.SupportPackageIsVersion4
+
+// Client API for SemaphoreService service
+
+type SemaphoreServiceClient interface {
+ // Create creates a new semaphore resource
+ // It initializes a semaphore for the resource in memory and persists it in the backend
+ //
+ // resource_id and n_permits are required fields
+ // the other fields are optional and if skipped will be set to default values
+ //
+ // The first request every client makes to the semaphore service should be a create request
+ //
+ // Only the first request received by the semaphore service will create the resource
+ // Any subsequent requests will be ignored
+ Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error)
+ // Acquire acquires a lease for the resource or reserves a place in the queue
+ //
+ // Clients will poll this endpoint to acquire a lease for the resource
+ // On the first poll, they will either be granted a lease or enqueued
+ // If enqueued, the client should poll periodically to check if the lease is granted
+ //
+ // The client is enqueued in the priority queue corresponding to the access_priority
+ // Leases are granted according to priority that is,
+ // first all high priority clients are granted leases followed by the others in order
+ //
+ // A client can be starved by other clients with higher priority
+ // Consider implementing a client-side timeout for how long to wait for the lease
+ // before bypassing the semaphore access (if possible)
+ // The priority can not be bumped up once the client is enqueued (Future work)
+ //
+ // No need to send a heartbeat while the client is enqueued
+ // Heartbeats are updated implicitly on every Acquire request
+ Acquire(ctx context.Context, in *AcquireRequest, opts ...grpc.CallOption) (*AcquireResponse, error)
+ // Release releases the lease on the resource
+ //
+ // If the lease has expired, the release request will be a noop
+ Release(ctx context.Context, in *ReleaseRequest, opts ...grpc.CallOption) (*ReleaseResponse, error)
+ // Heartbeat keeps the lease alive for the resource
+ //
+ // Clients should periodically send heartbeats once they have acquired the lease
+ // If no heartbeat is received within the dead_node_timeout, the lease will be revoked
+ //
+ // Client should monitor the access status returned to check if the lease is still valid
+ // if the lease is lost the client should take the necessary action
+ //
+ //
+ Heartbeat(ctx context.Context, in *HeartbeatRequest, opts ...grpc.CallOption) (*HeartbeatResponse, error)
+}
+
+type semaphoreServiceClient struct {
+ cc *grpc.ClientConn
+}
+
+func NewSemaphoreServiceClient(cc *grpc.ClientConn) SemaphoreServiceClient {
+ return &semaphoreServiceClient{cc}
+}
+
+func (c *semaphoreServiceClient) Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error) {
+ out := new(CreateResponse)
+ err := grpc.Invoke(ctx, "/operator.resourcegateway.SemaphoreService/Create", in, out, c.cc, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *semaphoreServiceClient) Acquire(ctx context.Context, in *AcquireRequest, opts ...grpc.CallOption) (*AcquireResponse, error) {
+ out := new(AcquireResponse)
+ err := grpc.Invoke(ctx, "/operator.resourcegateway.SemaphoreService/Acquire", in, out, c.cc, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *semaphoreServiceClient) Release(ctx context.Context, in *ReleaseRequest, opts ...grpc.CallOption) (*ReleaseResponse, error) {
+ out := new(ReleaseResponse)
+ err := grpc.Invoke(ctx, "/operator.resourcegateway.SemaphoreService/Release", in, out, c.cc, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *semaphoreServiceClient) Heartbeat(ctx context.Context, in *HeartbeatRequest, opts ...grpc.CallOption) (*HeartbeatResponse, error) {
+ out := new(HeartbeatResponse)
+ err := grpc.Invoke(ctx, "/operator.resourcegateway.SemaphoreService/Heartbeat", in, out, c.cc, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+// Server API for SemaphoreService service
+
+type SemaphoreServiceServer interface {
+ // Create creates a new semaphore resource
+ // It initializes a semaphore for the resource in memory and persists it in the backend
+ //
+ // resource_id and n_permits are required fields
+ // the other fields are optional and if skipped will be set to default values
+ //
+ // The first request every client makes to the semaphore service should be a create request
+ //
+ // Only the first request received by the semaphore service will create the resource
+ // Any subsequent requests will be ignored
+ Create(context.Context, *CreateRequest) (*CreateResponse, error)
+ // Acquire acquires a lease for the resource or reserves a place in the queue
+ //
+ // Clients will poll this endpoint to acquire a lease for the resource
+ // On the first poll, they will either be granted a lease or enqueued
+ // If enqueued, the client should poll periodically to check if the lease is granted
+ //
+ // The client is enqueued in the priority queue corresponding to the access_priority
+ // Leases are granted according to priority that is,
+ // first all high priority clients are granted leases followed by the others in order
+ //
+ // A client can be starved by other clients with higher priority
+ // Consider implementing a client-side timeout for how long to wait for the lease
+ // before bypassing the semaphore access (if possible)
+ // The priority can not be bumped up once the client is enqueued (Future work)
+ //
+ // No need to send a heartbeat while the client is enqueued
+ // Heartbeats are updated implicitly on every Acquire request
+ Acquire(context.Context, *AcquireRequest) (*AcquireResponse, error)
+ // Release releases the lease on the resource
+ //
+ // If the lease has expired, the release request will be a noop
+ Release(context.Context, *ReleaseRequest) (*ReleaseResponse, error)
+ // Heartbeat keeps the lease alive for the resource
+ //
+ // Clients should periodically send heartbeats once they have acquired the lease
+ // If no heartbeat is received within the dead_node_timeout, the lease will be revoked
+ //
+ // Client should monitor the access status returned to check if the lease is still valid
+ // if the lease is lost the client should take the necessary action
+ //
+ //
+ Heartbeat(context.Context, *HeartbeatRequest) (*HeartbeatResponse, error)
+}
+
+func RegisterSemaphoreServiceServer(s *grpc.Server, srv SemaphoreServiceServer) {
+ s.RegisterService(&_SemaphoreService_serviceDesc, srv)
+}
+
+func _SemaphoreService_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(CreateRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(SemaphoreServiceServer).Create(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/operator.resourcegateway.SemaphoreService/Create",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(SemaphoreServiceServer).Create(ctx, req.(*CreateRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _SemaphoreService_Acquire_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(AcquireRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(SemaphoreServiceServer).Acquire(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/operator.resourcegateway.SemaphoreService/Acquire",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(SemaphoreServiceServer).Acquire(ctx, req.(*AcquireRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _SemaphoreService_Release_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(ReleaseRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(SemaphoreServiceServer).Release(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/operator.resourcegateway.SemaphoreService/Release",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(SemaphoreServiceServer).Release(ctx, req.(*ReleaseRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _SemaphoreService_Heartbeat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(HeartbeatRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(SemaphoreServiceServer).Heartbeat(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/operator.resourcegateway.SemaphoreService/Heartbeat",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(SemaphoreServiceServer).Heartbeat(ctx, req.(*HeartbeatRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+var _SemaphoreService_serviceDesc = grpc.ServiceDesc{
+ ServiceName: "operator.resourcegateway.SemaphoreService",
+ HandlerType: (*SemaphoreServiceServer)(nil),
+ Methods: []grpc.MethodDesc{
+ {
+ MethodName: "Create",
+ Handler: _SemaphoreService_Create_Handler,
+ },
+ {
+ MethodName: "Acquire",
+ Handler: _SemaphoreService_Acquire_Handler,
+ },
+ {
+ MethodName: "Release",
+ Handler: _SemaphoreService_Release_Handler,
+ },
+ {
+ MethodName: "Heartbeat",
+ Handler: _SemaphoreService_Heartbeat_Handler,
+ },
+ },
+ Streams: []grpc.StreamDesc{},
+ Metadata: "resource_gateway.proto",
+}
+
+func init() {
+ proto.RegisterFile("resource_gateway.proto", fileDescriptor_resource_gateway_1e8a27e27414e1c7)
+}
+
+var fileDescriptor_resource_gateway_1e8a27e27414e1c7 = []byte{
+ // 535 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x54, 0x51, 0x8f, 0xd2, 0x4c,
+ 0x14, 0xa5, 0x40, 0x58, 0xb8, 0x1f, 0x94, 0x32, 0xd9, 0x7c, 0x21, 0x68, 0x22, 0xa9, 0x0f, 0xe2,
+ 0x6e, 0xc4, 0x04, 0x7d, 0xdb, 0xa7, 0xdd, 0xa5, 0x2e, 0x4d, 0x16, 0xac, 0x05, 0x34, 0x9a, 0x98,
+ 0x3a, 0xdb, 0x5e, 0xb5, 0xc9, 0xc2, 0x74, 0x67, 0x06, 0x0d, 0x3f, 0x46, 0x1f, 0xfd, 0x9d, 0xa6,
+ 0x9d, 0x16, 0x81, 0x04, 0x76, 0x4d, 0xd6, 0xc7, 0xde, 0x7b, 0xe6, 0x9e, 0xd3, 0x73, 0xee, 0x0c,
+ 0xfc, 0xcf, 0x51, 0xb0, 0x05, 0xf7, 0xd1, 0xfb, 0x42, 0x25, 0x7e, 0xa7, 0xcb, 0x6e, 0xc4, 0x99,
+ 0x64, 0xa4, 0xc9, 0x22, 0xe4, 0x54, 0x32, 0xde, 0xcd, 0x00, 0x69, 0xdf, 0x1c, 0x82, 0x7e, 0xea,
+ 0xfb, 0x28, 0x84, 0xc3, 0x43, 0xc6, 0x43, 0xb9, 0x34, 0x4f, 0xa0, 0x38, 0x59, 0x46, 0x48, 0x0e,
+ 0xc1, 0x98, 0xbc, 0x77, 0x2c, 0x6f, 0x3a, 0x1a, 0x3b, 0xd6, 0xb9, 0xfd, 0xca, 0xb6, 0xfa, 0x46,
+ 0x8e, 0x1c, 0x40, 0xe1, 0xf2, 0xf5, 0x3b, 0x43, 0x23, 0x00, 0xa5, 0xa1, 0xd5, 0xb7, 0xa7, 0x43,
+ 0x23, 0x4f, 0xca, 0x50, 0x1c, 0xd8, 0x17, 0x03, 0xa3, 0x60, 0xf6, 0xa1, 0xaa, 0xc6, 0x8d, 0x25,
+ 0x95, 0x0b, 0x61, 0xbe, 0xdc, 0x3b, 0x0c, 0xa0, 0xf4, 0x66, 0x6a, 0x4d, 0xad, 0xbe, 0x9a, 0x77,
+ 0x69, 0x9d, 0x8e, 0xad, 0xbe, 0x91, 0x37, 0x7f, 0x6a, 0x50, 0x3b, 0xe7, 0x48, 0x25, 0xba, 0x78,
+ 0xb3, 0x40, 0x21, 0xc9, 0x23, 0xf8, 0x6f, 0xf5, 0x6b, 0x61, 0xd0, 0xd4, 0xda, 0x5a, 0xa7, 0xe2,
+ 0x42, 0x56, 0xb2, 0x03, 0xf2, 0x00, 0x2a, 0x73, 0x2f, 0x42, 0x3e, 0x0b, 0xa5, 0x68, 0xe6, 0xdb,
+ 0x5a, 0xa7, 0xe6, 0x96, 0xe7, 0x8e, 0xfa, 0x26, 0x8f, 0xa1, 0x76, 0x8d, 0x54, 0xa0, 0x27, 0xc3,
+ 0x19, 0xb2, 0x85, 0x6c, 0x16, 0xda, 0x5a, 0xa7, 0xe8, 0x56, 0x93, 0xe2, 0x44, 0xd5, 0xc8, 0x11,
+ 0x34, 0x02, 0xa4, 0x81, 0x37, 0x67, 0xc1, 0x1f, 0x60, 0x31, 0x01, 0xd6, 0xe3, 0xc6, 0x88, 0x05,
+ 0x19, 0xd6, 0x34, 0x40, 0xcf, 0xf4, 0x89, 0x88, 0xcd, 0x05, 0x9a, 0xbf, 0xb4, 0xd8, 0xc8, 0x9b,
+ 0x45, 0xc8, 0xff, 0x4a, 0xb3, 0x7f, 0x1d, 0xe2, 0x5c, 0xc6, 0xed, 0x7c, 0xd2, 0x2e, 0xab, 0x82,
+ 0x1d, 0x90, 0xb7, 0x50, 0xa7, 0x89, 0x93, 0x5e, 0x94, 0x26, 0x93, 0xa8, 0xd6, 0x7b, 0xcf, 0xba,
+ 0xbb, 0xc2, 0xec, 0x6e, 0x26, 0xd9, 0x8d, 0x9d, 0x77, 0x75, 0xba, 0x19, 0xaf, 0x0f, 0xf5, 0x95,
+ 0x4e, 0xa5, 0x9d, 0x38, 0x50, 0x4b, 0xa9, 0x44, 0x92, 0x5a, 0x22, 0x55, 0xef, 0x1d, 0xdf, 0x46,
+ 0xa4, 0x32, 0x56, 0x34, 0x55, 0xba, 0x1e, 0xfb, 0x08, 0x74, 0x17, 0x13, 0x77, 0xef, 0xc5, 0x0c,
+ 0xb3, 0x01, 0xf5, 0xd5, 0xbc, 0xd4, 0x70, 0x07, 0x8c, 0x01, 0x52, 0x2e, 0xaf, 0x90, 0xca, 0xfb,
+ 0x21, 0x41, 0x68, 0xac, 0x4d, 0xfc, 0x57, 0xde, 0xf4, 0x7e, 0x14, 0xc0, 0x18, 0xe3, 0x8c, 0x46,
+ 0x5f, 0x19, 0xc7, 0x31, 0xf2, 0x6f, 0xa1, 0x8f, 0xe4, 0x23, 0x94, 0xd4, 0x42, 0x91, 0x27, 0xbb,
+ 0x27, 0x6f, 0x5c, 0x89, 0x56, 0xe7, 0x76, 0x60, 0x6a, 0x55, 0x8e, 0x7c, 0x82, 0x83, 0x34, 0x74,
+ 0xd2, 0xd9, 0xa7, 0x7c, 0x7d, 0x7f, 0x5b, 0x4f, 0xef, 0x80, 0x5c, 0x67, 0x48, 0x13, 0xda, 0xc7,
+ 0xb0, 0xb9, 0x14, 0xfb, 0x18, 0xb6, 0xe3, 0xce, 0x91, 0xcf, 0x50, 0x59, 0xc5, 0x43, 0x8e, 0x76,
+ 0x9f, 0xdc, 0xde, 0x8a, 0xd6, 0xf1, 0x9d, 0xb0, 0x19, 0xcf, 0x99, 0x0d, 0x0f, 0x7d, 0x36, 0xdb,
+ 0x79, 0xe6, 0xec, 0xd0, 0x4d, 0x0b, 0x17, 0xaa, 0xe0, 0xc4, 0x2f, 0xac, 0xa3, 0x7d, 0x68, 0x3c,
+ 0x3f, 0xd9, 0x82, 0x5e, 0x95, 0x92, 0xd7, 0xf7, 0xc5, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0x30,
+ 0x19, 0x29, 0x38, 0x97, 0x05, 0x00, 0x00,
+}
diff --git a/proto/resource_gateway.proto b/proto/resource_gateway.proto
new file mode 100644
index 0000000000..2aedfff44f
--- /dev/null
+++ b/proto/resource_gateway.proto
@@ -0,0 +1,152 @@
+syntax = "proto3";
+
+package operator.resourcegateway;
+
+option go_package = "/;resourcegateway";
+option java_multiple_files = true;
+option java_package = "com.operator.resourcegateway";
+option java_outer_classname = "ResourceGatewayProto";
+
+// SemaphoreService provides a counting semaphore to limit access to resources
+// and a priority queue to distinguish between different types of clients/requests
+service SemaphoreService {
+ // Create creates a new semaphore resource
+ // It initializes a semaphore for the resource in memory and persists it in the backend
+ //
+ // resource_id and n_permits are required fields
+ // the other fields are optional and if skipped will be set to default values
+ //
+ // The first request every client makes to the semaphore service should be a create request
+ //
+ // Only the first request received by the semaphore service will create the resource
+ // Any subsequent requests will be ignored
+ rpc Create(CreateRequest)
+ returns (CreateResponse) {}
+
+ // Acquire acquires a lease for the resource or reserves a place in the queue
+ //
+ // Clients will poll this endpoint to acquire a lease for the resource
+ // On the first poll, they will either be granted a lease or enqueued
+ // If enqueued, the client should poll periodically to check if the lease is granted
+ //
+ // The client is enqueued in the priority queue corresponding to the access_priority
+ // Leases are granted according to priority that is,
+ // first all high priority clients are granted leases followed by the others in order
+ //
+ // A client can be starved by other clients with higher priority
+ // Consider implementing a client-side timeout for how long to wait for the lease
+ // before bypassing the semaphore access (if possible)
+ // The priority can not be bumped up once the client is enqueued (Future work)
+ //
+ // No need to send a heartbeat while the client is enqueued
+ // Heartbeats are updated implicitly on every Acquire request
+ rpc Acquire(AcquireRequest)
+ returns (AcquireResponse) {}
+
+ // Release releases the lease on the resource
+ //
+ // If the lease has expired, the release request will be a noop
+rpc Release (ReleaseRequest)
+ returns (ReleaseResponse) {}
+
+ // Heartbeat keeps the lease alive for the resource
+ //
+ // Clients should periodically send heartbeats once they have acquired the lease
+ // If no heartbeat is received within the dead_node_timeout, the lease will be revoked
+ //
+ // Client should monitor the access status returned to check if the lease is still valid
+ // if the lease is lost the client should take the necessary action
+ //
+ //
+ rpc Heartbeat (HeartbeatRequest)
+ returns (HeartbeatResponse) {}
+}
+
+// AccessPriority specifies the priority of a client's access to a semaphore resource
+message AccessPriority {
+ // Type of priority
+ enum Type {
+ // Unspecified, do NOT use
+ TYPE_UNSPECIFIED = 0;
+ // Enqueued to low priority queue
+ LOW = 1;
+ // Enqueued to medium priority queue
+ MEDIUM = 2;
+ // Enqueued to high priority queue
+ HIGH = 3;
+ }
+}
+
+// AccessStatus specifies the status of a client's access to a semaphore resource
+message AccessStatus{
+ // Type of status
+ enum Type {
+ // Unspecified, do NOT use
+ TYPE_UNSPECIFIED = 0;
+ // Enqueued for access to the resource
+ QUEUED = 1;
+ // Lease acquired for the resource
+ LEASED = 2;
+ }
+}
+
+// CreateRequest is the request to create a new semaphore resource
+//
+// resource_id and n_permits are required fields
+// the other fields are optional and if skipped will be set to default values
+message CreateRequest {
+ // Resource Id of the new semaphore resource
+ string resource_id = 1;
+ // Number of permits that can be leased out for a resource
+ uint32 n_permits = 2;
+ // Max duration for which a lease can be held
+ uint64 lease_timeout = 3;
+ // Max duration after which a client is considered dead if there is no heartbeat
+ uint64 dead_node_timeout = 4;
+}
+
+// CreateResponse is the response to create a new semaphore resource
+message CreateResponse {}
+
+// AcquireRequest is the request to acquire a lease for a semaphore resource
+message AcquireRequest {
+ // Resource Id to acquire the lease for
+ // It should be the same as the resource_id in the CreateRequest
+ string resource_id = 1;
+ // Client Id who is acquiring the lease
+ // client_id can be any unique identifier for the client
+ string client_id = 2;
+ // Priority of the client's access to the resource
+ AccessPriority.Type access_priority = 3;
+}
+
+// AcquireResponse is the response to acquire a semaphore lock
+message AcquireResponse {
+ // Status of the client's access to the resource
+ AccessStatus.Type access_status = 1;
+}
+
+// ReleaseRequest is the request to release a semaphore lock
+message ReleaseRequest {
+ // Resource Id to release the lease for
+ string resource_id = 1;
+ // Client Id who is releasing the lease
+ string client_id = 2;
+}
+
+// ReleaseResponse is the response to release a semaphore lock
+message ReleaseResponse {}
+
+// HeartbeatRequest is the request to send a heartbeat to the semaphore service
+message HeartbeatRequest {
+ // Resource ID to keep the lease alive for
+ string resource_id = 1;
+ // Client ID to keep the lease alive for
+ string client_id = 2;
+}
+
+// HeartbeatLockResponse is the response to keep a semaphore lock alive
+message HeartbeatResponse {
+ // Status of the client's access to the resource
+ AccessStatus.Type access_status = 1;
+}
diff --git a/vendor/github.com/cespare/xxhash/v2/README.md b/vendor/github.com/cespare/xxhash/v2/README.md
index 8bf0e5b781..33c88305c4 100644
--- a/vendor/github.com/cespare/xxhash/v2/README.md
+++ b/vendor/github.com/cespare/xxhash/v2/README.md
@@ -70,3 +70,5 @@ benchstat <(go test -benchtime 500ms -count 15 -bench 'Sum64$')
- [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
- [FreeCache](https://github.com/coocood/freecache)
- [FastCache](https://github.com/VictoriaMetrics/fastcache)
+- [Ristretto](https://github.com/dgraph-io/ristretto)
+- [Badger](https://github.com/dgraph-io/badger)
diff --git a/vendor/github.com/cespare/xxhash/v2/xxhash.go b/vendor/github.com/cespare/xxhash/v2/xxhash.go
index a9e0d45c9d..78bddf1cee 100644
--- a/vendor/github.com/cespare/xxhash/v2/xxhash.go
+++ b/vendor/github.com/cespare/xxhash/v2/xxhash.go
@@ -19,10 +19,13 @@ const (
// Store the primes in an array as well.
//
// The consts are used when possible in Go code to avoid MOVs but we need a
-// contiguous array of the assembly code.
+// contiguous array for the assembly code.
var primes = [...]uint64{prime1, prime2, prime3, prime4, prime5}
// Digest implements hash.Hash64.
+//
+// Note that a zero-valued Digest is not ready to receive writes.
+// Call Reset or create a Digest using New before calling other methods.
type Digest struct {
v1 uint64
v2 uint64
@@ -33,19 +36,31 @@ type Digest struct {
n int // how much of mem is used
}
-// New creates a new Digest that computes the 64-bit xxHash algorithm.
+// New creates a new Digest with a zero seed.
func New() *Digest {
+ return NewWithSeed(0)
+}
+
+// NewWithSeed creates a new Digest with the given seed.
+func NewWithSeed(seed uint64) *Digest {
var d Digest
- d.Reset()
+ d.ResetWithSeed(seed)
return &d
}
// Reset clears the Digest's state so that it can be reused.
+// It uses a seed value of zero.
func (d *Digest) Reset() {
- d.v1 = primes[0] + prime2
- d.v2 = prime2
- d.v3 = 0
- d.v4 = -primes[0]
+ d.ResetWithSeed(0)
+}
+
+// ResetWithSeed clears the Digest's state so that it can be reused.
+// It uses the given seed to initialize the state.
+func (d *Digest) ResetWithSeed(seed uint64) {
+ d.v1 = seed + prime1 + prime2
+ d.v2 = seed + prime2
+ d.v3 = seed
+ d.v4 = seed - prime1
d.total = 0
d.n = 0
}
diff --git a/vendor/github.com/cespare/xxhash/v2/xxhash_asm.go b/vendor/github.com/cespare/xxhash/v2/xxhash_asm.go
index 9216e0a40c..78f95f2561 100644
--- a/vendor/github.com/cespare/xxhash/v2/xxhash_asm.go
+++ b/vendor/github.com/cespare/xxhash/v2/xxhash_asm.go
@@ -6,7 +6,7 @@
package xxhash
-// Sum64 computes the 64-bit xxHash digest of b.
+// Sum64 computes the 64-bit xxHash digest of b with a zero seed.
//
//go:noescape
func Sum64(b []byte) uint64
diff --git a/vendor/github.com/cespare/xxhash/v2/xxhash_other.go b/vendor/github.com/cespare/xxhash/v2/xxhash_other.go
index 26df13bba4..118e49e819 100644
--- a/vendor/github.com/cespare/xxhash/v2/xxhash_other.go
+++ b/vendor/github.com/cespare/xxhash/v2/xxhash_other.go
@@ -3,7 +3,7 @@
package xxhash
-// Sum64 computes the 64-bit xxHash digest of b.
+// Sum64 computes the 64-bit xxHash digest of b with a zero seed.
func Sum64(b []byte) uint64 {
// A simpler version would be
// d := New()
diff --git a/vendor/github.com/cespare/xxhash/v2/xxhash_safe.go b/vendor/github.com/cespare/xxhash/v2/xxhash_safe.go
index e86f1b5fd8..05f5e7dfe7 100644
--- a/vendor/github.com/cespare/xxhash/v2/xxhash_safe.go
+++ b/vendor/github.com/cespare/xxhash/v2/xxhash_safe.go
@@ -5,7 +5,7 @@
package xxhash
-// Sum64String computes the 64-bit xxHash digest of s.
+// Sum64String computes the 64-bit xxHash digest of s with a zero seed.
func Sum64String(s string) uint64 {
return Sum64([]byte(s))
}
diff --git a/vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go b/vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go
index 1c1638fd88..cf9d42aed5 100644
--- a/vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go
+++ b/vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go
@@ -33,7 +33,7 @@ import (
//
// See https://github.com/golang/go/issues/42739 for discussion.
-// Sum64String computes the 64-bit xxHash digest of s.
+// Sum64String computes the 64-bit xxHash digest of s with a zero seed.
// It may be faster than Sum64([]byte(s)) by avoiding a copy.
func Sum64String(s string) uint64 {
b := *(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)}))
diff --git a/vendor/github.com/matttproud/golang_protobuf_extensions/LICENSE b/vendor/github.com/coreos/go-oidc/v3/LICENSE
similarity index 99%
rename from vendor/github.com/matttproud/golang_protobuf_extensions/LICENSE
rename to vendor/github.com/coreos/go-oidc/v3/LICENSE
index 8dada3edaf..e06d208186 100644
--- a/vendor/github.com/matttproud/golang_protobuf_extensions/LICENSE
+++ b/vendor/github.com/coreos/go-oidc/v3/LICENSE
@@ -1,4 +1,4 @@
- Apache License
+Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
@@ -199,3 +199,4 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+
diff --git a/vendor/github.com/coreos/go-oidc/v3/NOTICE b/vendor/github.com/coreos/go-oidc/v3/NOTICE
new file mode 100644
index 0000000000..b39ddfa5cb
--- /dev/null
+++ b/vendor/github.com/coreos/go-oidc/v3/NOTICE
@@ -0,0 +1,5 @@
+CoreOS Project
+Copyright 2014 CoreOS, Inc
+
+This product includes software developed at CoreOS, Inc.
+(http://www.coreos.com/).
diff --git a/vendor/github.com/coreos/go-oidc/v3/oidc/jose.go b/vendor/github.com/coreos/go-oidc/v3/oidc/jose.go
new file mode 100644
index 0000000000..f42d37d481
--- /dev/null
+++ b/vendor/github.com/coreos/go-oidc/v3/oidc/jose.go
@@ -0,0 +1,32 @@
+package oidc
+
+import jose "github.com/go-jose/go-jose/v4"
+
+// JOSE asymmetric signing algorithm values as defined by RFC 7518
+//
+// see: https://tools.ietf.org/html/rfc7518#section-3.1
+const (
+ RS256 = "RS256" // RSASSA-PKCS-v1.5 using SHA-256
+ RS384 = "RS384" // RSASSA-PKCS-v1.5 using SHA-384
+ RS512 = "RS512" // RSASSA-PKCS-v1.5 using SHA-512
+ ES256 = "ES256" // ECDSA using P-256 and SHA-256
+ ES384 = "ES384" // ECDSA using P-384 and SHA-384
+ ES512 = "ES512" // ECDSA using P-521 and SHA-512
+ PS256 = "PS256" // RSASSA-PSS using SHA256 and MGF1-SHA256
+ PS384 = "PS384" // RSASSA-PSS using SHA384 and MGF1-SHA384
+ PS512 = "PS512" // RSASSA-PSS using SHA512 and MGF1-SHA512
+ EdDSA = "EdDSA" // Ed25519 using SHA-512
+)
+
+var allAlgs = []jose.SignatureAlgorithm{
+ jose.RS256,
+ jose.RS384,
+ jose.RS512,
+ jose.ES256,
+ jose.ES384,
+ jose.ES512,
+ jose.PS256,
+ jose.PS384,
+ jose.PS512,
+ jose.EdDSA,
+}
diff --git a/vendor/github.com/coreos/go-oidc/v3/oidc/jwks.go b/vendor/github.com/coreos/go-oidc/v3/oidc/jwks.go
new file mode 100644
index 0000000000..6a846ece95
--- /dev/null
+++ b/vendor/github.com/coreos/go-oidc/v3/oidc/jwks.go
@@ -0,0 +1,269 @@
+package oidc
+
+import (
+ "context"
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/ed25519"
+ "crypto/rsa"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "sync"
+ "time"
+
+ jose "github.com/go-jose/go-jose/v4"
+)
+
+// StaticKeySet is a verifier that validates JWT against a static set of public keys.
+type StaticKeySet struct {
+ // PublicKeys used to verify the JWT. Supported types are *rsa.PublicKey and
+ // *ecdsa.PublicKey.
+ PublicKeys []crypto.PublicKey
+}
+
+// VerifySignature compares the signature against a static set of public keys.
+func (s *StaticKeySet) VerifySignature(ctx context.Context, jwt string) ([]byte, error) {
+ // Algorithms are already checked by Verifier, so this parse method accepts
+ // any algorithm.
+ jws, err := jose.ParseSigned(jwt, allAlgs)
+ if err != nil {
+ return nil, fmt.Errorf("parsing jwt: %v", err)
+ }
+ for _, pub := range s.PublicKeys {
+ switch pub.(type) {
+ case *rsa.PublicKey:
+ case *ecdsa.PublicKey:
+ case ed25519.PublicKey:
+ default:
+ return nil, fmt.Errorf("invalid public key type provided: %T", pub)
+ }
+ payload, err := jws.Verify(pub)
+ if err != nil {
+ continue
+ }
+ return payload, nil
+ }
+ return nil, fmt.Errorf("no public keys able to verify jwt")
+}
+
+// NewRemoteKeySet returns a KeySet that can validate JSON web tokens by using HTTP
+// GETs to fetch JSON web token sets hosted at a remote URL. This is automatically
+// used by NewProvider using the URLs returned by OpenID Connect discovery, but is
+// exposed for providers that don't support discovery or to prevent round trips to the
+// discovery URL.
+//
+// The returned KeySet is a long lived verifier that caches keys based on any
+// keys change. Reuse a common remote key set instead of creating new ones as needed.
+func NewRemoteKeySet(ctx context.Context, jwksURL string) *RemoteKeySet {
+ return newRemoteKeySet(ctx, jwksURL, time.Now)
+}
+
+func newRemoteKeySet(ctx context.Context, jwksURL string, now func() time.Time) *RemoteKeySet {
+ if now == nil {
+ now = time.Now
+ }
+ return &RemoteKeySet{
+ jwksURL: jwksURL,
+ now: now,
+ // For historical reasons, this package uses contexts for configuration, not just
+ // cancellation. In hindsight, this was a bad idea.
+ //
+ // Attemps to reason about how cancels should work with background requests have
+ // largely lead to confusion. Use the context here as a config bag-of-values and
+ // ignore the cancel function.
+ ctx: context.WithoutCancel(ctx),
+ }
+}
+
+// RemoteKeySet is a KeySet implementation that validates JSON web tokens against
+// a jwks_uri endpoint.
+type RemoteKeySet struct {
+ jwksURL string
+ now func() time.Time
+
+ // Used for configuration. Cancelation is ignored.
+ ctx context.Context
+
+ // guard all other fields
+ mu sync.RWMutex
+
+ // inflight suppresses parallel execution of updateKeys and allows
+ // multiple goroutines to wait for its result.
+ inflight *inflight
+
+ // A set of cached keys.
+ cachedKeys []jose.JSONWebKey
+}
+
+// inflight is used to wait on some in-flight request from multiple goroutines.
+type inflight struct {
+ doneCh chan struct{}
+
+ keys []jose.JSONWebKey
+ err error
+}
+
+func newInflight() *inflight {
+ return &inflight{doneCh: make(chan struct{})}
+}
+
+// wait returns a channel that multiple goroutines can receive on. Once it returns
+// a value, the inflight request is done and result() can be inspected.
+func (i *inflight) wait() <-chan struct{} {
+ return i.doneCh
+}
+
+// done can only be called by a single goroutine. It records the result of the
+// inflight request and signals other goroutines that the result is safe to
+// inspect.
+func (i *inflight) done(keys []jose.JSONWebKey, err error) {
+ i.keys = keys
+ i.err = err
+ close(i.doneCh)
+}
+
+// result cannot be called until the wait() channel has returned a value.
+func (i *inflight) result() ([]jose.JSONWebKey, error) {
+ return i.keys, i.err
+}
+
+// paresdJWTKey is a context key that allows common setups to avoid parsing the
+// JWT twice. It holds a *jose.JSONWebSignature value.
+var parsedJWTKey contextKey
+
+// VerifySignature validates a payload against a signature from the jwks_uri.
+//
+// Users MUST NOT call this method directly and should use an IDTokenVerifier
+// instead. This method skips critical validations such as 'alg' values and is
+// only exported to implement the KeySet interface.
+func (r *RemoteKeySet) VerifySignature(ctx context.Context, jwt string) ([]byte, error) {
+ jws, ok := ctx.Value(parsedJWTKey).(*jose.JSONWebSignature)
+ if !ok {
+ // The algorithm values are already enforced by the Validator, which also sets
+ // the context value above to pre-parsed signature.
+ //
+ // Practically, this codepath isn't called in normal use of this package, but
+ // if it is, the algorithms have already been checked.
+ var err error
+ jws, err = jose.ParseSigned(jwt, allAlgs)
+ if err != nil {
+ return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
+ }
+ }
+ return r.verify(ctx, jws)
+}
+
+func (r *RemoteKeySet) verify(ctx context.Context, jws *jose.JSONWebSignature) ([]byte, error) {
+ // We don't support JWTs signed with multiple signatures.
+ keyID := ""
+ for _, sig := range jws.Signatures {
+ keyID = sig.Header.KeyID
+ break
+ }
+
+ keys := r.keysFromCache()
+ for _, key := range keys {
+ if keyID == "" || key.KeyID == keyID {
+ if payload, err := jws.Verify(&key); err == nil {
+ return payload, nil
+ }
+ }
+ }
+
+ // If the kid doesn't match, check for new keys from the remote. This is the
+ // strategy recommended by the spec.
+ //
+ // https://openid.net/specs/openid-connect-core-1_0.html#RotateSigKeys
+ keys, err := r.keysFromRemote(ctx)
+ if err != nil {
+ return nil, fmt.Errorf("fetching keys %w", err)
+ }
+
+ for _, key := range keys {
+ if keyID == "" || key.KeyID == keyID {
+ if payload, err := jws.Verify(&key); err == nil {
+ return payload, nil
+ }
+ }
+ }
+ return nil, errors.New("failed to verify id token signature")
+}
+
+func (r *RemoteKeySet) keysFromCache() (keys []jose.JSONWebKey) {
+ r.mu.RLock()
+ defer r.mu.RUnlock()
+ return r.cachedKeys
+}
+
+// keysFromRemote syncs the key set from the remote set, records the values in the
+// cache, and returns the key set.
+func (r *RemoteKeySet) keysFromRemote(ctx context.Context) ([]jose.JSONWebKey, error) {
+ // Need to lock to inspect the inflight request field.
+ r.mu.Lock()
+ // If there's not a current inflight request, create one.
+ if r.inflight == nil {
+ r.inflight = newInflight()
+
+ // This goroutine has exclusive ownership over the current inflight
+ // request. It releases the resource by nil'ing the inflight field
+ // once the goroutine is done.
+ go func() {
+ // Sync keys and finish inflight when that's done.
+ keys, err := r.updateKeys()
+
+ r.inflight.done(keys, err)
+
+ // Lock to update the keys and indicate that there is no longer an
+ // inflight request.
+ r.mu.Lock()
+ defer r.mu.Unlock()
+
+ if err == nil {
+ r.cachedKeys = keys
+ }
+
+ // Free inflight so a different request can run.
+ r.inflight = nil
+ }()
+ }
+ inflight := r.inflight
+ r.mu.Unlock()
+
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ case <-inflight.wait():
+ return inflight.result()
+ }
+}
+
+func (r *RemoteKeySet) updateKeys() ([]jose.JSONWebKey, error) {
+ req, err := http.NewRequest("GET", r.jwksURL, nil)
+ if err != nil {
+ return nil, fmt.Errorf("oidc: can't create request: %v", err)
+ }
+
+ resp, err := doRequest(r.ctx, req)
+ if err != nil {
+ return nil, fmt.Errorf("oidc: get keys failed %w", err)
+ }
+ defer resp.Body.Close()
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, fmt.Errorf("unable to read response body: %v", err)
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("oidc: get keys failed: %s %s", resp.Status, body)
+ }
+
+ var keySet jose.JSONWebKeySet
+ err = unmarshalResp(resp, body, &keySet)
+ if err != nil {
+ return nil, fmt.Errorf("oidc: failed to decode keys: %v %s", err, body)
+ }
+ return keySet.Keys, nil
+}
diff --git a/vendor/github.com/coreos/go-oidc/v3/oidc/oidc.go b/vendor/github.com/coreos/go-oidc/v3/oidc/oidc.go
new file mode 100644
index 0000000000..17419f3883
--- /dev/null
+++ b/vendor/github.com/coreos/go-oidc/v3/oidc/oidc.go
@@ -0,0 +1,554 @@
+// Package oidc implements OpenID Connect client logic for the golang.org/x/oauth2 package.
+package oidc
+
+import (
+ "context"
+ "crypto/sha256"
+ "crypto/sha512"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "hash"
+ "io"
+ "mime"
+ "net/http"
+ "strings"
+ "sync"
+ "time"
+
+ "golang.org/x/oauth2"
+)
+
+const (
+ // ScopeOpenID is the mandatory scope for all OpenID Connect OAuth2 requests.
+ ScopeOpenID = "openid"
+
+ // ScopeOfflineAccess is an optional scope defined by OpenID Connect for requesting
+ // OAuth2 refresh tokens.
+ //
+ // Support for this scope differs between OpenID Connect providers. For instance
+ // Google rejects it, favoring appending "access_type=offline" as part of the
+ // authorization request instead.
+ //
+ // See: https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
+ ScopeOfflineAccess = "offline_access"
+)
+
+var (
+ errNoAtHash = errors.New("id token did not have an access token hash")
+ errInvalidAtHash = errors.New("access token hash does not match value in ID token")
+)
+
+type contextKey int
+
+var issuerURLKey contextKey
+
+// ClientContext returns a new Context that carries the provided HTTP client.
+//
+// This method sets the same context key used by the golang.org/x/oauth2 package,
+// so the returned context works for that package too.
+//
+// myClient := &http.Client{}
+// ctx := oidc.ClientContext(parentContext, myClient)
+//
+// // This will use the custom client
+// provider, err := oidc.NewProvider(ctx, "https://accounts.example.com")
+func ClientContext(ctx context.Context, client *http.Client) context.Context {
+ return context.WithValue(ctx, oauth2.HTTPClient, client)
+}
+
+func getClient(ctx context.Context) *http.Client {
+ if c, ok := ctx.Value(oauth2.HTTPClient).(*http.Client); ok {
+ return c
+ }
+ return nil
+}
+
+// InsecureIssuerURLContext allows discovery to work when the issuer_url reported
+// by upstream is mismatched with the discovery URL. This is meant for integration
+// with off-spec providers such as Azure.
+//
+// discoveryBaseURL := "https://login.microsoftonline.com/organizations/v2.0"
+// issuerURL := "https://login.microsoftonline.com/my-tenantid/v2.0"
+//
+// ctx := oidc.InsecureIssuerURLContext(parentContext, issuerURL)
+//
+// // Provider will be discovered with the discoveryBaseURL, but use issuerURL
+// // for future issuer validation.
+// provider, err := oidc.NewProvider(ctx, discoveryBaseURL)
+//
+// This is insecure because validating the correct issuer is critical for multi-tenant
+// providers. Any overrides here MUST be carefully reviewed.
+func InsecureIssuerURLContext(ctx context.Context, issuerURL string) context.Context {
+ return context.WithValue(ctx, issuerURLKey, issuerURL)
+}
+
+func doRequest(ctx context.Context, req *http.Request) (*http.Response, error) {
+ client := http.DefaultClient
+ if c := getClient(ctx); c != nil {
+ client = c
+ }
+ return client.Do(req.WithContext(ctx))
+}
+
+// Provider represents an OpenID Connect server's configuration.
+type Provider struct {
+ issuer string
+ authURL string
+ tokenURL string
+ deviceAuthURL string
+ userInfoURL string
+ jwksURL string
+ algorithms []string
+
+ // Raw claims returned by the server.
+ rawClaims []byte
+
+ // Guards all of the following fields.
+ mu sync.Mutex
+ // HTTP client specified from the initial NewProvider request. This is used
+ // when creating the common key set.
+ client *http.Client
+ // A key set that uses context.Background() and is shared between all code paths
+ // that don't have a convinent way of supplying a unique context.
+ commonRemoteKeySet KeySet
+}
+
+func (p *Provider) remoteKeySet() KeySet {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+ if p.commonRemoteKeySet == nil {
+ ctx := context.Background()
+ if p.client != nil {
+ ctx = ClientContext(ctx, p.client)
+ }
+ p.commonRemoteKeySet = NewRemoteKeySet(ctx, p.jwksURL)
+ }
+ return p.commonRemoteKeySet
+}
+
+type providerJSON struct {
+ Issuer string `json:"issuer"`
+ AuthURL string `json:"authorization_endpoint"`
+ TokenURL string `json:"token_endpoint"`
+ DeviceAuthURL string `json:"device_authorization_endpoint"`
+ JWKSURL string `json:"jwks_uri"`
+ UserInfoURL string `json:"userinfo_endpoint"`
+ Algorithms []string `json:"id_token_signing_alg_values_supported"`
+}
+
+// supportedAlgorithms is a list of algorithms explicitly supported by this
+// package. If a provider supports other algorithms, such as HS256 or none,
+// those values won't be passed to the IDTokenVerifier.
+var supportedAlgorithms = map[string]bool{
+ RS256: true,
+ RS384: true,
+ RS512: true,
+ ES256: true,
+ ES384: true,
+ ES512: true,
+ PS256: true,
+ PS384: true,
+ PS512: true,
+ EdDSA: true,
+}
+
+// ProviderConfig allows creating providers when discovery isn't supported. It's
+// generally easier to use NewProvider directly.
+type ProviderConfig struct {
+ // IssuerURL is the identity of the provider, and the string it uses to sign
+ // ID tokens with. For example "https://accounts.google.com". This value MUST
+ // match ID tokens exactly.
+ IssuerURL string
+ // AuthURL is the endpoint used by the provider to support the OAuth 2.0
+ // authorization endpoint.
+ AuthURL string
+ // TokenURL is the endpoint used by the provider to support the OAuth 2.0
+ // token endpoint.
+ TokenURL string
+ // DeviceAuthURL is the endpoint used by the provider to support the OAuth 2.0
+ // device authorization endpoint.
+ DeviceAuthURL string
+ // UserInfoURL is the endpoint used by the provider to support the OpenID
+ // Connect UserInfo flow.
+ //
+ // https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
+ UserInfoURL string
+ // JWKSURL is the endpoint used by the provider to advertise public keys to
+ // verify issued ID tokens. This endpoint is polled as new keys are made
+ // available.
+ JWKSURL string
+
+ // Algorithms, if provided, indicate a list of JWT algorithms allowed to sign
+ // ID tokens. If not provided, this defaults to the algorithms advertised by
+ // the JWK endpoint, then the set of algorithms supported by this package.
+ Algorithms []string
+}
+
+// NewProvider initializes a provider from a set of endpoints, rather than
+// through discovery.
+func (p *ProviderConfig) NewProvider(ctx context.Context) *Provider {
+ return &Provider{
+ issuer: p.IssuerURL,
+ authURL: p.AuthURL,
+ tokenURL: p.TokenURL,
+ deviceAuthURL: p.DeviceAuthURL,
+ userInfoURL: p.UserInfoURL,
+ jwksURL: p.JWKSURL,
+ algorithms: p.Algorithms,
+ client: getClient(ctx),
+ }
+}
+
+// NewProvider uses the OpenID Connect discovery mechanism to construct a Provider.
+//
+// The issuer is the URL identifier for the service. For example: "https://accounts.google.com"
+// or "https://login.salesforce.com".
+func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
+ wellKnown := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
+ req, err := http.NewRequest("GET", wellKnown, nil)
+ if err != nil {
+ return nil, err
+ }
+ resp, err := doRequest(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, fmt.Errorf("unable to read response body: %v", err)
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("%s: %s", resp.Status, body)
+ }
+
+ var p providerJSON
+ err = unmarshalResp(resp, body, &p)
+ if err != nil {
+ return nil, fmt.Errorf("oidc: failed to decode provider discovery object: %v", err)
+ }
+
+ issuerURL, skipIssuerValidation := ctx.Value(issuerURLKey).(string)
+ if !skipIssuerValidation {
+ issuerURL = issuer
+ }
+ if p.Issuer != issuerURL && !skipIssuerValidation {
+ return nil, fmt.Errorf("oidc: issuer did not match the issuer returned by provider, expected %q got %q", issuer, p.Issuer)
+ }
+ var algs []string
+ for _, a := range p.Algorithms {
+ if supportedAlgorithms[a] {
+ algs = append(algs, a)
+ }
+ }
+ return &Provider{
+ issuer: issuerURL,
+ authURL: p.AuthURL,
+ tokenURL: p.TokenURL,
+ deviceAuthURL: p.DeviceAuthURL,
+ userInfoURL: p.UserInfoURL,
+ jwksURL: p.JWKSURL,
+ algorithms: algs,
+ rawClaims: body,
+ client: getClient(ctx),
+ }, nil
+}
+
+// Claims unmarshals raw fields returned by the server during discovery.
+//
+// var claims struct {
+// ScopesSupported []string `json:"scopes_supported"`
+// ClaimsSupported []string `json:"claims_supported"`
+// }
+//
+// if err := provider.Claims(&claims); err != nil {
+// // handle unmarshaling error
+// }
+//
+// For a list of fields defined by the OpenID Connect spec see:
+// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
+func (p *Provider) Claims(v interface{}) error {
+ if p.rawClaims == nil {
+ return errors.New("oidc: claims not set")
+ }
+ return json.Unmarshal(p.rawClaims, v)
+}
+
+// Endpoint returns the OAuth2 auth and token endpoints for the given provider.
+func (p *Provider) Endpoint() oauth2.Endpoint {
+ return oauth2.Endpoint{AuthURL: p.authURL, DeviceAuthURL: p.deviceAuthURL, TokenURL: p.tokenURL}
+}
+
+// UserInfoEndpoint returns the OpenID Connect userinfo endpoint for the given
+// provider.
+func (p *Provider) UserInfoEndpoint() string {
+ return p.userInfoURL
+}
+
+// UserInfo represents the OpenID Connect userinfo claims.
+type UserInfo struct {
+ Subject string `json:"sub"`
+ Profile string `json:"profile"`
+ Email string `json:"email"`
+ EmailVerified bool `json:"email_verified"`
+
+ claims []byte
+}
+
+type userInfoRaw struct {
+ Subject string `json:"sub"`
+ Profile string `json:"profile"`
+ Email string `json:"email"`
+ // Handle providers that return email_verified as a string
+ // https://forums.aws.amazon.com/thread.jspa?messageID=949441 and
+ // https://discuss.elastic.co/t/openid-error-after-authenticating-against-aws-cognito/206018/11
+ EmailVerified stringAsBool `json:"email_verified"`
+}
+
+// Claims unmarshals the raw JSON object claims into the provided object.
+func (u *UserInfo) Claims(v interface{}) error {
+ if u.claims == nil {
+ return errors.New("oidc: claims not set")
+ }
+ return json.Unmarshal(u.claims, v)
+}
+
+// UserInfo uses the token source to query the provider's user info endpoint.
+func (p *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource) (*UserInfo, error) {
+ if p.userInfoURL == "" {
+ return nil, errors.New("oidc: user info endpoint is not supported by this provider")
+ }
+
+ req, err := http.NewRequest("GET", p.userInfoURL, nil)
+ if err != nil {
+ return nil, fmt.Errorf("oidc: create GET request: %v", err)
+ }
+
+ token, err := tokenSource.Token()
+ if err != nil {
+ return nil, fmt.Errorf("oidc: get access token: %v", err)
+ }
+ token.SetAuthHeader(req)
+
+ resp, err := doRequest(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("%s: %s", resp.Status, body)
+ }
+
+ ct := resp.Header.Get("Content-Type")
+ mediaType, _, parseErr := mime.ParseMediaType(ct)
+ if parseErr == nil && mediaType == "application/jwt" {
+ payload, err := p.remoteKeySet().VerifySignature(ctx, string(body))
+ if err != nil {
+ return nil, fmt.Errorf("oidc: invalid userinfo jwt signature %v", err)
+ }
+ body = payload
+ }
+
+ var userInfo userInfoRaw
+ if err := json.Unmarshal(body, &userInfo); err != nil {
+ return nil, fmt.Errorf("oidc: failed to decode userinfo: %v", err)
+ }
+ return &UserInfo{
+ Subject: userInfo.Subject,
+ Profile: userInfo.Profile,
+ Email: userInfo.Email,
+ EmailVerified: bool(userInfo.EmailVerified),
+ claims: body,
+ }, nil
+}
+
+// IDToken is an OpenID Connect extension that provides a predictable representation
+// of an authorization event.
+//
+// The ID Token only holds fields OpenID Connect requires. To access additional
+// claims returned by the server, use the Claims method.
+type IDToken struct {
+ // The URL of the server which issued this token. OpenID Connect
+ // requires this value always be identical to the URL used for
+ // initial discovery.
+ //
+ // Note: Because of a known issue with Google Accounts' implementation
+ // this value may differ when using Google.
+ //
+ // See: https://developers.google.com/identity/protocols/OpenIDConnect#obtainuserinfo
+ Issuer string
+
+ // The client ID, or set of client IDs, that this token is issued for. For
+ // common uses, this is the client that initialized the auth flow.
+ //
+ // This package ensures the audience contains an expected value.
+ Audience []string
+
+ // A unique string which identifies the end user.
+ Subject string
+
+ // Expiry of the token. Ths package will not process tokens that have
+ // expired unless that validation is explicitly turned off.
+ Expiry time.Time
+ // When the token was issued by the provider.
+ IssuedAt time.Time
+
+ // Initial nonce provided during the authentication redirect.
+ //
+ // This package does NOT provided verification on the value of this field
+ // and it's the user's responsibility to ensure it contains a valid value.
+ Nonce string
+
+ // at_hash claim, if set in the ID token. Callers can verify an access token
+ // that corresponds to the ID token using the VerifyAccessToken method.
+ AccessTokenHash string
+
+ // signature algorithm used for ID token, needed to compute a verification hash of an
+ // access token
+ sigAlgorithm string
+
+ // Raw payload of the id_token.
+ claims []byte
+
+ // Map of distributed claim names to claim sources
+ distributedClaims map[string]claimSource
+}
+
+// Claims unmarshals the raw JSON payload of the ID Token into a provided struct.
+//
+// idToken, err := idTokenVerifier.Verify(rawIDToken)
+// if err != nil {
+// // handle error
+// }
+// var claims struct {
+// Email string `json:"email"`
+// EmailVerified bool `json:"email_verified"`
+// }
+// if err := idToken.Claims(&claims); err != nil {
+// // handle error
+// }
+func (i *IDToken) Claims(v interface{}) error {
+ if i.claims == nil {
+ return errors.New("oidc: claims not set")
+ }
+ return json.Unmarshal(i.claims, v)
+}
+
+// VerifyAccessToken verifies that the hash of the access token that corresponds to the iD token
+// matches the hash in the id token. It returns an error if the hashes don't match.
+// It is the caller's responsibility to ensure that the optional access token hash is present for the ID token
+// before calling this method. See https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
+func (i *IDToken) VerifyAccessToken(accessToken string) error {
+ if i.AccessTokenHash == "" {
+ return errNoAtHash
+ }
+ var h hash.Hash
+ switch i.sigAlgorithm {
+ case RS256, ES256, PS256:
+ h = sha256.New()
+ case RS384, ES384, PS384:
+ h = sha512.New384()
+ case RS512, ES512, PS512, EdDSA:
+ h = sha512.New()
+ default:
+ return fmt.Errorf("oidc: unsupported signing algorithm %q", i.sigAlgorithm)
+ }
+ h.Write([]byte(accessToken)) // hash documents that Write will never return an error
+ sum := h.Sum(nil)[:h.Size()/2]
+ actual := base64.RawURLEncoding.EncodeToString(sum)
+ if actual != i.AccessTokenHash {
+ return errInvalidAtHash
+ }
+ return nil
+}
+
+type idToken struct {
+ Issuer string `json:"iss"`
+ Subject string `json:"sub"`
+ Audience audience `json:"aud"`
+ Expiry jsonTime `json:"exp"`
+ IssuedAt jsonTime `json:"iat"`
+ NotBefore *jsonTime `json:"nbf"`
+ Nonce string `json:"nonce"`
+ AtHash string `json:"at_hash"`
+ ClaimNames map[string]string `json:"_claim_names"`
+ ClaimSources map[string]claimSource `json:"_claim_sources"`
+}
+
+type claimSource struct {
+ Endpoint string `json:"endpoint"`
+ AccessToken string `json:"access_token"`
+}
+
+type stringAsBool bool
+
+func (sb *stringAsBool) UnmarshalJSON(b []byte) error {
+ switch string(b) {
+ case "true", `"true"`:
+ *sb = true
+ case "false", `"false"`:
+ *sb = false
+ default:
+ return errors.New("invalid value for boolean")
+ }
+ return nil
+}
+
+type audience []string
+
+func (a *audience) UnmarshalJSON(b []byte) error {
+ var s string
+ if json.Unmarshal(b, &s) == nil {
+ *a = audience{s}
+ return nil
+ }
+ var auds []string
+ if err := json.Unmarshal(b, &auds); err != nil {
+ return err
+ }
+ *a = auds
+ return nil
+}
+
+type jsonTime time.Time
+
+func (j *jsonTime) UnmarshalJSON(b []byte) error {
+ var n json.Number
+ if err := json.Unmarshal(b, &n); err != nil {
+ return err
+ }
+ var unix int64
+
+ if t, err := n.Int64(); err == nil {
+ unix = t
+ } else {
+ f, err := n.Float64()
+ if err != nil {
+ return err
+ }
+ unix = int64(f)
+ }
+ *j = jsonTime(time.Unix(unix, 0))
+ return nil
+}
+
+func unmarshalResp(r *http.Response, body []byte, v interface{}) error {
+ err := json.Unmarshal(body, &v)
+ if err == nil {
+ return nil
+ }
+ ct := r.Header.Get("Content-Type")
+ mediaType, _, parseErr := mime.ParseMediaType(ct)
+ if parseErr == nil && mediaType == "application/json" {
+ return fmt.Errorf("got Content-Type = application/json, but could not unmarshal as JSON: %v", err)
+ }
+ return fmt.Errorf("expected Content-Type = application/json, got %q: %v", ct, err)
+}
diff --git a/vendor/github.com/coreos/go-oidc/v3/oidc/verify.go b/vendor/github.com/coreos/go-oidc/v3/oidc/verify.go
new file mode 100644
index 0000000000..52b27b746a
--- /dev/null
+++ b/vendor/github.com/coreos/go-oidc/v3/oidc/verify.go
@@ -0,0 +1,355 @@
+package oidc
+
+import (
+ "bytes"
+ "context"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+ "time"
+
+ jose "github.com/go-jose/go-jose/v4"
+ "golang.org/x/oauth2"
+)
+
+const (
+ issuerGoogleAccounts = "https://accounts.google.com"
+ issuerGoogleAccountsNoScheme = "accounts.google.com"
+)
+
+// TokenExpiredError indicates that Verify failed because the token was expired. This
+// error does NOT indicate that the token is not also invalid for other reasons. Other
+// checks might have failed if the expiration check had not failed.
+type TokenExpiredError struct {
+ // Expiry is the time when the token expired.
+ Expiry time.Time
+}
+
+func (e *TokenExpiredError) Error() string {
+ return fmt.Sprintf("oidc: token is expired (Token Expiry: %v)", e.Expiry)
+}
+
+// KeySet is a set of publc JSON Web Keys that can be used to validate the signature
+// of JSON web tokens. This is expected to be backed by a remote key set through
+// provider metadata discovery or an in-memory set of keys delivered out-of-band.
+type KeySet interface {
+ // VerifySignature parses the JSON web token, verifies the signature, and returns
+ // the raw payload. Header and claim fields are validated by other parts of the
+ // package. For example, the KeySet does not need to check values such as signature
+ // algorithm, issuer, and audience since the IDTokenVerifier validates these values
+ // independently.
+ //
+ // If VerifySignature makes HTTP requests to verify the token, it's expected to
+ // use any HTTP client associated with the context through ClientContext.
+ VerifySignature(ctx context.Context, jwt string) (payload []byte, err error)
+}
+
+// IDTokenVerifier provides verification for ID Tokens.
+type IDTokenVerifier struct {
+ keySet KeySet
+ config *Config
+ issuer string
+}
+
+// NewVerifier returns a verifier manually constructed from a key set and issuer URL.
+//
+// It's easier to use provider discovery to construct an IDTokenVerifier than creating
+// one directly. This method is intended to be used with provider that don't support
+// metadata discovery, or avoiding round trips when the key set URL is already known.
+//
+// This constructor can be used to create a verifier directly using the issuer URL and
+// JSON Web Key Set URL without using discovery:
+//
+// keySet := oidc.NewRemoteKeySet(ctx, "https://www.googleapis.com/oauth2/v3/certs")
+// verifier := oidc.NewVerifier("https://accounts.google.com", keySet, config)
+//
+// Or a static key set (e.g. for testing):
+//
+// keySet := &oidc.StaticKeySet{PublicKeys: []crypto.PublicKey{pub1, pub2}}
+// verifier := oidc.NewVerifier("https://accounts.google.com", keySet, config)
+func NewVerifier(issuerURL string, keySet KeySet, config *Config) *IDTokenVerifier {
+ return &IDTokenVerifier{keySet: keySet, config: config, issuer: issuerURL}
+}
+
+// Config is the configuration for an IDTokenVerifier.
+type Config struct {
+ // Expected audience of the token. For a majority of the cases this is expected to be
+ // the ID of the client that initialized the login flow. It may occasionally differ if
+ // the provider supports the authorizing party (azp) claim.
+ //
+ // If not provided, users must explicitly set SkipClientIDCheck.
+ ClientID string
+ // If specified, only this set of algorithms may be used to sign the JWT.
+ //
+ // If the IDTokenVerifier is created from a provider with (*Provider).Verifier, this
+ // defaults to the set of algorithms the provider supports. Otherwise this values
+ // defaults to RS256.
+ SupportedSigningAlgs []string
+
+ // If true, no ClientID check performed. Must be true if ClientID field is empty.
+ SkipClientIDCheck bool
+ // If true, token expiry is not checked.
+ SkipExpiryCheck bool
+
+ // SkipIssuerCheck is intended for specialized cases where the the caller wishes to
+ // defer issuer validation. When enabled, callers MUST independently verify the Token's
+ // Issuer is a known good value.
+ //
+ // Mismatched issuers often indicate client mis-configuration. If mismatches are
+ // unexpected, evaluate if the provided issuer URL is incorrect instead of enabling
+ // this option.
+ SkipIssuerCheck bool
+
+ // Time function to check Token expiry. Defaults to time.Now
+ Now func() time.Time
+
+ // InsecureSkipSignatureCheck causes this package to skip JWT signature validation.
+ // It's intended for special cases where providers (such as Azure), use the "none"
+ // algorithm.
+ //
+ // This option can only be enabled safely when the ID Token is received directly
+ // from the provider after the token exchange.
+ //
+ // This option MUST NOT be used when receiving an ID Token from sources other
+ // than the token endpoint.
+ InsecureSkipSignatureCheck bool
+}
+
+// VerifierContext returns an IDTokenVerifier that uses the provider's key set to
+// verify JWTs. As opposed to Verifier, the context is used to configure requests
+// to the upstream JWKs endpoint. The provided context's cancellation is ignored.
+func (p *Provider) VerifierContext(ctx context.Context, config *Config) *IDTokenVerifier {
+ return p.newVerifier(NewRemoteKeySet(ctx, p.jwksURL), config)
+}
+
+// Verifier returns an IDTokenVerifier that uses the provider's key set to verify JWTs.
+//
+// The returned verifier uses a background context for all requests to the upstream
+// JWKs endpoint. To control that context, use VerifierContext instead.
+func (p *Provider) Verifier(config *Config) *IDTokenVerifier {
+ return p.newVerifier(p.remoteKeySet(), config)
+}
+
+func (p *Provider) newVerifier(keySet KeySet, config *Config) *IDTokenVerifier {
+ if len(config.SupportedSigningAlgs) == 0 && len(p.algorithms) > 0 {
+ // Make a copy so we don't modify the config values.
+ cp := &Config{}
+ *cp = *config
+ cp.SupportedSigningAlgs = p.algorithms
+ config = cp
+ }
+ return NewVerifier(p.issuer, keySet, config)
+}
+
+func parseJWT(p string) ([]byte, error) {
+ parts := strings.Split(p, ".")
+ if len(parts) < 2 {
+ return nil, fmt.Errorf("oidc: malformed jwt, expected 3 parts got %d", len(parts))
+ }
+ payload, err := base64.RawURLEncoding.DecodeString(parts[1])
+ if err != nil {
+ return nil, fmt.Errorf("oidc: malformed jwt payload: %v", err)
+ }
+ return payload, nil
+}
+
+func contains(sli []string, ele string) bool {
+ for _, s := range sli {
+ if s == ele {
+ return true
+ }
+ }
+ return false
+}
+
+// Returns the Claims from the distributed JWT token
+func resolveDistributedClaim(ctx context.Context, verifier *IDTokenVerifier, src claimSource) ([]byte, error) {
+ req, err := http.NewRequest("GET", src.Endpoint, nil)
+ if err != nil {
+ return nil, fmt.Errorf("malformed request: %v", err)
+ }
+ if src.AccessToken != "" {
+ req.Header.Set("Authorization", "Bearer "+src.AccessToken)
+ }
+
+ resp, err := doRequest(ctx, req)
+ if err != nil {
+ return nil, fmt.Errorf("oidc: Request to endpoint failed: %v", err)
+ }
+ defer resp.Body.Close()
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, fmt.Errorf("unable to read response body: %v", err)
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("oidc: request failed: %v", resp.StatusCode)
+ }
+
+ token, err := verifier.Verify(ctx, string(body))
+ if err != nil {
+ return nil, fmt.Errorf("malformed response body: %v", err)
+ }
+
+ return token.claims, nil
+}
+
+// Verify parses a raw ID Token, verifies it's been signed by the provider, performs
+// any additional checks depending on the Config, and returns the payload.
+//
+// Verify does NOT do nonce validation, which is the callers responsibility.
+//
+// See: https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
+//
+// oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
+// if err != nil {
+// // handle error
+// }
+//
+// // Extract the ID Token from oauth2 token.
+// rawIDToken, ok := oauth2Token.Extra("id_token").(string)
+// if !ok {
+// // handle error
+// }
+//
+// token, err := verifier.Verify(ctx, rawIDToken)
+func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*IDToken, error) {
+ // Throw out tokens with invalid claims before trying to verify the token. This lets
+ // us do cheap checks before possibly re-syncing keys.
+ payload, err := parseJWT(rawIDToken)
+ if err != nil {
+ return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
+ }
+ var token idToken
+ if err := json.Unmarshal(payload, &token); err != nil {
+ return nil, fmt.Errorf("oidc: failed to unmarshal claims: %v", err)
+ }
+
+ distributedClaims := make(map[string]claimSource)
+
+ //step through the token to map claim names to claim sources"
+ for cn, src := range token.ClaimNames {
+ if src == "" {
+ return nil, fmt.Errorf("oidc: failed to obtain source from claim name")
+ }
+ s, ok := token.ClaimSources[src]
+ if !ok {
+ return nil, fmt.Errorf("oidc: source does not exist")
+ }
+ distributedClaims[cn] = s
+ }
+
+ t := &IDToken{
+ Issuer: token.Issuer,
+ Subject: token.Subject,
+ Audience: []string(token.Audience),
+ Expiry: time.Time(token.Expiry),
+ IssuedAt: time.Time(token.IssuedAt),
+ Nonce: token.Nonce,
+ AccessTokenHash: token.AtHash,
+ claims: payload,
+ distributedClaims: distributedClaims,
+ }
+
+ // Check issuer.
+ if !v.config.SkipIssuerCheck && t.Issuer != v.issuer {
+ // Google sometimes returns "accounts.google.com" as the issuer claim instead of
+ // the required "https://accounts.google.com". Detect this case and allow it only
+ // for Google.
+ //
+ // We will not add hooks to let other providers go off spec like this.
+ if !(v.issuer == issuerGoogleAccounts && t.Issuer == issuerGoogleAccountsNoScheme) {
+ return nil, fmt.Errorf("oidc: id token issued by a different provider, expected %q got %q", v.issuer, t.Issuer)
+ }
+ }
+
+ // If a client ID has been provided, make sure it's part of the audience. SkipClientIDCheck must be true if ClientID is empty.
+ //
+ // This check DOES NOT ensure that the ClientID is the party to which the ID Token was issued (i.e. Authorized party).
+ if !v.config.SkipClientIDCheck {
+ if v.config.ClientID != "" {
+ if !contains(t.Audience, v.config.ClientID) {
+ return nil, fmt.Errorf("oidc: expected audience %q got %q", v.config.ClientID, t.Audience)
+ }
+ } else {
+ return nil, fmt.Errorf("oidc: invalid configuration, clientID must be provided or SkipClientIDCheck must be set")
+ }
+ }
+
+ // If a SkipExpiryCheck is false, make sure token is not expired.
+ if !v.config.SkipExpiryCheck {
+ now := time.Now
+ if v.config.Now != nil {
+ now = v.config.Now
+ }
+ nowTime := now()
+
+ if t.Expiry.Before(nowTime) {
+ return nil, &TokenExpiredError{Expiry: t.Expiry}
+ }
+
+ // If nbf claim is provided in token, ensure that it is indeed in the past.
+ if token.NotBefore != nil {
+ nbfTime := time.Time(*token.NotBefore)
+ // Set to 5 minutes since this is what other OpenID Connect providers do to deal with clock skew.
+ // https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/6.12.2/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs#L149-L153
+ leeway := 5 * time.Minute
+
+ if nowTime.Add(leeway).Before(nbfTime) {
+ return nil, fmt.Errorf("oidc: current time %v before the nbf (not before) time: %v", nowTime, nbfTime)
+ }
+ }
+ }
+
+ if v.config.InsecureSkipSignatureCheck {
+ return t, nil
+ }
+
+ var supportedSigAlgs []jose.SignatureAlgorithm
+ for _, alg := range v.config.SupportedSigningAlgs {
+ supportedSigAlgs = append(supportedSigAlgs, jose.SignatureAlgorithm(alg))
+ }
+ if len(supportedSigAlgs) == 0 {
+ // If no algorithms were specified by both the config and discovery, default
+ // to the one mandatory algorithm "RS256".
+ supportedSigAlgs = []jose.SignatureAlgorithm{jose.RS256}
+ }
+ jws, err := jose.ParseSigned(rawIDToken, supportedSigAlgs)
+ if err != nil {
+ return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
+ }
+
+ switch len(jws.Signatures) {
+ case 0:
+ return nil, fmt.Errorf("oidc: id token not signed")
+ case 1:
+ default:
+ return nil, fmt.Errorf("oidc: multiple signatures on id token not supported")
+ }
+ sig := jws.Signatures[0]
+ t.sigAlgorithm = sig.Header.Algorithm
+
+ ctx = context.WithValue(ctx, parsedJWTKey, jws)
+ gotPayload, err := v.keySet.VerifySignature(ctx, rawIDToken)
+ if err != nil {
+ return nil, fmt.Errorf("failed to verify signature: %v", err)
+ }
+
+ // Ensure that the payload returned by the square actually matches the payload parsed earlier.
+ if !bytes.Equal(gotPayload, payload) {
+ return nil, errors.New("oidc: internal error, payload parsed did not match previous payload")
+ }
+
+ return t, nil
+}
+
+// Nonce returns an auth code option which requires the ID Token created by the
+// OpenID Connect provider to contain the specified nonce.
+func Nonce(nonce string) oauth2.AuthCodeOption {
+ return oauth2.SetAuthURLParam("nonce", nonce)
+}
diff --git a/vendor/github.com/go-jose/go-jose/v4/.gitignore b/vendor/github.com/go-jose/go-jose/v4/.gitignore
new file mode 100644
index 0000000000..eb29ebaefd
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v4/.gitignore
@@ -0,0 +1,2 @@
+jose-util/jose-util
+jose-util.t.err
\ No newline at end of file
diff --git a/vendor/github.com/go-jose/go-jose/v4/.golangci.yml b/vendor/github.com/go-jose/go-jose/v4/.golangci.yml
new file mode 100644
index 0000000000..2a577a8f95
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v4/.golangci.yml
@@ -0,0 +1,53 @@
+# https://github.com/golangci/golangci-lint
+
+run:
+ skip-files:
+ - doc_test.go
+ modules-download-mode: readonly
+
+linters:
+ enable-all: true
+ disable:
+ - gochecknoglobals
+ - goconst
+ - lll
+ - maligned
+ - nakedret
+ - scopelint
+ - unparam
+ - funlen # added in 1.18 (requires go-jose changes before it can be enabled)
+
+linters-settings:
+ gocyclo:
+ min-complexity: 35
+
+issues:
+ exclude-rules:
+ - text: "don't use ALL_CAPS in Go names"
+ linters:
+ - golint
+ - text: "hardcoded credentials"
+ linters:
+ - gosec
+ - text: "weak cryptographic primitive"
+ linters:
+ - gosec
+ - path: json/
+ linters:
+ - dupl
+ - errcheck
+ - gocritic
+ - gocyclo
+ - golint
+ - govet
+ - ineffassign
+ - staticcheck
+ - structcheck
+ - stylecheck
+ - unused
+ - path: _test\.go
+ linters:
+ - scopelint
+ - path: jwk.go
+ linters:
+ - gocyclo
diff --git a/vendor/github.com/go-jose/go-jose/v4/.travis.yml b/vendor/github.com/go-jose/go-jose/v4/.travis.yml
new file mode 100644
index 0000000000..48de631b00
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v4/.travis.yml
@@ -0,0 +1,33 @@
+language: go
+
+matrix:
+ fast_finish: true
+ allow_failures:
+ - go: tip
+
+go:
+ - "1.13.x"
+ - "1.14.x"
+ - tip
+
+before_script:
+ - export PATH=$HOME/.local/bin:$PATH
+
+before_install:
+ - go get -u github.com/mattn/goveralls github.com/wadey/gocovmerge
+ - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.18.0
+ - pip install cram --user
+
+script:
+ - go test -v -covermode=count -coverprofile=profile.cov .
+ - go test -v -covermode=count -coverprofile=cryptosigner/profile.cov ./cryptosigner
+ - go test -v -covermode=count -coverprofile=cipher/profile.cov ./cipher
+ - go test -v -covermode=count -coverprofile=jwt/profile.cov ./jwt
+ - go test -v ./json # no coverage for forked encoding/json package
+ - golangci-lint run
+ - cd jose-util && go build && PATH=$PWD:$PATH cram -v jose-util.t # cram tests jose-util
+ - cd ..
+
+after_success:
+ - gocovmerge *.cov */*.cov > merged.coverprofile
+ - goveralls -coverprofile merged.coverprofile -service=travis-ci
diff --git a/vendor/github.com/go-jose/go-jose/v4/CHANGELOG.md b/vendor/github.com/go-jose/go-jose/v4/CHANGELOG.md
new file mode 100644
index 0000000000..246979f161
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v4/CHANGELOG.md
@@ -0,0 +1,89 @@
+# v4.0.3
+
+## Changed
+
+ - Allow unmarshalling JSONWebKeySets with unsupported key types (#130)
+ - Document that OpaqueKeyEncrypter can't be implemented (for now) (#129)
+ - Dependency updates
+
+# v4.0.2
+
+## Changed
+
+ - Improved documentation of Verify() to note that JSONWebKeySet is a supported
+ argument type (#104)
+ - Defined exported error values for missing x5c header and unsupported elliptic
+ curves error cases (#117)
+
+# v4.0.1
+
+## Fixed
+
+ - An attacker could send a JWE containing compressed data that used large
+ amounts of memory and CPU when decompressed by `Decrypt` or `DecryptMulti`.
+ Those functions now return an error if the decompressed data would exceed
+ 250kB or 10x the compressed size (whichever is larger). Thanks to
+ Enze Wang@Alioth and Jianjun Chen@Zhongguancun Lab (@zer0yu and @chenjj)
+ for reporting.
+
+# v4.0.0
+
+This release makes some breaking changes in order to more thoroughly
+address the vulnerabilities discussed in [Three New Attacks Against JSON Web
+Tokens][1], "Sign/encrypt confusion", "Billion hash attack", and "Polyglot
+token".
+
+## Changed
+
+ - Limit JWT encryption types (exclude password or public key types) (#78)
+ - Enforce minimum length for HMAC keys (#85)
+ - jwt: match any audience in a list, rather than requiring all audiences (#81)
+ - jwt: accept only Compact Serialization (#75)
+ - jws: Add expected algorithms for signatures (#74)
+ - Require specifying expected algorithms for ParseEncrypted,
+ ParseSigned, ParseDetached, jwt.ParseEncrypted, jwt.ParseSigned,
+ jwt.ParseSignedAndEncrypted (#69, #74)
+ - Usually there is a small, known set of appropriate algorithms for a program
+ to use and it's a mistake to allow unexpected algorithms. For instance the
+ "billion hash attack" relies in part on programs accepting the PBES2
+ encryption algorithm and doing the necessary work even if they weren't
+ specifically configured to allow PBES2.
+ - Revert "Strip padding off base64 strings" (#82)
+ - The specs require base64url encoding without padding.
+ - Minimum supported Go version is now 1.21
+
+## Added
+
+ - ParseSignedCompact, ParseSignedJSON, ParseEncryptedCompact, ParseEncryptedJSON.
+ - These allow parsing a specific serialization, as opposed to ParseSigned and
+ ParseEncrypted, which try to automatically detect which serialization was
+ provided. It's common to require a specific serialization for a specific
+ protocol - for instance JWT requires Compact serialization.
+
+[1]: https://i.blackhat.com/BH-US-23/Presentations/US-23-Tervoort-Three-New-Attacks-Against-JSON-Web-Tokens.pdf
+
+# v3.0.2
+
+## Fixed
+
+ - DecryptMulti: handle decompression error (#19)
+
+## Changed
+
+ - jwe/CompactSerialize: improve performance (#67)
+ - Increase the default number of PBKDF2 iterations to 600k (#48)
+ - Return the proper algorithm for ECDSA keys (#45)
+
+## Added
+
+ - Add Thumbprint support for opaque signers (#38)
+
+# v3.0.1
+
+## Fixed
+
+ - Security issue: an attacker specifying a large "p2c" value can cause
+ JSONWebEncryption.Decrypt and JSONWebEncryption.DecryptMulti to consume large
+ amounts of CPU, causing a DoS. Thanks to Matt Schwager (@mschwager) for the
+ disclosure and to Tom Tervoort for originally publishing the category of attack.
+ https://i.blackhat.com/BH-US-23/Presentations/US-23-Tervoort-Three-New-Attacks-Against-JSON-Web-Tokens.pdf
diff --git a/vendor/github.com/go-jose/go-jose/v4/CONTRIBUTING.md b/vendor/github.com/go-jose/go-jose/v4/CONTRIBUTING.md
new file mode 100644
index 0000000000..b63e1f8fee
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v4/CONTRIBUTING.md
@@ -0,0 +1,15 @@
+# Contributing
+
+If you would like to contribute code to go-jose you can do so through GitHub by
+forking the repository and sending a pull request.
+
+When submitting code, please make every effort to follow existing conventions
+and style in order to keep the code as readable as possible. Please also make
+sure all tests pass by running `go test`, and format your code with `go fmt`.
+We also recommend using `golint` and `errcheck`.
+
+Before your code can be accepted into the project you must also sign the
+Individual Contributor License Agreement. We use [cla-assistant.io][1] and you
+will be prompted to sign once a pull request is opened.
+
+[1]: https://cla-assistant.io/
diff --git a/vendor/google.golang.org/appengine/LICENSE b/vendor/github.com/go-jose/go-jose/v4/LICENSE
similarity index 100%
rename from vendor/google.golang.org/appengine/LICENSE
rename to vendor/github.com/go-jose/go-jose/v4/LICENSE
diff --git a/vendor/github.com/go-jose/go-jose/v4/README.md b/vendor/github.com/go-jose/go-jose/v4/README.md
new file mode 100644
index 0000000000..79a7c5ecc8
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v4/README.md
@@ -0,0 +1,114 @@
+# Go JOSE
+
+[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4)
+[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4/jwt.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4/jwt)
+[![license](https://img.shields.io/badge/license-apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/go-jose/go-jose/master/LICENSE)
+[![test](https://img.shields.io/github/checks-status/go-jose/go-jose/v4)](https://github.com/go-jose/go-jose/actions)
+
+Package jose aims to provide an implementation of the Javascript Object Signing
+and Encryption set of standards. This includes support for JSON Web Encryption,
+JSON Web Signature, and JSON Web Token standards.
+
+**Disclaimer**: This library contains encryption software that is subject to
+the U.S. Export Administration Regulations. You may not export, re-export,
+transfer or download this code or any part of it in violation of any United
+States law, directive or regulation. In particular this software may not be
+exported or re-exported in any form or on any media to Iran, North Sudan,
+Syria, Cuba, or North Korea, or to denied persons or entities mentioned on any
+US maintained blocked list.
+
+## Overview
+
+The implementation follows the
+[JSON Web Encryption](https://dx.doi.org/10.17487/RFC7516) (RFC 7516),
+[JSON Web Signature](https://dx.doi.org/10.17487/RFC7515) (RFC 7515), and
+[JSON Web Token](https://dx.doi.org/10.17487/RFC7519) (RFC 7519) specifications.
+Tables of supported algorithms are shown below. The library supports both
+the compact and JWS/JWE JSON Serialization formats, and has optional support for
+multiple recipients. It also comes with a small command-line utility
+([`jose-util`](https://pkg.go.dev/github.com/go-jose/go-jose/jose-util))
+for dealing with JOSE messages in a shell.
+
+**Note**: We use a forked version of the `encoding/json` package from the Go
+standard library which uses case-sensitive matching for member names (instead
+of [case-insensitive matching](https://www.ietf.org/mail-archive/web/json/current/msg03763.html)).
+This is to avoid differences in interpretation of messages between go-jose and
+libraries in other languages.
+
+### Versions
+
+[Version 4](https://github.com/go-jose/go-jose)
+([branch](https://github.com/go-jose/go-jose/tree/main),
+[doc](https://pkg.go.dev/github.com/go-jose/go-jose/v4), [releases](https://github.com/go-jose/go-jose/releases)) is the current stable version:
+
+ import "github.com/go-jose/go-jose/v4"
+
+The old [square/go-jose](https://github.com/square/go-jose) repo contains the prior v1 and v2 versions, which
+are still useable but not actively developed anymore.
+
+Version 3, in this repo, is still receiving security fixes but not functionality
+updates.
+
+### Supported algorithms
+
+See below for a table of supported algorithms. Algorithm identifiers match
+the names in the [JSON Web Algorithms](https://dx.doi.org/10.17487/RFC7518)
+standard where possible. The Godoc reference has a list of constants.
+
+ Key encryption | Algorithm identifier(s)
+ :------------------------- | :------------------------------
+ RSA-PKCS#1v1.5 | RSA1_5
+ RSA-OAEP | RSA-OAEP, RSA-OAEP-256
+ AES key wrap | A128KW, A192KW, A256KW
+ AES-GCM key wrap | A128GCMKW, A192GCMKW, A256GCMKW
+ ECDH-ES + AES key wrap | ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW
+ ECDH-ES (direct) | ECDH-ES1
+ Direct encryption | dir1
+
+1. Not supported in multi-recipient mode
+
+ Signing / MAC | Algorithm identifier(s)
+ :------------------------- | :------------------------------
+ RSASSA-PKCS#1v1.5 | RS256, RS384, RS512
+ RSASSA-PSS | PS256, PS384, PS512
+ HMAC | HS256, HS384, HS512
+ ECDSA | ES256, ES384, ES512
+ Ed25519 | EdDSA2
+
+2. Only available in version 2 of the package
+
+ Content encryption | Algorithm identifier(s)
+ :------------------------- | :------------------------------
+ AES-CBC+HMAC | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512
+ AES-GCM | A128GCM, A192GCM, A256GCM
+
+ Compression | Algorithm identifiers(s)
+ :------------------------- | -------------------------------
+ DEFLATE (RFC 1951) | DEF
+
+### Supported key types
+
+See below for a table of supported key types. These are understood by the
+library, and can be passed to corresponding functions such as `NewEncrypter` or
+`NewSigner`. Each of these keys can also be wrapped in a JWK if desired, which
+allows attaching a key id.
+
+ Algorithm(s) | Corresponding types
+ :------------------------- | -------------------------------
+ RSA | *[rsa.PublicKey](https://pkg.go.dev/crypto/rsa/#PublicKey), *[rsa.PrivateKey](https://pkg.go.dev/crypto/rsa/#PrivateKey)
+ ECDH, ECDSA | *[ecdsa.PublicKey](https://pkg.go.dev/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](https://pkg.go.dev/crypto/ecdsa/#PrivateKey)
+ EdDSA1 | [ed25519.PublicKey](https://pkg.go.dev/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://pkg.go.dev/crypto/ed25519#PrivateKey)
+ AES, HMAC | []byte
+
+1. Only available in version 2 or later of the package
+
+## Examples
+
+[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4)
+[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4/jwt.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4/jwt)
+
+Examples can be found in the Godoc
+reference for this package. The
+[`jose-util`](https://github.com/go-jose/go-jose/tree/v4/jose-util)
+subdirectory also contains a small command-line utility which might be useful
+as an example as well.
diff --git a/vendor/github.com/go-jose/go-jose/v4/SECURITY.md b/vendor/github.com/go-jose/go-jose/v4/SECURITY.md
new file mode 100644
index 0000000000..2f18a75a82
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v4/SECURITY.md
@@ -0,0 +1,13 @@
+# Security Policy
+This document explains how to contact the Let's Encrypt security team to report security vulnerabilities.
+
+## Supported Versions
+| Version | Supported |
+| ------- | ----------|
+| >= v3 | ✓ |
+| v2 | ✗ |
+| v1 | ✗ |
+
+## Reporting a vulnerability
+
+Please see [https://letsencrypt.org/contact/#security](https://letsencrypt.org/contact/#security) for the email address to report a vulnerability. Ensure that the subject line for your report contains the word `vulnerability` and is descriptive. Your email should be acknowledged within 24 hours. If you do not receive a response within 24 hours, please follow-up again with another email.
diff --git a/vendor/github.com/go-jose/go-jose/v4/asymmetric.go b/vendor/github.com/go-jose/go-jose/v4/asymmetric.go
new file mode 100644
index 0000000000..f8d5774ef5
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v4/asymmetric.go
@@ -0,0 +1,595 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+ "crypto"
+ "crypto/aes"
+ "crypto/ecdsa"
+ "crypto/ed25519"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha1"
+ "crypto/sha256"
+ "errors"
+ "fmt"
+ "math/big"
+
+ josecipher "github.com/go-jose/go-jose/v4/cipher"
+ "github.com/go-jose/go-jose/v4/json"
+)
+
+// A generic RSA-based encrypter/verifier
+type rsaEncrypterVerifier struct {
+ publicKey *rsa.PublicKey
+}
+
+// A generic RSA-based decrypter/signer
+type rsaDecrypterSigner struct {
+ privateKey *rsa.PrivateKey
+}
+
+// A generic EC-based encrypter/verifier
+type ecEncrypterVerifier struct {
+ publicKey *ecdsa.PublicKey
+}
+
+type edEncrypterVerifier struct {
+ publicKey ed25519.PublicKey
+}
+
+// A key generator for ECDH-ES
+type ecKeyGenerator struct {
+ size int
+ algID string
+ publicKey *ecdsa.PublicKey
+}
+
+// A generic EC-based decrypter/signer
+type ecDecrypterSigner struct {
+ privateKey *ecdsa.PrivateKey
+}
+
+type edDecrypterSigner struct {
+ privateKey ed25519.PrivateKey
+}
+
+// newRSARecipient creates recipientKeyInfo based on the given key.
+func newRSARecipient(keyAlg KeyAlgorithm, publicKey *rsa.PublicKey) (recipientKeyInfo, error) {
+ // Verify that key management algorithm is supported by this encrypter
+ switch keyAlg {
+ case RSA1_5, RSA_OAEP, RSA_OAEP_256:
+ default:
+ return recipientKeyInfo{}, ErrUnsupportedAlgorithm
+ }
+
+ if publicKey == nil {
+ return recipientKeyInfo{}, errors.New("invalid public key")
+ }
+
+ return recipientKeyInfo{
+ keyAlg: keyAlg,
+ keyEncrypter: &rsaEncrypterVerifier{
+ publicKey: publicKey,
+ },
+ }, nil
+}
+
+// newRSASigner creates a recipientSigInfo based on the given key.
+func newRSASigner(sigAlg SignatureAlgorithm, privateKey *rsa.PrivateKey) (recipientSigInfo, error) {
+ // Verify that key management algorithm is supported by this encrypter
+ switch sigAlg {
+ case RS256, RS384, RS512, PS256, PS384, PS512:
+ default:
+ return recipientSigInfo{}, ErrUnsupportedAlgorithm
+ }
+
+ if privateKey == nil {
+ return recipientSigInfo{}, errors.New("invalid private key")
+ }
+
+ return recipientSigInfo{
+ sigAlg: sigAlg,
+ publicKey: staticPublicKey(&JSONWebKey{
+ Key: privateKey.Public(),
+ }),
+ signer: &rsaDecrypterSigner{
+ privateKey: privateKey,
+ },
+ }, nil
+}
+
+func newEd25519Signer(sigAlg SignatureAlgorithm, privateKey ed25519.PrivateKey) (recipientSigInfo, error) {
+ if sigAlg != EdDSA {
+ return recipientSigInfo{}, ErrUnsupportedAlgorithm
+ }
+
+ if privateKey == nil {
+ return recipientSigInfo{}, errors.New("invalid private key")
+ }
+ return recipientSigInfo{
+ sigAlg: sigAlg,
+ publicKey: staticPublicKey(&JSONWebKey{
+ Key: privateKey.Public(),
+ }),
+ signer: &edDecrypterSigner{
+ privateKey: privateKey,
+ },
+ }, nil
+}
+
+// newECDHRecipient creates recipientKeyInfo based on the given key.
+func newECDHRecipient(keyAlg KeyAlgorithm, publicKey *ecdsa.PublicKey) (recipientKeyInfo, error) {
+ // Verify that key management algorithm is supported by this encrypter
+ switch keyAlg {
+ case ECDH_ES, ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW:
+ default:
+ return recipientKeyInfo{}, ErrUnsupportedAlgorithm
+ }
+
+ if publicKey == nil || !publicKey.Curve.IsOnCurve(publicKey.X, publicKey.Y) {
+ return recipientKeyInfo{}, errors.New("invalid public key")
+ }
+
+ return recipientKeyInfo{
+ keyAlg: keyAlg,
+ keyEncrypter: &ecEncrypterVerifier{
+ publicKey: publicKey,
+ },
+ }, nil
+}
+
+// newECDSASigner creates a recipientSigInfo based on the given key.
+func newECDSASigner(sigAlg SignatureAlgorithm, privateKey *ecdsa.PrivateKey) (recipientSigInfo, error) {
+ // Verify that key management algorithm is supported by this encrypter
+ switch sigAlg {
+ case ES256, ES384, ES512:
+ default:
+ return recipientSigInfo{}, ErrUnsupportedAlgorithm
+ }
+
+ if privateKey == nil {
+ return recipientSigInfo{}, errors.New("invalid private key")
+ }
+
+ return recipientSigInfo{
+ sigAlg: sigAlg,
+ publicKey: staticPublicKey(&JSONWebKey{
+ Key: privateKey.Public(),
+ }),
+ signer: &ecDecrypterSigner{
+ privateKey: privateKey,
+ },
+ }, nil
+}
+
+// Encrypt the given payload and update the object.
+func (ctx rsaEncrypterVerifier) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) {
+ encryptedKey, err := ctx.encrypt(cek, alg)
+ if err != nil {
+ return recipientInfo{}, err
+ }
+
+ return recipientInfo{
+ encryptedKey: encryptedKey,
+ header: &rawHeader{},
+ }, nil
+}
+
+// Encrypt the given payload. Based on the key encryption algorithm,
+// this will either use RSA-PKCS1v1.5 or RSA-OAEP (with SHA-1 or SHA-256).
+func (ctx rsaEncrypterVerifier) encrypt(cek []byte, alg KeyAlgorithm) ([]byte, error) {
+ switch alg {
+ case RSA1_5:
+ return rsa.EncryptPKCS1v15(RandReader, ctx.publicKey, cek)
+ case RSA_OAEP:
+ return rsa.EncryptOAEP(sha1.New(), RandReader, ctx.publicKey, cek, []byte{})
+ case RSA_OAEP_256:
+ return rsa.EncryptOAEP(sha256.New(), RandReader, ctx.publicKey, cek, []byte{})
+ }
+
+ return nil, ErrUnsupportedAlgorithm
+}
+
+// Decrypt the given payload and return the content encryption key.
+func (ctx rsaDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
+ return ctx.decrypt(recipient.encryptedKey, headers.getAlgorithm(), generator)
+}
+
+// Decrypt the given payload. Based on the key encryption algorithm,
+// this will either use RSA-PKCS1v1.5 or RSA-OAEP (with SHA-1 or SHA-256).
+func (ctx rsaDecrypterSigner) decrypt(jek []byte, alg KeyAlgorithm, generator keyGenerator) ([]byte, error) {
+ // Note: The random reader on decrypt operations is only used for blinding,
+ // so stubbing is meanlingless (hence the direct use of rand.Reader).
+ switch alg {
+ case RSA1_5:
+ defer func() {
+ // DecryptPKCS1v15SessionKey sometimes panics on an invalid payload
+ // because of an index out of bounds error, which we want to ignore.
+ // This has been fixed in Go 1.3.1 (released 2014/08/13), the recover()
+ // only exists for preventing crashes with unpatched versions.
+ // See: https://groups.google.com/forum/#!topic/golang-dev/7ihX6Y6kx9k
+ // See: https://code.google.com/p/go/source/detail?r=58ee390ff31602edb66af41ed10901ec95904d33
+ _ = recover()
+ }()
+
+ // Perform some input validation.
+ keyBytes := ctx.privateKey.PublicKey.N.BitLen() / 8
+ if keyBytes != len(jek) {
+ // Input size is incorrect, the encrypted payload should always match
+ // the size of the public modulus (e.g. using a 2048 bit key will
+ // produce 256 bytes of output). Reject this since it's invalid input.
+ return nil, ErrCryptoFailure
+ }
+
+ cek, _, err := generator.genKey()
+ if err != nil {
+ return nil, ErrCryptoFailure
+ }
+
+ // When decrypting an RSA-PKCS1v1.5 payload, we must take precautions to
+ // prevent chosen-ciphertext attacks as described in RFC 3218, "Preventing
+ // the Million Message Attack on Cryptographic Message Syntax". We are
+ // therefore deliberately ignoring errors here.
+ _ = rsa.DecryptPKCS1v15SessionKey(rand.Reader, ctx.privateKey, jek, cek)
+
+ return cek, nil
+ case RSA_OAEP:
+ // Use rand.Reader for RSA blinding
+ return rsa.DecryptOAEP(sha1.New(), rand.Reader, ctx.privateKey, jek, []byte{})
+ case RSA_OAEP_256:
+ // Use rand.Reader for RSA blinding
+ return rsa.DecryptOAEP(sha256.New(), rand.Reader, ctx.privateKey, jek, []byte{})
+ }
+
+ return nil, ErrUnsupportedAlgorithm
+}
+
+// Sign the given payload
+func (ctx rsaDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
+ var hash crypto.Hash
+
+ switch alg {
+ case RS256, PS256:
+ hash = crypto.SHA256
+ case RS384, PS384:
+ hash = crypto.SHA384
+ case RS512, PS512:
+ hash = crypto.SHA512
+ default:
+ return Signature{}, ErrUnsupportedAlgorithm
+ }
+
+ hasher := hash.New()
+
+ // According to documentation, Write() on hash never fails
+ _, _ = hasher.Write(payload)
+ hashed := hasher.Sum(nil)
+
+ var out []byte
+ var err error
+
+ switch alg {
+ case RS256, RS384, RS512:
+ // TODO(https://github.com/go-jose/go-jose/issues/40): As of go1.20, the
+ // random parameter is legacy and ignored, and it can be nil.
+ // https://cs.opensource.google/go/go/+/refs/tags/go1.20:src/crypto/rsa/pkcs1v15.go;l=263;bpv=0;bpt=1
+ out, err = rsa.SignPKCS1v15(RandReader, ctx.privateKey, hash, hashed)
+ case PS256, PS384, PS512:
+ out, err = rsa.SignPSS(RandReader, ctx.privateKey, hash, hashed, &rsa.PSSOptions{
+ SaltLength: rsa.PSSSaltLengthEqualsHash,
+ })
+ }
+
+ if err != nil {
+ return Signature{}, err
+ }
+
+ return Signature{
+ Signature: out,
+ protected: &rawHeader{},
+ }, nil
+}
+
+// Verify the given payload
+func (ctx rsaEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
+ var hash crypto.Hash
+
+ switch alg {
+ case RS256, PS256:
+ hash = crypto.SHA256
+ case RS384, PS384:
+ hash = crypto.SHA384
+ case RS512, PS512:
+ hash = crypto.SHA512
+ default:
+ return ErrUnsupportedAlgorithm
+ }
+
+ hasher := hash.New()
+
+ // According to documentation, Write() on hash never fails
+ _, _ = hasher.Write(payload)
+ hashed := hasher.Sum(nil)
+
+ switch alg {
+ case RS256, RS384, RS512:
+ return rsa.VerifyPKCS1v15(ctx.publicKey, hash, hashed, signature)
+ case PS256, PS384, PS512:
+ return rsa.VerifyPSS(ctx.publicKey, hash, hashed, signature, nil)
+ }
+
+ return ErrUnsupportedAlgorithm
+}
+
+// Encrypt the given payload and update the object.
+func (ctx ecEncrypterVerifier) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) {
+ switch alg {
+ case ECDH_ES:
+ // ECDH-ES mode doesn't wrap a key, the shared secret is used directly as the key.
+ return recipientInfo{
+ header: &rawHeader{},
+ }, nil
+ case ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW:
+ default:
+ return recipientInfo{}, ErrUnsupportedAlgorithm
+ }
+
+ generator := ecKeyGenerator{
+ algID: string(alg),
+ publicKey: ctx.publicKey,
+ }
+
+ switch alg {
+ case ECDH_ES_A128KW:
+ generator.size = 16
+ case ECDH_ES_A192KW:
+ generator.size = 24
+ case ECDH_ES_A256KW:
+ generator.size = 32
+ }
+
+ kek, header, err := generator.genKey()
+ if err != nil {
+ return recipientInfo{}, err
+ }
+
+ block, err := aes.NewCipher(kek)
+ if err != nil {
+ return recipientInfo{}, err
+ }
+
+ jek, err := josecipher.KeyWrap(block, cek)
+ if err != nil {
+ return recipientInfo{}, err
+ }
+
+ return recipientInfo{
+ encryptedKey: jek,
+ header: &header,
+ }, nil
+}
+
+// Get key size for EC key generator
+func (ctx ecKeyGenerator) keySize() int {
+ return ctx.size
+}
+
+// Get a content encryption key for ECDH-ES
+func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) {
+ priv, err := ecdsa.GenerateKey(ctx.publicKey.Curve, RandReader)
+ if err != nil {
+ return nil, rawHeader{}, err
+ }
+
+ out := josecipher.DeriveECDHES(ctx.algID, []byte{}, []byte{}, priv, ctx.publicKey, ctx.size)
+
+ b, err := json.Marshal(&JSONWebKey{
+ Key: &priv.PublicKey,
+ })
+ if err != nil {
+ return nil, nil, err
+ }
+
+ headers := rawHeader{
+ headerEPK: makeRawMessage(b),
+ }
+
+ return out, headers, nil
+}
+
+// Decrypt the given payload and return the content encryption key.
+func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
+ epk, err := headers.getEPK()
+ if err != nil {
+ return nil, errors.New("go-jose/go-jose: invalid epk header")
+ }
+ if epk == nil {
+ return nil, errors.New("go-jose/go-jose: missing epk header")
+ }
+
+ publicKey, ok := epk.Key.(*ecdsa.PublicKey)
+ if publicKey == nil || !ok {
+ return nil, errors.New("go-jose/go-jose: invalid epk header")
+ }
+
+ if !ctx.privateKey.Curve.IsOnCurve(publicKey.X, publicKey.Y) {
+ return nil, errors.New("go-jose/go-jose: invalid public key in epk header")
+ }
+
+ apuData, err := headers.getAPU()
+ if err != nil {
+ return nil, errors.New("go-jose/go-jose: invalid apu header")
+ }
+ apvData, err := headers.getAPV()
+ if err != nil {
+ return nil, errors.New("go-jose/go-jose: invalid apv header")
+ }
+
+ deriveKey := func(algID string, size int) []byte {
+ return josecipher.DeriveECDHES(algID, apuData.bytes(), apvData.bytes(), ctx.privateKey, publicKey, size)
+ }
+
+ var keySize int
+
+ algorithm := headers.getAlgorithm()
+ switch algorithm {
+ case ECDH_ES:
+ // ECDH-ES uses direct key agreement, no key unwrapping necessary.
+ return deriveKey(string(headers.getEncryption()), generator.keySize()), nil
+ case ECDH_ES_A128KW:
+ keySize = 16
+ case ECDH_ES_A192KW:
+ keySize = 24
+ case ECDH_ES_A256KW:
+ keySize = 32
+ default:
+ return nil, ErrUnsupportedAlgorithm
+ }
+
+ key := deriveKey(string(algorithm), keySize)
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return nil, err
+ }
+
+ return josecipher.KeyUnwrap(block, recipient.encryptedKey)
+}
+
+func (ctx edDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
+ if alg != EdDSA {
+ return Signature{}, ErrUnsupportedAlgorithm
+ }
+
+ sig, err := ctx.privateKey.Sign(RandReader, payload, crypto.Hash(0))
+ if err != nil {
+ return Signature{}, err
+ }
+
+ return Signature{
+ Signature: sig,
+ protected: &rawHeader{},
+ }, nil
+}
+
+func (ctx edEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
+ if alg != EdDSA {
+ return ErrUnsupportedAlgorithm
+ }
+ ok := ed25519.Verify(ctx.publicKey, payload, signature)
+ if !ok {
+ return errors.New("go-jose/go-jose: ed25519 signature failed to verify")
+ }
+ return nil
+}
+
+// Sign the given payload
+func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
+ var expectedBitSize int
+ var hash crypto.Hash
+
+ switch alg {
+ case ES256:
+ expectedBitSize = 256
+ hash = crypto.SHA256
+ case ES384:
+ expectedBitSize = 384
+ hash = crypto.SHA384
+ case ES512:
+ expectedBitSize = 521
+ hash = crypto.SHA512
+ }
+
+ curveBits := ctx.privateKey.Curve.Params().BitSize
+ if expectedBitSize != curveBits {
+ return Signature{}, fmt.Errorf("go-jose/go-jose: expected %d bit key, got %d bits instead", expectedBitSize, curveBits)
+ }
+
+ hasher := hash.New()
+
+ // According to documentation, Write() on hash never fails
+ _, _ = hasher.Write(payload)
+ hashed := hasher.Sum(nil)
+
+ r, s, err := ecdsa.Sign(RandReader, ctx.privateKey, hashed)
+ if err != nil {
+ return Signature{}, err
+ }
+
+ keyBytes := curveBits / 8
+ if curveBits%8 > 0 {
+ keyBytes++
+ }
+
+ // We serialize the outputs (r and s) into big-endian byte arrays and pad
+ // them with zeros on the left to make sure the sizes work out. Both arrays
+ // must be keyBytes long, and the output must be 2*keyBytes long.
+ rBytes := r.Bytes()
+ rBytesPadded := make([]byte, keyBytes)
+ copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)
+
+ sBytes := s.Bytes()
+ sBytesPadded := make([]byte, keyBytes)
+ copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)
+
+ out := append(rBytesPadded, sBytesPadded...)
+
+ return Signature{
+ Signature: out,
+ protected: &rawHeader{},
+ }, nil
+}
+
+// Verify the given payload
+func (ctx ecEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
+ var keySize int
+ var hash crypto.Hash
+
+ switch alg {
+ case ES256:
+ keySize = 32
+ hash = crypto.SHA256
+ case ES384:
+ keySize = 48
+ hash = crypto.SHA384
+ case ES512:
+ keySize = 66
+ hash = crypto.SHA512
+ default:
+ return ErrUnsupportedAlgorithm
+ }
+
+ if len(signature) != 2*keySize {
+ return fmt.Errorf("go-jose/go-jose: invalid signature size, have %d bytes, wanted %d", len(signature), 2*keySize)
+ }
+
+ hasher := hash.New()
+
+ // According to documentation, Write() on hash never fails
+ _, _ = hasher.Write(payload)
+ hashed := hasher.Sum(nil)
+
+ r := big.NewInt(0).SetBytes(signature[:keySize])
+ s := big.NewInt(0).SetBytes(signature[keySize:])
+
+ match := ecdsa.Verify(ctx.publicKey, hashed, r, s)
+ if !match {
+ return errors.New("go-jose/go-jose: ecdsa signature failed to verify")
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/go-jose/go-jose/v4/cipher/cbc_hmac.go b/vendor/github.com/go-jose/go-jose/v4/cipher/cbc_hmac.go
new file mode 100644
index 0000000000..af029cec0b
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v4/cipher/cbc_hmac.go
@@ -0,0 +1,196 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package josecipher
+
+import (
+ "bytes"
+ "crypto/cipher"
+ "crypto/hmac"
+ "crypto/sha256"
+ "crypto/sha512"
+ "crypto/subtle"
+ "encoding/binary"
+ "errors"
+ "hash"
+)
+
+const (
+ nonceBytes = 16
+)
+
+// NewCBCHMAC instantiates a new AEAD based on CBC+HMAC.
+func NewCBCHMAC(key []byte, newBlockCipher func([]byte) (cipher.Block, error)) (cipher.AEAD, error) {
+ keySize := len(key) / 2
+ integrityKey := key[:keySize]
+ encryptionKey := key[keySize:]
+
+ blockCipher, err := newBlockCipher(encryptionKey)
+ if err != nil {
+ return nil, err
+ }
+
+ var hash func() hash.Hash
+ switch keySize {
+ case 16:
+ hash = sha256.New
+ case 24:
+ hash = sha512.New384
+ case 32:
+ hash = sha512.New
+ }
+
+ return &cbcAEAD{
+ hash: hash,
+ blockCipher: blockCipher,
+ authtagBytes: keySize,
+ integrityKey: integrityKey,
+ }, nil
+}
+
+// An AEAD based on CBC+HMAC
+type cbcAEAD struct {
+ hash func() hash.Hash
+ authtagBytes int
+ integrityKey []byte
+ blockCipher cipher.Block
+}
+
+func (ctx *cbcAEAD) NonceSize() int {
+ return nonceBytes
+}
+
+func (ctx *cbcAEAD) Overhead() int {
+ // Maximum overhead is block size (for padding) plus auth tag length, where
+ // the length of the auth tag is equivalent to the key size.
+ return ctx.blockCipher.BlockSize() + ctx.authtagBytes
+}
+
+// Seal encrypts and authenticates the plaintext.
+func (ctx *cbcAEAD) Seal(dst, nonce, plaintext, data []byte) []byte {
+ // Output buffer -- must take care not to mangle plaintext input.
+ ciphertext := make([]byte, uint64(len(plaintext))+uint64(ctx.Overhead()))[:len(plaintext)]
+ copy(ciphertext, plaintext)
+ ciphertext = padBuffer(ciphertext, ctx.blockCipher.BlockSize())
+
+ cbc := cipher.NewCBCEncrypter(ctx.blockCipher, nonce)
+
+ cbc.CryptBlocks(ciphertext, ciphertext)
+ authtag := ctx.computeAuthTag(data, nonce, ciphertext)
+
+ ret, out := resize(dst, uint64(len(dst))+uint64(len(ciphertext))+uint64(len(authtag)))
+ copy(out, ciphertext)
+ copy(out[len(ciphertext):], authtag)
+
+ return ret
+}
+
+// Open decrypts and authenticates the ciphertext.
+func (ctx *cbcAEAD) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
+ if len(ciphertext) < ctx.authtagBytes {
+ return nil, errors.New("go-jose/go-jose: invalid ciphertext (too short)")
+ }
+
+ offset := len(ciphertext) - ctx.authtagBytes
+ expectedTag := ctx.computeAuthTag(data, nonce, ciphertext[:offset])
+ match := subtle.ConstantTimeCompare(expectedTag, ciphertext[offset:])
+ if match != 1 {
+ return nil, errors.New("go-jose/go-jose: invalid ciphertext (auth tag mismatch)")
+ }
+
+ cbc := cipher.NewCBCDecrypter(ctx.blockCipher, nonce)
+
+ // Make copy of ciphertext buffer, don't want to modify in place
+ buffer := append([]byte{}, ciphertext[:offset]...)
+
+ if len(buffer)%ctx.blockCipher.BlockSize() > 0 {
+ return nil, errors.New("go-jose/go-jose: invalid ciphertext (invalid length)")
+ }
+
+ cbc.CryptBlocks(buffer, buffer)
+
+ // Remove padding
+ plaintext, err := unpadBuffer(buffer, ctx.blockCipher.BlockSize())
+ if err != nil {
+ return nil, err
+ }
+
+ ret, out := resize(dst, uint64(len(dst))+uint64(len(plaintext)))
+ copy(out, plaintext)
+
+ return ret, nil
+}
+
+// Compute an authentication tag
+func (ctx *cbcAEAD) computeAuthTag(aad, nonce, ciphertext []byte) []byte {
+ buffer := make([]byte, uint64(len(aad))+uint64(len(nonce))+uint64(len(ciphertext))+8)
+ n := 0
+ n += copy(buffer, aad)
+ n += copy(buffer[n:], nonce)
+ n += copy(buffer[n:], ciphertext)
+ binary.BigEndian.PutUint64(buffer[n:], uint64(len(aad))*8)
+
+ // According to documentation, Write() on hash.Hash never fails.
+ hmac := hmac.New(ctx.hash, ctx.integrityKey)
+ _, _ = hmac.Write(buffer)
+
+ return hmac.Sum(nil)[:ctx.authtagBytes]
+}
+
+// resize ensures that the given slice has a capacity of at least n bytes.
+// If the capacity of the slice is less than n, a new slice is allocated
+// and the existing data will be copied.
+func resize(in []byte, n uint64) (head, tail []byte) {
+ if uint64(cap(in)) >= n {
+ head = in[:n]
+ } else {
+ head = make([]byte, n)
+ copy(head, in)
+ }
+
+ tail = head[len(in):]
+ return
+}
+
+// Apply padding
+func padBuffer(buffer []byte, blockSize int) []byte {
+ missing := blockSize - (len(buffer) % blockSize)
+ ret, out := resize(buffer, uint64(len(buffer))+uint64(missing))
+ padding := bytes.Repeat([]byte{byte(missing)}, missing)
+ copy(out, padding)
+ return ret
+}
+
+// Remove padding
+func unpadBuffer(buffer []byte, blockSize int) ([]byte, error) {
+ if len(buffer)%blockSize != 0 {
+ return nil, errors.New("go-jose/go-jose: invalid padding")
+ }
+
+ last := buffer[len(buffer)-1]
+ count := int(last)
+
+ if count == 0 || count > blockSize || count > len(buffer) {
+ return nil, errors.New("go-jose/go-jose: invalid padding")
+ }
+
+ padding := bytes.Repeat([]byte{last}, count)
+ if !bytes.HasSuffix(buffer, padding) {
+ return nil, errors.New("go-jose/go-jose: invalid padding")
+ }
+
+ return buffer[:len(buffer)-count], nil
+}
diff --git a/vendor/github.com/go-jose/go-jose/v4/cipher/concat_kdf.go b/vendor/github.com/go-jose/go-jose/v4/cipher/concat_kdf.go
new file mode 100644
index 0000000000..f62c3bdba5
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v4/cipher/concat_kdf.go
@@ -0,0 +1,75 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package josecipher
+
+import (
+ "crypto"
+ "encoding/binary"
+ "hash"
+ "io"
+)
+
+type concatKDF struct {
+ z, info []byte
+ i uint32
+ cache []byte
+ hasher hash.Hash
+}
+
+// NewConcatKDF builds a KDF reader based on the given inputs.
+func NewConcatKDF(hash crypto.Hash, z, algID, ptyUInfo, ptyVInfo, supPubInfo, supPrivInfo []byte) io.Reader {
+ buffer := make([]byte, uint64(len(algID))+uint64(len(ptyUInfo))+uint64(len(ptyVInfo))+uint64(len(supPubInfo))+uint64(len(supPrivInfo)))
+ n := 0
+ n += copy(buffer, algID)
+ n += copy(buffer[n:], ptyUInfo)
+ n += copy(buffer[n:], ptyVInfo)
+ n += copy(buffer[n:], supPubInfo)
+ copy(buffer[n:], supPrivInfo)
+
+ hasher := hash.New()
+
+ return &concatKDF{
+ z: z,
+ info: buffer,
+ hasher: hasher,
+ cache: []byte{},
+ i: 1,
+ }
+}
+
+func (ctx *concatKDF) Read(out []byte) (int, error) {
+ copied := copy(out, ctx.cache)
+ ctx.cache = ctx.cache[copied:]
+
+ for copied < len(out) {
+ ctx.hasher.Reset()
+
+ // Write on a hash.Hash never fails
+ _ = binary.Write(ctx.hasher, binary.BigEndian, ctx.i)
+ _, _ = ctx.hasher.Write(ctx.z)
+ _, _ = ctx.hasher.Write(ctx.info)
+
+ hash := ctx.hasher.Sum(nil)
+ chunkCopied := copy(out[copied:], hash)
+ copied += chunkCopied
+ ctx.cache = hash[chunkCopied:]
+
+ ctx.i++
+ }
+
+ return copied, nil
+}
diff --git a/vendor/github.com/go-jose/go-jose/v4/cipher/ecdh_es.go b/vendor/github.com/go-jose/go-jose/v4/cipher/ecdh_es.go
new file mode 100644
index 0000000000..093c646740
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v4/cipher/ecdh_es.go
@@ -0,0 +1,86 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package josecipher
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "encoding/binary"
+)
+
+// DeriveECDHES derives a shared encryption key using ECDH/ConcatKDF as described in JWE/JWA.
+// It is an error to call this function with a private/public key that are not on the same
+// curve. Callers must ensure that the keys are valid before calling this function. Output
+// size may be at most 1<<16 bytes (64 KiB).
+func DeriveECDHES(alg string, apuData, apvData []byte, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, size int) []byte {
+ if size > 1<<16 {
+ panic("ECDH-ES output size too large, must be less than or equal to 1<<16")
+ }
+
+ // algId, partyUInfo, partyVInfo inputs must be prefixed with the length
+ algID := lengthPrefixed([]byte(alg))
+ ptyUInfo := lengthPrefixed(apuData)
+ ptyVInfo := lengthPrefixed(apvData)
+
+ // suppPubInfo is the encoded length of the output size in bits
+ supPubInfo := make([]byte, 4)
+ binary.BigEndian.PutUint32(supPubInfo, uint32(size)*8)
+
+ if !priv.PublicKey.Curve.IsOnCurve(pub.X, pub.Y) {
+ panic("public key not on same curve as private key")
+ }
+
+ z, _ := priv.Curve.ScalarMult(pub.X, pub.Y, priv.D.Bytes())
+ zBytes := z.Bytes()
+
+ // Note that calling z.Bytes() on a big.Int may strip leading zero bytes from
+ // the returned byte array. This can lead to a problem where zBytes will be
+ // shorter than expected which breaks the key derivation. Therefore we must pad
+ // to the full length of the expected coordinate here before calling the KDF.
+ octSize := dSize(priv.Curve)
+ if len(zBytes) != octSize {
+ zBytes = append(bytes.Repeat([]byte{0}, octSize-len(zBytes)), zBytes...)
+ }
+
+ reader := NewConcatKDF(crypto.SHA256, zBytes, algID, ptyUInfo, ptyVInfo, supPubInfo, []byte{})
+ key := make([]byte, size)
+
+ // Read on the KDF will never fail
+ _, _ = reader.Read(key)
+
+ return key
+}
+
+// dSize returns the size in octets for a coordinate on a elliptic curve.
+func dSize(curve elliptic.Curve) int {
+ order := curve.Params().P
+ bitLen := order.BitLen()
+ size := bitLen / 8
+ if bitLen%8 != 0 {
+ size++
+ }
+ return size
+}
+
+func lengthPrefixed(data []byte) []byte {
+ out := make([]byte, len(data)+4)
+ binary.BigEndian.PutUint32(out, uint32(len(data)))
+ copy(out[4:], data)
+ return out
+}
diff --git a/vendor/github.com/go-jose/go-jose/v4/cipher/key_wrap.go b/vendor/github.com/go-jose/go-jose/v4/cipher/key_wrap.go
new file mode 100644
index 0000000000..b9effbca8a
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v4/cipher/key_wrap.go
@@ -0,0 +1,109 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package josecipher
+
+import (
+ "crypto/cipher"
+ "crypto/subtle"
+ "encoding/binary"
+ "errors"
+)
+
+var defaultIV = []byte{0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6}
+
+// KeyWrap implements NIST key wrapping; it wraps a content encryption key (cek) with the given block cipher.
+func KeyWrap(block cipher.Block, cek []byte) ([]byte, error) {
+ if len(cek)%8 != 0 {
+ return nil, errors.New("go-jose/go-jose: key wrap input must be 8 byte blocks")
+ }
+
+ n := len(cek) / 8
+ r := make([][]byte, n)
+
+ for i := range r {
+ r[i] = make([]byte, 8)
+ copy(r[i], cek[i*8:])
+ }
+
+ buffer := make([]byte, 16)
+ tBytes := make([]byte, 8)
+ copy(buffer, defaultIV)
+
+ for t := 0; t < 6*n; t++ {
+ copy(buffer[8:], r[t%n])
+
+ block.Encrypt(buffer, buffer)
+
+ binary.BigEndian.PutUint64(tBytes, uint64(t+1))
+
+ for i := 0; i < 8; i++ {
+ buffer[i] ^= tBytes[i]
+ }
+ copy(r[t%n], buffer[8:])
+ }
+
+ out := make([]byte, (n+1)*8)
+ copy(out, buffer[:8])
+ for i := range r {
+ copy(out[(i+1)*8:], r[i])
+ }
+
+ return out, nil
+}
+
+// KeyUnwrap implements NIST key unwrapping; it unwraps a content encryption key (cek) with the given block cipher.
+func KeyUnwrap(block cipher.Block, ciphertext []byte) ([]byte, error) {
+ if len(ciphertext)%8 != 0 {
+ return nil, errors.New("go-jose/go-jose: key wrap input must be 8 byte blocks")
+ }
+
+ n := (len(ciphertext) / 8) - 1
+ r := make([][]byte, n)
+
+ for i := range r {
+ r[i] = make([]byte, 8)
+ copy(r[i], ciphertext[(i+1)*8:])
+ }
+
+ buffer := make([]byte, 16)
+ tBytes := make([]byte, 8)
+ copy(buffer[:8], ciphertext[:8])
+
+ for t := 6*n - 1; t >= 0; t-- {
+ binary.BigEndian.PutUint64(tBytes, uint64(t+1))
+
+ for i := 0; i < 8; i++ {
+ buffer[i] ^= tBytes[i]
+ }
+ copy(buffer[8:], r[t%n])
+
+ block.Decrypt(buffer, buffer)
+
+ copy(r[t%n], buffer[8:])
+ }
+
+ if subtle.ConstantTimeCompare(buffer[:8], defaultIV) == 0 {
+ return nil, errors.New("go-jose/go-jose: failed to unwrap key")
+ }
+
+ out := make([]byte, n*8)
+ for i := range r {
+ copy(out[i*8:], r[i])
+ }
+
+ return out, nil
+}
diff --git a/vendor/github.com/go-jose/go-jose/v4/crypter.go b/vendor/github.com/go-jose/go-jose/v4/crypter.go
new file mode 100644
index 0000000000..d81b03b447
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v4/crypter.go
@@ -0,0 +1,599 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+ "crypto/ecdsa"
+ "crypto/rsa"
+ "errors"
+ "fmt"
+
+ "github.com/go-jose/go-jose/v4/json"
+)
+
+// Encrypter represents an encrypter which produces an encrypted JWE object.
+type Encrypter interface {
+ Encrypt(plaintext []byte) (*JSONWebEncryption, error)
+ EncryptWithAuthData(plaintext []byte, aad []byte) (*JSONWebEncryption, error)
+ Options() EncrypterOptions
+}
+
+// A generic content cipher
+type contentCipher interface {
+ keySize() int
+ encrypt(cek []byte, aad, plaintext []byte) (*aeadParts, error)
+ decrypt(cek []byte, aad []byte, parts *aeadParts) ([]byte, error)
+}
+
+// A key generator (for generating/getting a CEK)
+type keyGenerator interface {
+ keySize() int
+ genKey() ([]byte, rawHeader, error)
+}
+
+// A generic key encrypter
+type keyEncrypter interface {
+ encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) // Encrypt a key
+}
+
+// A generic key decrypter
+type keyDecrypter interface {
+ decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) // Decrypt a key
+}
+
+// A generic encrypter based on the given key encrypter and content cipher.
+type genericEncrypter struct {
+ contentAlg ContentEncryption
+ compressionAlg CompressionAlgorithm
+ cipher contentCipher
+ recipients []recipientKeyInfo
+ keyGenerator keyGenerator
+ extraHeaders map[HeaderKey]interface{}
+}
+
+type recipientKeyInfo struct {
+ keyID string
+ keyAlg KeyAlgorithm
+ keyEncrypter keyEncrypter
+}
+
+// EncrypterOptions represents options that can be set on new encrypters.
+type EncrypterOptions struct {
+ Compression CompressionAlgorithm
+
+ // Optional map of name/value pairs to be inserted into the protected
+ // header of a JWS object. Some specifications which make use of
+ // JWS require additional values here.
+ //
+ // Values will be serialized by [json.Marshal] and must be valid inputs to
+ // that function.
+ //
+ // [json.Marshal]: https://pkg.go.dev/encoding/json#Marshal
+ ExtraHeaders map[HeaderKey]interface{}
+}
+
+// WithHeader adds an arbitrary value to the ExtraHeaders map, initializing it
+// if necessary, and returns the updated EncrypterOptions.
+//
+// The v parameter will be serialized by [json.Marshal] and must be a valid
+// input to that function.
+//
+// [json.Marshal]: https://pkg.go.dev/encoding/json#Marshal
+func (eo *EncrypterOptions) WithHeader(k HeaderKey, v interface{}) *EncrypterOptions {
+ if eo.ExtraHeaders == nil {
+ eo.ExtraHeaders = map[HeaderKey]interface{}{}
+ }
+ eo.ExtraHeaders[k] = v
+ return eo
+}
+
+// WithContentType adds a content type ("cty") header and returns the updated
+// EncrypterOptions.
+func (eo *EncrypterOptions) WithContentType(contentType ContentType) *EncrypterOptions {
+ return eo.WithHeader(HeaderContentType, contentType)
+}
+
+// WithType adds a type ("typ") header and returns the updated EncrypterOptions.
+func (eo *EncrypterOptions) WithType(typ ContentType) *EncrypterOptions {
+ return eo.WithHeader(HeaderType, typ)
+}
+
+// Recipient represents an algorithm/key to encrypt messages to.
+//
+// PBES2Count and PBES2Salt correspond with the "p2c" and "p2s" headers used
+// on the password-based encryption algorithms PBES2-HS256+A128KW,
+// PBES2-HS384+A192KW, and PBES2-HS512+A256KW. If they are not provided a safe
+// default of 100000 will be used for the count and a 128-bit random salt will
+// be generated.
+type Recipient struct {
+ Algorithm KeyAlgorithm
+ // Key must have one of these types:
+ // - ed25519.PublicKey
+ // - *ecdsa.PublicKey
+ // - *rsa.PublicKey
+ // - *JSONWebKey
+ // - JSONWebKey
+ // - []byte (a symmetric key)
+ // - Any type that satisfies the OpaqueKeyEncrypter interface
+ //
+ // The type of Key must match the value of Algorithm.
+ Key interface{}
+ KeyID string
+ PBES2Count int
+ PBES2Salt []byte
+}
+
+// NewEncrypter creates an appropriate encrypter based on the key type
+func NewEncrypter(enc ContentEncryption, rcpt Recipient, opts *EncrypterOptions) (Encrypter, error) {
+ encrypter := &genericEncrypter{
+ contentAlg: enc,
+ recipients: []recipientKeyInfo{},
+ cipher: getContentCipher(enc),
+ }
+ if opts != nil {
+ encrypter.compressionAlg = opts.Compression
+ encrypter.extraHeaders = opts.ExtraHeaders
+ }
+
+ if encrypter.cipher == nil {
+ return nil, ErrUnsupportedAlgorithm
+ }
+
+ var keyID string
+ var rawKey interface{}
+ switch encryptionKey := rcpt.Key.(type) {
+ case JSONWebKey:
+ keyID, rawKey = encryptionKey.KeyID, encryptionKey.Key
+ case *JSONWebKey:
+ keyID, rawKey = encryptionKey.KeyID, encryptionKey.Key
+ case OpaqueKeyEncrypter:
+ keyID, rawKey = encryptionKey.KeyID(), encryptionKey
+ default:
+ rawKey = encryptionKey
+ }
+
+ switch rcpt.Algorithm {
+ case DIRECT:
+ // Direct encryption mode must be treated differently
+ keyBytes, ok := rawKey.([]byte)
+ if !ok {
+ return nil, ErrUnsupportedKeyType
+ }
+ if encrypter.cipher.keySize() != len(keyBytes) {
+ return nil, ErrInvalidKeySize
+ }
+ encrypter.keyGenerator = staticKeyGenerator{
+ key: keyBytes,
+ }
+ recipientInfo, _ := newSymmetricRecipient(rcpt.Algorithm, keyBytes)
+ recipientInfo.keyID = keyID
+ if rcpt.KeyID != "" {
+ recipientInfo.keyID = rcpt.KeyID
+ }
+ encrypter.recipients = []recipientKeyInfo{recipientInfo}
+ return encrypter, nil
+ case ECDH_ES:
+ // ECDH-ES (w/o key wrapping) is similar to DIRECT mode
+ keyDSA, ok := rawKey.(*ecdsa.PublicKey)
+ if !ok {
+ return nil, ErrUnsupportedKeyType
+ }
+ encrypter.keyGenerator = ecKeyGenerator{
+ size: encrypter.cipher.keySize(),
+ algID: string(enc),
+ publicKey: keyDSA,
+ }
+ recipientInfo, _ := newECDHRecipient(rcpt.Algorithm, keyDSA)
+ recipientInfo.keyID = keyID
+ if rcpt.KeyID != "" {
+ recipientInfo.keyID = rcpt.KeyID
+ }
+ encrypter.recipients = []recipientKeyInfo{recipientInfo}
+ return encrypter, nil
+ default:
+ // Can just add a standard recipient
+ encrypter.keyGenerator = randomKeyGenerator{
+ size: encrypter.cipher.keySize(),
+ }
+ err := encrypter.addRecipient(rcpt)
+ return encrypter, err
+ }
+}
+
+// NewMultiEncrypter creates a multi-encrypter based on the given parameters
+func NewMultiEncrypter(enc ContentEncryption, rcpts []Recipient, opts *EncrypterOptions) (Encrypter, error) {
+ cipher := getContentCipher(enc)
+
+ if cipher == nil {
+ return nil, ErrUnsupportedAlgorithm
+ }
+ if len(rcpts) == 0 {
+ return nil, fmt.Errorf("go-jose/go-jose: recipients is nil or empty")
+ }
+
+ encrypter := &genericEncrypter{
+ contentAlg: enc,
+ recipients: []recipientKeyInfo{},
+ cipher: cipher,
+ keyGenerator: randomKeyGenerator{
+ size: cipher.keySize(),
+ },
+ }
+
+ if opts != nil {
+ encrypter.compressionAlg = opts.Compression
+ encrypter.extraHeaders = opts.ExtraHeaders
+ }
+
+ for _, recipient := range rcpts {
+ err := encrypter.addRecipient(recipient)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return encrypter, nil
+}
+
+func (ctx *genericEncrypter) addRecipient(recipient Recipient) (err error) {
+ var recipientInfo recipientKeyInfo
+
+ switch recipient.Algorithm {
+ case DIRECT, ECDH_ES:
+ return fmt.Errorf("go-jose/go-jose: key algorithm '%s' not supported in multi-recipient mode", recipient.Algorithm)
+ }
+
+ recipientInfo, err = makeJWERecipient(recipient.Algorithm, recipient.Key)
+ if recipient.KeyID != "" {
+ recipientInfo.keyID = recipient.KeyID
+ }
+
+ switch recipient.Algorithm {
+ case PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW:
+ if sr, ok := recipientInfo.keyEncrypter.(*symmetricKeyCipher); ok {
+ sr.p2c = recipient.PBES2Count
+ sr.p2s = recipient.PBES2Salt
+ }
+ }
+
+ if err == nil {
+ ctx.recipients = append(ctx.recipients, recipientInfo)
+ }
+ return err
+}
+
+func makeJWERecipient(alg KeyAlgorithm, encryptionKey interface{}) (recipientKeyInfo, error) {
+ switch encryptionKey := encryptionKey.(type) {
+ case *rsa.PublicKey:
+ return newRSARecipient(alg, encryptionKey)
+ case *ecdsa.PublicKey:
+ return newECDHRecipient(alg, encryptionKey)
+ case []byte:
+ return newSymmetricRecipient(alg, encryptionKey)
+ case string:
+ return newSymmetricRecipient(alg, []byte(encryptionKey))
+ case *JSONWebKey:
+ recipient, err := makeJWERecipient(alg, encryptionKey.Key)
+ recipient.keyID = encryptionKey.KeyID
+ return recipient, err
+ case OpaqueKeyEncrypter:
+ return newOpaqueKeyEncrypter(alg, encryptionKey)
+ }
+ return recipientKeyInfo{}, ErrUnsupportedKeyType
+}
+
+// newDecrypter creates an appropriate decrypter based on the key type
+func newDecrypter(decryptionKey interface{}) (keyDecrypter, error) {
+ switch decryptionKey := decryptionKey.(type) {
+ case *rsa.PrivateKey:
+ return &rsaDecrypterSigner{
+ privateKey: decryptionKey,
+ }, nil
+ case *ecdsa.PrivateKey:
+ return &ecDecrypterSigner{
+ privateKey: decryptionKey,
+ }, nil
+ case []byte:
+ return &symmetricKeyCipher{
+ key: decryptionKey,
+ }, nil
+ case string:
+ return &symmetricKeyCipher{
+ key: []byte(decryptionKey),
+ }, nil
+ case JSONWebKey:
+ return newDecrypter(decryptionKey.Key)
+ case *JSONWebKey:
+ return newDecrypter(decryptionKey.Key)
+ case OpaqueKeyDecrypter:
+ return &opaqueKeyDecrypter{decrypter: decryptionKey}, nil
+ default:
+ return nil, ErrUnsupportedKeyType
+ }
+}
+
+// Implementation of encrypt method producing a JWE object.
+func (ctx *genericEncrypter) Encrypt(plaintext []byte) (*JSONWebEncryption, error) {
+ return ctx.EncryptWithAuthData(plaintext, nil)
+}
+
+// Implementation of encrypt method producing a JWE object.
+func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JSONWebEncryption, error) {
+ obj := &JSONWebEncryption{}
+ obj.aad = aad
+
+ obj.protected = &rawHeader{}
+ err := obj.protected.set(headerEncryption, ctx.contentAlg)
+ if err != nil {
+ return nil, err
+ }
+
+ obj.recipients = make([]recipientInfo, len(ctx.recipients))
+
+ if len(ctx.recipients) == 0 {
+ return nil, fmt.Errorf("go-jose/go-jose: no recipients to encrypt to")
+ }
+
+ cek, headers, err := ctx.keyGenerator.genKey()
+ if err != nil {
+ return nil, err
+ }
+
+ obj.protected.merge(&headers)
+
+ for i, info := range ctx.recipients {
+ recipient, err := info.keyEncrypter.encryptKey(cek, info.keyAlg)
+ if err != nil {
+ return nil, err
+ }
+
+ err = recipient.header.set(headerAlgorithm, info.keyAlg)
+ if err != nil {
+ return nil, err
+ }
+
+ if info.keyID != "" {
+ err = recipient.header.set(headerKeyID, info.keyID)
+ if err != nil {
+ return nil, err
+ }
+ }
+ obj.recipients[i] = recipient
+ }
+
+ if len(ctx.recipients) == 1 {
+ // Move per-recipient headers into main protected header if there's
+ // only a single recipient.
+ obj.protected.merge(obj.recipients[0].header)
+ obj.recipients[0].header = nil
+ }
+
+ if ctx.compressionAlg != NONE {
+ plaintext, err = compress(ctx.compressionAlg, plaintext)
+ if err != nil {
+ return nil, err
+ }
+
+ err = obj.protected.set(headerCompression, ctx.compressionAlg)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ for k, v := range ctx.extraHeaders {
+ b, err := json.Marshal(v)
+ if err != nil {
+ return nil, err
+ }
+ (*obj.protected)[k] = makeRawMessage(b)
+ }
+
+ authData := obj.computeAuthData()
+ parts, err := ctx.cipher.encrypt(cek, authData, plaintext)
+ if err != nil {
+ return nil, err
+ }
+
+ obj.iv = parts.iv
+ obj.ciphertext = parts.ciphertext
+ obj.tag = parts.tag
+
+ return obj, nil
+}
+
+func (ctx *genericEncrypter) Options() EncrypterOptions {
+ return EncrypterOptions{
+ Compression: ctx.compressionAlg,
+ ExtraHeaders: ctx.extraHeaders,
+ }
+}
+
+// Decrypt and validate the object and return the plaintext. This
+// function does not support multi-recipient. If you desire multi-recipient
+// decryption use DecryptMulti instead.
+//
+// The decryptionKey argument must contain a private or symmetric key
+// and must have one of these types:
+// - *ecdsa.PrivateKey
+// - *rsa.PrivateKey
+// - *JSONWebKey
+// - JSONWebKey
+// - *JSONWebKeySet
+// - JSONWebKeySet
+// - []byte (a symmetric key)
+// - string (a symmetric key)
+// - Any type that satisfies the OpaqueKeyDecrypter interface.
+//
+// Note that ed25519 is only available for signatures, not encryption, so is
+// not an option here.
+//
+// Automatically decompresses plaintext, but returns an error if the decompressed
+// data would be >250kB or >10x the size of the compressed data, whichever is larger.
+func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) {
+ headers := obj.mergedHeaders(nil)
+
+ if len(obj.recipients) > 1 {
+ return nil, errors.New("go-jose/go-jose: too many recipients in payload; expecting only one")
+ }
+
+ critical, err := headers.getCritical()
+ if err != nil {
+ return nil, fmt.Errorf("go-jose/go-jose: invalid crit header")
+ }
+
+ if len(critical) > 0 {
+ return nil, fmt.Errorf("go-jose/go-jose: unsupported crit header")
+ }
+
+ key, err := tryJWKS(decryptionKey, obj.Header)
+ if err != nil {
+ return nil, err
+ }
+ decrypter, err := newDecrypter(key)
+ if err != nil {
+ return nil, err
+ }
+
+ cipher := getContentCipher(headers.getEncryption())
+ if cipher == nil {
+ return nil, fmt.Errorf("go-jose/go-jose: unsupported enc value '%s'", string(headers.getEncryption()))
+ }
+
+ generator := randomKeyGenerator{
+ size: cipher.keySize(),
+ }
+
+ parts := &aeadParts{
+ iv: obj.iv,
+ ciphertext: obj.ciphertext,
+ tag: obj.tag,
+ }
+
+ authData := obj.computeAuthData()
+
+ var plaintext []byte
+ recipient := obj.recipients[0]
+ recipientHeaders := obj.mergedHeaders(&recipient)
+
+ cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator)
+ if err == nil {
+ // Found a valid CEK -- let's try to decrypt.
+ plaintext, err = cipher.decrypt(cek, authData, parts)
+ }
+
+ if plaintext == nil {
+ return nil, ErrCryptoFailure
+ }
+
+ // The "zip" header parameter may only be present in the protected header.
+ if comp := obj.protected.getCompression(); comp != "" {
+ plaintext, err = decompress(comp, plaintext)
+ if err != nil {
+ return nil, fmt.Errorf("go-jose/go-jose: failed to decompress plaintext: %v", err)
+ }
+ }
+
+ return plaintext, nil
+}
+
+// DecryptMulti decrypts and validates the object and returns the plaintexts,
+// with support for multiple recipients. It returns the index of the recipient
+// for which the decryption was successful, the merged headers for that recipient,
+// and the plaintext.
+//
+// The decryptionKey argument must have one of the types allowed for the
+// decryptionKey argument of Decrypt().
+//
+// Automatically decompresses plaintext, but returns an error if the decompressed
+// data would be >250kB or >3x the size of the compressed data, whichever is larger.
+func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Header, []byte, error) {
+ globalHeaders := obj.mergedHeaders(nil)
+
+ critical, err := globalHeaders.getCritical()
+ if err != nil {
+ return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: invalid crit header")
+ }
+
+ if len(critical) > 0 {
+ return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: unsupported crit header")
+ }
+
+ key, err := tryJWKS(decryptionKey, obj.Header)
+ if err != nil {
+ return -1, Header{}, nil, err
+ }
+ decrypter, err := newDecrypter(key)
+ if err != nil {
+ return -1, Header{}, nil, err
+ }
+
+ encryption := globalHeaders.getEncryption()
+ cipher := getContentCipher(encryption)
+ if cipher == nil {
+ return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: unsupported enc value '%s'", string(encryption))
+ }
+
+ generator := randomKeyGenerator{
+ size: cipher.keySize(),
+ }
+
+ parts := &aeadParts{
+ iv: obj.iv,
+ ciphertext: obj.ciphertext,
+ tag: obj.tag,
+ }
+
+ authData := obj.computeAuthData()
+
+ index := -1
+ var plaintext []byte
+ var headers rawHeader
+
+ for i, recipient := range obj.recipients {
+ recipientHeaders := obj.mergedHeaders(&recipient)
+
+ cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator)
+ if err == nil {
+ // Found a valid CEK -- let's try to decrypt.
+ plaintext, err = cipher.decrypt(cek, authData, parts)
+ if err == nil {
+ index = i
+ headers = recipientHeaders
+ break
+ }
+ }
+ }
+
+ if plaintext == nil {
+ return -1, Header{}, nil, ErrCryptoFailure
+ }
+
+ // The "zip" header parameter may only be present in the protected header.
+ if comp := obj.protected.getCompression(); comp != "" {
+ plaintext, err = decompress(comp, plaintext)
+ if err != nil {
+ return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: failed to decompress plaintext: %v", err)
+ }
+ }
+
+ sanitized, err := headers.sanitized()
+ if err != nil {
+ return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: failed to sanitize header: %v", err)
+ }
+
+ return index, sanitized, plaintext, err
+}
diff --git a/vendor/github.com/go-jose/go-jose/v4/doc.go b/vendor/github.com/go-jose/go-jose/v4/doc.go
new file mode 100644
index 0000000000..0ad40ca085
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v4/doc.go
@@ -0,0 +1,25 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+Package jose aims to provide an implementation of the Javascript Object Signing
+and Encryption set of standards. It implements encryption and signing based on
+the JSON Web Encryption and JSON Web Signature standards, with optional JSON Web
+Token support available in a sub-package. The library supports both the compact
+and JWS/JWE JSON Serialization formats, and has optional support for multiple
+recipients.
+*/
+package jose
diff --git a/vendor/github.com/go-jose/go-jose/v4/encoding.go b/vendor/github.com/go-jose/go-jose/v4/encoding.go
new file mode 100644
index 0000000000..4f6e0d4a5c
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v4/encoding.go
@@ -0,0 +1,228 @@
+/*-
+ * Copyright 2014 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jose
+
+import (
+ "bytes"
+ "compress/flate"
+ "encoding/base64"
+ "encoding/binary"
+ "fmt"
+ "io"
+ "math/big"
+ "strings"
+ "unicode"
+
+ "github.com/go-jose/go-jose/v4/json"
+)
+
+// Helper function to serialize known-good objects.
+// Precondition: value is not a nil pointer.
+func mustSerializeJSON(value interface{}) []byte {
+ out, err := json.Marshal(value)
+ if err != nil {
+ panic(err)
+ }
+ // We never want to serialize the top-level value "null," since it's not a
+ // valid JOSE message. But if a caller passes in a nil pointer to this method,
+ // MarshalJSON will happily serialize it as the top-level value "null". If
+ // that value is then embedded in another operation, for instance by being
+ // base64-encoded and fed as input to a signing algorithm
+ // (https://github.com/go-jose/go-jose/issues/22), the result will be
+ // incorrect. Because this method is intended for known-good objects, and a nil
+ // pointer is not a known-good object, we are free to panic in this case.
+ // Note: It's not possible to directly check whether the data pointed at by an
+ // interface is a nil pointer, so we do this hacky workaround.
+ // https://groups.google.com/forum/#!topic/golang-nuts/wnH302gBa4I
+ if string(out) == "null" {
+ panic("Tried to serialize a nil pointer.")
+ }
+ return out
+}
+
+// Strip all newlines and whitespace
+func stripWhitespace(data string) string {
+ buf := strings.Builder{}
+ buf.Grow(len(data))
+ for _, r := range data {
+ if !unicode.IsSpace(r) {
+ buf.WriteRune(r)
+ }
+ }
+ return buf.String()
+}
+
+// Perform compression based on algorithm
+func compress(algorithm CompressionAlgorithm, input []byte) ([]byte, error) {
+ switch algorithm {
+ case DEFLATE:
+ return deflate(input)
+ default:
+ return nil, ErrUnsupportedAlgorithm
+ }
+}
+
+// Perform decompression based on algorithm
+func decompress(algorithm CompressionAlgorithm, input []byte) ([]byte, error) {
+ switch algorithm {
+ case DEFLATE:
+ return inflate(input)
+ default:
+ return nil, ErrUnsupportedAlgorithm
+ }
+}
+
+// deflate compresses the input.
+func deflate(input []byte) ([]byte, error) {
+ output := new(bytes.Buffer)
+
+ // Writing to byte buffer, err is always nil
+ writer, _ := flate.NewWriter(output, 1)
+ _, _ = io.Copy(writer, bytes.NewBuffer(input))
+
+ err := writer.Close()
+ return output.Bytes(), err
+}
+
+// inflate decompresses the input.
+//
+// Errors if the decompressed data would be >250kB or >10x the size of the
+// compressed data, whichever is larger.
+func inflate(input []byte) ([]byte, error) {
+ output := new(bytes.Buffer)
+ reader := flate.NewReader(bytes.NewBuffer(input))
+
+ maxCompressedSize := max(250_000, 10*int64(len(input)))
+
+ limit := maxCompressedSize + 1
+ n, err := io.CopyN(output, reader, limit)
+ if err != nil && err != io.EOF {
+ return nil, err
+ }
+ if n == limit {
+ return nil, fmt.Errorf("uncompressed data would be too large (>%d bytes)", maxCompressedSize)
+ }
+
+ err = reader.Close()
+ return output.Bytes(), err
+}
+
+// byteBuffer represents a slice of bytes that can be serialized to url-safe base64.
+type byteBuffer struct {
+ data []byte
+}
+
+func newBuffer(data []byte) *byteBuffer {
+ if data == nil {
+ return nil
+ }
+ return &byteBuffer{
+ data: data,
+ }
+}
+
+func newFixedSizeBuffer(data []byte, length int) *byteBuffer {
+ if len(data) > length {
+ panic("go-jose/go-jose: invalid call to newFixedSizeBuffer (len(data) > length)")
+ }
+ pad := make([]byte, length-len(data))
+ return newBuffer(append(pad, data...))
+}
+
+func newBufferFromInt(num uint64) *byteBuffer {
+ data := make([]byte, 8)
+ binary.BigEndian.PutUint64(data, num)
+ return newBuffer(bytes.TrimLeft(data, "\x00"))
+}
+
+func (b *byteBuffer) MarshalJSON() ([]byte, error) {
+ return json.Marshal(b.base64())
+}
+
+func (b *byteBuffer) UnmarshalJSON(data []byte) error {
+ var encoded string
+ err := json.Unmarshal(data, &encoded)
+ if err != nil {
+ return err
+ }
+
+ if encoded == "" {
+ return nil
+ }
+
+ decoded, err := base64.RawURLEncoding.DecodeString(encoded)
+ if err != nil {
+ return err
+ }
+
+ *b = *newBuffer(decoded)
+
+ return nil
+}
+
+func (b *byteBuffer) base64() string {
+ return base64.RawURLEncoding.EncodeToString(b.data)
+}
+
+func (b *byteBuffer) bytes() []byte {
+ // Handling nil here allows us to transparently handle nil slices when serializing.
+ if b == nil {
+ return nil
+ }
+ return b.data
+}
+
+func (b byteBuffer) bigInt() *big.Int {
+ return new(big.Int).SetBytes(b.data)
+}
+
+func (b byteBuffer) toInt() int {
+ return int(b.bigInt().Int64())
+}
+
+func base64EncodeLen(sl []byte) int {
+ return base64.RawURLEncoding.EncodedLen(len(sl))
+}
+
+func base64JoinWithDots(inputs ...[]byte) string {
+ if len(inputs) == 0 {
+ return ""
+ }
+
+ // Count of dots.
+ totalCount := len(inputs) - 1
+
+ for _, input := range inputs {
+ totalCount += base64EncodeLen(input)
+ }
+
+ out := make([]byte, totalCount)
+ startEncode := 0
+ for i, input := range inputs {
+ base64.RawURLEncoding.Encode(out[startEncode:], input)
+
+ if i == len(inputs)-1 {
+ continue
+ }
+
+ startEncode += base64EncodeLen(input)
+ out[startEncode] = '.'
+ startEncode++
+ }
+
+ return string(out)
+}
diff --git a/vendor/github.com/go-jose/go-jose/v4/json/LICENSE b/vendor/github.com/go-jose/go-jose/v4/json/LICENSE
new file mode 100644
index 0000000000..7448756763
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v4/json/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2012 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/go-jose/go-jose/v4/json/README.md b/vendor/github.com/go-jose/go-jose/v4/json/README.md
new file mode 100644
index 0000000000..86de5e5581
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v4/json/README.md
@@ -0,0 +1,13 @@
+# Safe JSON
+
+This repository contains a fork of the `encoding/json` package from Go 1.6.
+
+The following changes were made:
+
+* Object deserialization uses case-sensitive member name matching instead of
+ [case-insensitive matching](https://www.ietf.org/mail-archive/web/json/current/msg03763.html).
+ This is to avoid differences in the interpretation of JOSE messages between
+ go-jose and libraries written in other languages.
+* When deserializing a JSON object, we check for duplicate keys and reject the
+ input whenever we detect a duplicate. Rather than trying to work with malformed
+ data, we prefer to reject it right away.
diff --git a/vendor/github.com/go-jose/go-jose/v4/json/decode.go b/vendor/github.com/go-jose/go-jose/v4/json/decode.go
new file mode 100644
index 0000000000..50634dd847
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v4/json/decode.go
@@ -0,0 +1,1216 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Represents JSON data structure using native Go types: booleans, floats,
+// strings, arrays, and maps.
+
+package json
+
+import (
+ "bytes"
+ "encoding"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "math"
+ "reflect"
+ "runtime"
+ "strconv"
+ "unicode"
+ "unicode/utf16"
+ "unicode/utf8"
+)
+
+// Unmarshal parses the JSON-encoded data and stores the result
+// in the value pointed to by v.
+//
+// Unmarshal uses the inverse of the encodings that
+// Marshal uses, allocating maps, slices, and pointers as necessary,
+// with the following additional rules:
+//
+// To unmarshal JSON into a pointer, Unmarshal first handles the case of
+// the JSON being the JSON literal null. In that case, Unmarshal sets
+// the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into
+// the value pointed at by the pointer. If the pointer is nil, Unmarshal
+// allocates a new value for it to point to.
+//
+// To unmarshal JSON into a struct, Unmarshal matches incoming object
+// keys to the keys used by Marshal (either the struct field name or its tag),
+// preferring an exact match but also accepting a case-insensitive match.
+// Unmarshal will only set exported fields of the struct.
+//
+// To unmarshal JSON into an interface value,
+// Unmarshal stores one of these in the interface value:
+//
+// bool, for JSON booleans
+// float64, for JSON numbers
+// string, for JSON strings
+// []interface{}, for JSON arrays
+// map[string]interface{}, for JSON objects
+// nil for JSON null
+//
+// To unmarshal a JSON array into a slice, Unmarshal resets the slice length
+// to zero and then appends each element to the slice.
+// As a special case, to unmarshal an empty JSON array into a slice,
+// Unmarshal replaces the slice with a new empty slice.
+//
+// To unmarshal a JSON array into a Go array, Unmarshal decodes
+// JSON array elements into corresponding Go array elements.
+// If the Go array is smaller than the JSON array,
+// the additional JSON array elements are discarded.
+// If the JSON array is smaller than the Go array,
+// the additional Go array elements are set to zero values.
+//
+// To unmarshal a JSON object into a string-keyed map, Unmarshal first
+// establishes a map to use, If the map is nil, Unmarshal allocates a new map.
+// Otherwise Unmarshal reuses the existing map, keeping existing entries.
+// Unmarshal then stores key-value pairs from the JSON object into the map.
+//
+// If a JSON value is not appropriate for a given target type,
+// or if a JSON number overflows the target type, Unmarshal
+// skips that field and completes the unmarshaling as best it can.
+// If no more serious errors are encountered, Unmarshal returns
+// an UnmarshalTypeError describing the earliest such error.
+//
+// The JSON null value unmarshals into an interface, map, pointer, or slice
+// by setting that Go value to nil. Because null is often used in JSON to mean
+// “not present,” unmarshaling a JSON null into any other Go type has no effect
+// on the value and produces no error.
+//
+// When unmarshaling quoted strings, invalid UTF-8 or
+// invalid UTF-16 surrogate pairs are not treated as an error.
+// Instead, they are replaced by the Unicode replacement
+// character U+FFFD.
+func Unmarshal(data []byte, v interface{}) error {
+ // Check for well-formedness.
+ // Avoids filling out half a data structure
+ // before discovering a JSON syntax error.
+ var d decodeState
+ err := checkValid(data, &d.scan)
+ if err != nil {
+ return err
+ }
+
+ d.init(data)
+ return d.unmarshal(v)
+}
+
+// Unmarshaler is the interface implemented by objects
+// that can unmarshal a JSON description of themselves.
+// The input can be assumed to be a valid encoding of
+// a JSON value. UnmarshalJSON must copy the JSON data
+// if it wishes to retain the data after returning.
+type Unmarshaler interface {
+ UnmarshalJSON([]byte) error
+}
+
+// An UnmarshalTypeError describes a JSON value that was
+// not appropriate for a value of a specific Go type.
+type UnmarshalTypeError struct {
+ Value string // description of JSON value - "bool", "array", "number -5"
+ Type reflect.Type // type of Go value it could not be assigned to
+ Offset int64 // error occurred after reading Offset bytes
+}
+
+func (e *UnmarshalTypeError) Error() string {
+ return "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String()
+}
+
+// An UnmarshalFieldError describes a JSON object key that
+// led to an unexported (and therefore unwritable) struct field.
+// (No longer used; kept for compatibility.)
+type UnmarshalFieldError struct {
+ Key string
+ Type reflect.Type
+ Field reflect.StructField
+}
+
+func (e *UnmarshalFieldError) Error() string {
+ return "json: cannot unmarshal object key " + strconv.Quote(e.Key) + " into unexported field " + e.Field.Name + " of type " + e.Type.String()
+}
+
+// An InvalidUnmarshalError describes an invalid argument passed to Unmarshal.
+// (The argument to Unmarshal must be a non-nil pointer.)
+type InvalidUnmarshalError struct {
+ Type reflect.Type
+}
+
+func (e *InvalidUnmarshalError) Error() string {
+ if e.Type == nil {
+ return "json: Unmarshal(nil)"
+ }
+
+ if e.Type.Kind() != reflect.Ptr {
+ return "json: Unmarshal(non-pointer " + e.Type.String() + ")"
+ }
+ return "json: Unmarshal(nil " + e.Type.String() + ")"
+}
+
+func (d *decodeState) unmarshal(v interface{}) (err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ if _, ok := r.(runtime.Error); ok {
+ panic(r)
+ }
+ err = r.(error)
+ }
+ }()
+
+ rv := reflect.ValueOf(v)
+ if rv.Kind() != reflect.Ptr || rv.IsNil() {
+ return &InvalidUnmarshalError{reflect.TypeOf(v)}
+ }
+
+ d.scan.reset()
+ // We decode rv not rv.Elem because the Unmarshaler interface
+ // test must be applied at the top level of the value.
+ d.value(rv)
+ return d.savedError
+}
+
+// A Number represents a JSON number literal.
+type Number string
+
+// String returns the literal text of the number.
+func (n Number) String() string { return string(n) }
+
+// Float64 returns the number as a float64.
+func (n Number) Float64() (float64, error) {
+ return strconv.ParseFloat(string(n), 64)
+}
+
+// Int64 returns the number as an int64.
+func (n Number) Int64() (int64, error) {
+ return strconv.ParseInt(string(n), 10, 64)
+}
+
+// isValidNumber reports whether s is a valid JSON number literal.
+func isValidNumber(s string) bool {
+ // This function implements the JSON numbers grammar.
+ // See https://tools.ietf.org/html/rfc7159#section-6
+ // and http://json.org/number.gif
+
+ if s == "" {
+ return false
+ }
+
+ // Optional -
+ if s[0] == '-' {
+ s = s[1:]
+ if s == "" {
+ return false
+ }
+ }
+
+ // Digits
+ switch {
+ default:
+ return false
+
+ case s[0] == '0':
+ s = s[1:]
+
+ case '1' <= s[0] && s[0] <= '9':
+ s = s[1:]
+ for len(s) > 0 && '0' <= s[0] && s[0] <= '9' {
+ s = s[1:]
+ }
+ }
+
+ // . followed by 1 or more digits.
+ if len(s) >= 2 && s[0] == '.' && '0' <= s[1] && s[1] <= '9' {
+ s = s[2:]
+ for len(s) > 0 && '0' <= s[0] && s[0] <= '9' {
+ s = s[1:]
+ }
+ }
+
+ // e or E followed by an optional - or + and
+ // 1 or more digits.
+ if len(s) >= 2 && (s[0] == 'e' || s[0] == 'E') {
+ s = s[1:]
+ if s[0] == '+' || s[0] == '-' {
+ s = s[1:]
+ if s == "" {
+ return false
+ }
+ }
+ for len(s) > 0 && '0' <= s[0] && s[0] <= '9' {
+ s = s[1:]
+ }
+ }
+
+ // Make sure we are at the end.
+ return s == ""
+}
+
+type NumberUnmarshalType int
+
+const (
+ // unmarshal a JSON number into an interface{} as a float64
+ UnmarshalFloat NumberUnmarshalType = iota
+ // unmarshal a JSON number into an interface{} as a `json.Number`
+ UnmarshalJSONNumber
+ // unmarshal a JSON number into an interface{} as a int64
+ // if value is an integer otherwise float64
+ UnmarshalIntOrFloat
+)
+
+// decodeState represents the state while decoding a JSON value.
+type decodeState struct {
+ data []byte
+ off int // read offset in data
+ scan scanner
+ nextscan scanner // for calls to nextValue
+ savedError error
+ numberType NumberUnmarshalType
+}
+
+// errPhase is used for errors that should not happen unless
+// there is a bug in the JSON decoder or something is editing
+// the data slice while the decoder executes.
+var errPhase = errors.New("JSON decoder out of sync - data changing underfoot?")
+
+func (d *decodeState) init(data []byte) *decodeState {
+ d.data = data
+ d.off = 0
+ d.savedError = nil
+ return d
+}
+
+// error aborts the decoding by panicking with err.
+func (d *decodeState) error(err error) {
+ panic(err)
+}
+
+// saveError saves the first err it is called with,
+// for reporting at the end of the unmarshal.
+func (d *decodeState) saveError(err error) {
+ if d.savedError == nil {
+ d.savedError = err
+ }
+}
+
+// next cuts off and returns the next full JSON value in d.data[d.off:].
+// The next value is known to be an object or array, not a literal.
+func (d *decodeState) next() []byte {
+ c := d.data[d.off]
+ item, rest, err := nextValue(d.data[d.off:], &d.nextscan)
+ if err != nil {
+ d.error(err)
+ }
+ d.off = len(d.data) - len(rest)
+
+ // Our scanner has seen the opening brace/bracket
+ // and thinks we're still in the middle of the object.
+ // invent a closing brace/bracket to get it out.
+ if c == '{' {
+ d.scan.step(&d.scan, '}')
+ } else {
+ d.scan.step(&d.scan, ']')
+ }
+
+ return item
+}
+
+// scanWhile processes bytes in d.data[d.off:] until it
+// receives a scan code not equal to op.
+// It updates d.off and returns the new scan code.
+func (d *decodeState) scanWhile(op int) int {
+ var newOp int
+ for {
+ if d.off >= len(d.data) {
+ newOp = d.scan.eof()
+ d.off = len(d.data) + 1 // mark processed EOF with len+1
+ } else {
+ c := d.data[d.off]
+ d.off++
+ newOp = d.scan.step(&d.scan, c)
+ }
+ if newOp != op {
+ break
+ }
+ }
+ return newOp
+}
+
+// value decodes a JSON value from d.data[d.off:] into the value.
+// it updates d.off to point past the decoded value.
+func (d *decodeState) value(v reflect.Value) {
+ if !v.IsValid() {
+ _, rest, err := nextValue(d.data[d.off:], &d.nextscan)
+ if err != nil {
+ d.error(err)
+ }
+ d.off = len(d.data) - len(rest)
+
+ // d.scan thinks we're still at the beginning of the item.
+ // Feed in an empty string - the shortest, simplest value -
+ // so that it knows we got to the end of the value.
+ if d.scan.redo {
+ // rewind.
+ d.scan.redo = false
+ d.scan.step = stateBeginValue
+ }
+ d.scan.step(&d.scan, '"')
+ d.scan.step(&d.scan, '"')
+
+ n := len(d.scan.parseState)
+ if n > 0 && d.scan.parseState[n-1] == parseObjectKey {
+ // d.scan thinks we just read an object key; finish the object
+ d.scan.step(&d.scan, ':')
+ d.scan.step(&d.scan, '"')
+ d.scan.step(&d.scan, '"')
+ d.scan.step(&d.scan, '}')
+ }
+
+ return
+ }
+
+ switch op := d.scanWhile(scanSkipSpace); op {
+ default:
+ d.error(errPhase)
+
+ case scanBeginArray:
+ d.array(v)
+
+ case scanBeginObject:
+ d.object(v)
+
+ case scanBeginLiteral:
+ d.literal(v)
+ }
+}
+
+type unquotedValue struct{}
+
+// valueQuoted is like value but decodes a
+// quoted string literal or literal null into an interface value.
+// If it finds anything other than a quoted string literal or null,
+// valueQuoted returns unquotedValue{}.
+func (d *decodeState) valueQuoted() interface{} {
+ switch op := d.scanWhile(scanSkipSpace); op {
+ default:
+ d.error(errPhase)
+
+ case scanBeginArray:
+ d.array(reflect.Value{})
+
+ case scanBeginObject:
+ d.object(reflect.Value{})
+
+ case scanBeginLiteral:
+ switch v := d.literalInterface().(type) {
+ case nil, string:
+ return v
+ }
+ }
+ return unquotedValue{}
+}
+
+// indirect walks down v allocating pointers as needed,
+// until it gets to a non-pointer.
+// if it encounters an Unmarshaler, indirect stops and returns that.
+// if decodingNull is true, indirect stops at the last pointer so it can be set to nil.
+func (d *decodeState) indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnmarshaler, reflect.Value) {
+ // If v is a named type and is addressable,
+ // start with its address, so that if the type has pointer methods,
+ // we find them.
+ if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() {
+ v = v.Addr()
+ }
+ for {
+ // Load value from interface, but only if the result will be
+ // usefully addressable.
+ if v.Kind() == reflect.Interface && !v.IsNil() {
+ e := v.Elem()
+ if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) {
+ v = e
+ continue
+ }
+ }
+
+ if v.Kind() != reflect.Ptr {
+ break
+ }
+
+ if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() {
+ break
+ }
+ if v.IsNil() {
+ v.Set(reflect.New(v.Type().Elem()))
+ }
+ if v.Type().NumMethod() > 0 {
+ if u, ok := v.Interface().(Unmarshaler); ok {
+ return u, nil, reflect.Value{}
+ }
+ if u, ok := v.Interface().(encoding.TextUnmarshaler); ok {
+ return nil, u, reflect.Value{}
+ }
+ }
+ v = v.Elem()
+ }
+ return nil, nil, v
+}
+
+// array consumes an array from d.data[d.off-1:], decoding into the value v.
+// the first byte of the array ('[') has been read already.
+func (d *decodeState) array(v reflect.Value) {
+ // Check for unmarshaler.
+ u, ut, pv := d.indirect(v, false)
+ if u != nil {
+ d.off--
+ err := u.UnmarshalJSON(d.next())
+ if err != nil {
+ d.error(err)
+ }
+ return
+ }
+ if ut != nil {
+ d.saveError(&UnmarshalTypeError{"array", v.Type(), int64(d.off)})
+ d.off--
+ d.next()
+ return
+ }
+
+ v = pv
+
+ // Check type of target.
+ switch v.Kind() {
+ case reflect.Interface:
+ if v.NumMethod() == 0 {
+ // Decoding into nil interface? Switch to non-reflect code.
+ v.Set(reflect.ValueOf(d.arrayInterface()))
+ return
+ }
+ // Otherwise it's invalid.
+ fallthrough
+ default:
+ d.saveError(&UnmarshalTypeError{"array", v.Type(), int64(d.off)})
+ d.off--
+ d.next()
+ return
+ case reflect.Array:
+ case reflect.Slice:
+ break
+ }
+
+ i := 0
+ for {
+ // Look ahead for ] - can only happen on first iteration.
+ op := d.scanWhile(scanSkipSpace)
+ if op == scanEndArray {
+ break
+ }
+
+ // Back up so d.value can have the byte we just read.
+ d.off--
+ d.scan.undo(op)
+
+ // Get element of array, growing if necessary.
+ if v.Kind() == reflect.Slice {
+ // Grow slice if necessary
+ if i >= v.Cap() {
+ newcap := v.Cap() + v.Cap()/2
+ if newcap < 4 {
+ newcap = 4
+ }
+ newv := reflect.MakeSlice(v.Type(), v.Len(), newcap)
+ reflect.Copy(newv, v)
+ v.Set(newv)
+ }
+ if i >= v.Len() {
+ v.SetLen(i + 1)
+ }
+ }
+
+ if i < v.Len() {
+ // Decode into element.
+ d.value(v.Index(i))
+ } else {
+ // Ran out of fixed array: skip.
+ d.value(reflect.Value{})
+ }
+ i++
+
+ // Next token must be , or ].
+ op = d.scanWhile(scanSkipSpace)
+ if op == scanEndArray {
+ break
+ }
+ if op != scanArrayValue {
+ d.error(errPhase)
+ }
+ }
+
+ if i < v.Len() {
+ if v.Kind() == reflect.Array {
+ // Array. Zero the rest.
+ z := reflect.Zero(v.Type().Elem())
+ for ; i < v.Len(); i++ {
+ v.Index(i).Set(z)
+ }
+ } else {
+ v.SetLen(i)
+ }
+ }
+ if i == 0 && v.Kind() == reflect.Slice {
+ v.Set(reflect.MakeSlice(v.Type(), 0, 0))
+ }
+}
+
+var nullLiteral = []byte("null")
+
+// object consumes an object from d.data[d.off-1:], decoding into the value v.
+// the first byte ('{') of the object has been read already.
+func (d *decodeState) object(v reflect.Value) {
+ // Check for unmarshaler.
+ u, ut, pv := d.indirect(v, false)
+ if u != nil {
+ d.off--
+ err := u.UnmarshalJSON(d.next())
+ if err != nil {
+ d.error(err)
+ }
+ return
+ }
+ if ut != nil {
+ d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)})
+ d.off--
+ d.next() // skip over { } in input
+ return
+ }
+ v = pv
+
+ // Decoding into nil interface? Switch to non-reflect code.
+ if v.Kind() == reflect.Interface && v.NumMethod() == 0 {
+ v.Set(reflect.ValueOf(d.objectInterface()))
+ return
+ }
+
+ // Check type of target: struct or map[string]T
+ switch v.Kind() {
+ case reflect.Map:
+ // map must have string kind
+ t := v.Type()
+ if t.Key().Kind() != reflect.String {
+ d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)})
+ d.off--
+ d.next() // skip over { } in input
+ return
+ }
+ if v.IsNil() {
+ v.Set(reflect.MakeMap(t))
+ }
+ case reflect.Struct:
+
+ default:
+ d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)})
+ d.off--
+ d.next() // skip over { } in input
+ return
+ }
+
+ var mapElem reflect.Value
+ keys := map[string]bool{}
+
+ for {
+ // Read opening " of string key or closing }.
+ op := d.scanWhile(scanSkipSpace)
+ if op == scanEndObject {
+ // closing } - can only happen on first iteration.
+ break
+ }
+ if op != scanBeginLiteral {
+ d.error(errPhase)
+ }
+
+ // Read key.
+ start := d.off - 1
+ op = d.scanWhile(scanContinue)
+ item := d.data[start : d.off-1]
+ key, ok := unquote(item)
+ if !ok {
+ d.error(errPhase)
+ }
+
+ // Check for duplicate keys.
+ _, ok = keys[key]
+ if !ok {
+ keys[key] = true
+ } else {
+ d.error(fmt.Errorf("json: duplicate key '%s' in object", key))
+ }
+
+ // Figure out field corresponding to key.
+ var subv reflect.Value
+ destring := false // whether the value is wrapped in a string to be decoded first
+
+ if v.Kind() == reflect.Map {
+ elemType := v.Type().Elem()
+ if !mapElem.IsValid() {
+ mapElem = reflect.New(elemType).Elem()
+ } else {
+ mapElem.Set(reflect.Zero(elemType))
+ }
+ subv = mapElem
+ } else {
+ var f *field
+ fields := cachedTypeFields(v.Type())
+ for i := range fields {
+ ff := &fields[i]
+ if bytes.Equal(ff.nameBytes, []byte(key)) {
+ f = ff
+ break
+ }
+ }
+ if f != nil {
+ subv = v
+ destring = f.quoted
+ for _, i := range f.index {
+ if subv.Kind() == reflect.Ptr {
+ if subv.IsNil() {
+ subv.Set(reflect.New(subv.Type().Elem()))
+ }
+ subv = subv.Elem()
+ }
+ subv = subv.Field(i)
+ }
+ }
+ }
+
+ // Read : before value.
+ if op == scanSkipSpace {
+ op = d.scanWhile(scanSkipSpace)
+ }
+ if op != scanObjectKey {
+ d.error(errPhase)
+ }
+
+ // Read value.
+ if destring {
+ switch qv := d.valueQuoted().(type) {
+ case nil:
+ d.literalStore(nullLiteral, subv, false)
+ case string:
+ d.literalStore([]byte(qv), subv, true)
+ default:
+ d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal unquoted value into %v", subv.Type()))
+ }
+ } else {
+ d.value(subv)
+ }
+
+ // Write value back to map;
+ // if using struct, subv points into struct already.
+ if v.Kind() == reflect.Map {
+ kv := reflect.ValueOf(key).Convert(v.Type().Key())
+ v.SetMapIndex(kv, subv)
+ }
+
+ // Next token must be , or }.
+ op = d.scanWhile(scanSkipSpace)
+ if op == scanEndObject {
+ break
+ }
+ if op != scanObjectValue {
+ d.error(errPhase)
+ }
+ }
+}
+
+// literal consumes a literal from d.data[d.off-1:], decoding into the value v.
+// The first byte of the literal has been read already
+// (that's how the caller knows it's a literal).
+func (d *decodeState) literal(v reflect.Value) {
+ // All bytes inside literal return scanContinue op code.
+ start := d.off - 1
+ op := d.scanWhile(scanContinue)
+
+ // Scan read one byte too far; back up.
+ d.off--
+ d.scan.undo(op)
+
+ d.literalStore(d.data[start:d.off], v, false)
+}
+
+// convertNumber converts the number literal s to a float64, int64 or a Number
+// depending on d.numberDecodeType.
+func (d *decodeState) convertNumber(s string) (interface{}, error) {
+ switch d.numberType {
+
+ case UnmarshalJSONNumber:
+ return Number(s), nil
+ case UnmarshalIntOrFloat:
+ v, err := strconv.ParseInt(s, 10, 64)
+ if err == nil {
+ return v, nil
+ }
+
+ // tries to parse integer number in scientific notation
+ f, err := strconv.ParseFloat(s, 64)
+ if err != nil {
+ return nil, &UnmarshalTypeError{"number " + s, reflect.TypeOf(0.0), int64(d.off)}
+ }
+
+ // if it has no decimal value use int64
+ if fi, fd := math.Modf(f); fd == 0.0 {
+ return int64(fi), nil
+ }
+ return f, nil
+ default:
+ f, err := strconv.ParseFloat(s, 64)
+ if err != nil {
+ return nil, &UnmarshalTypeError{"number " + s, reflect.TypeOf(0.0), int64(d.off)}
+ }
+ return f, nil
+ }
+
+}
+
+var numberType = reflect.TypeOf(Number(""))
+
+// literalStore decodes a literal stored in item into v.
+//
+// fromQuoted indicates whether this literal came from unwrapping a
+// string from the ",string" struct tag option. this is used only to
+// produce more helpful error messages.
+func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool) {
+ // Check for unmarshaler.
+ if len(item) == 0 {
+ //Empty string given
+ d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
+ return
+ }
+ wantptr := item[0] == 'n' // null
+ u, ut, pv := d.indirect(v, wantptr)
+ if u != nil {
+ err := u.UnmarshalJSON(item)
+ if err != nil {
+ d.error(err)
+ }
+ return
+ }
+ if ut != nil {
+ if item[0] != '"' {
+ if fromQuoted {
+ d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
+ } else {
+ d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)})
+ }
+ return
+ }
+ s, ok := unquoteBytes(item)
+ if !ok {
+ if fromQuoted {
+ d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
+ } else {
+ d.error(errPhase)
+ }
+ }
+ err := ut.UnmarshalText(s)
+ if err != nil {
+ d.error(err)
+ }
+ return
+ }
+
+ v = pv
+
+ switch c := item[0]; c {
+ case 'n': // null
+ switch v.Kind() {
+ case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
+ v.Set(reflect.Zero(v.Type()))
+ // otherwise, ignore null for primitives/string
+ }
+ case 't', 'f': // true, false
+ value := c == 't'
+ switch v.Kind() {
+ default:
+ if fromQuoted {
+ d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
+ } else {
+ d.saveError(&UnmarshalTypeError{"bool", v.Type(), int64(d.off)})
+ }
+ case reflect.Bool:
+ v.SetBool(value)
+ case reflect.Interface:
+ if v.NumMethod() == 0 {
+ v.Set(reflect.ValueOf(value))
+ } else {
+ d.saveError(&UnmarshalTypeError{"bool", v.Type(), int64(d.off)})
+ }
+ }
+
+ case '"': // string
+ s, ok := unquoteBytes(item)
+ if !ok {
+ if fromQuoted {
+ d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
+ } else {
+ d.error(errPhase)
+ }
+ }
+ switch v.Kind() {
+ default:
+ d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)})
+ case reflect.Slice:
+ if v.Type().Elem().Kind() != reflect.Uint8 {
+ d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)})
+ break
+ }
+ b := make([]byte, base64.StdEncoding.DecodedLen(len(s)))
+ n, err := base64.StdEncoding.Decode(b, s)
+ if err != nil {
+ d.saveError(err)
+ break
+ }
+ v.SetBytes(b[:n])
+ case reflect.String:
+ v.SetString(string(s))
+ case reflect.Interface:
+ if v.NumMethod() == 0 {
+ v.Set(reflect.ValueOf(string(s)))
+ } else {
+ d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)})
+ }
+ }
+
+ default: // number
+ if c != '-' && (c < '0' || c > '9') {
+ if fromQuoted {
+ d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
+ } else {
+ d.error(errPhase)
+ }
+ }
+ s := string(item)
+ switch v.Kind() {
+ default:
+ if v.Kind() == reflect.String && v.Type() == numberType {
+ v.SetString(s)
+ if !isValidNumber(s) {
+ d.error(fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", item))
+ }
+ break
+ }
+ if fromQuoted {
+ d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
+ } else {
+ d.error(&UnmarshalTypeError{"number", v.Type(), int64(d.off)})
+ }
+ case reflect.Interface:
+ n, err := d.convertNumber(s)
+ if err != nil {
+ d.saveError(err)
+ break
+ }
+ if v.NumMethod() != 0 {
+ d.saveError(&UnmarshalTypeError{"number", v.Type(), int64(d.off)})
+ break
+ }
+ v.Set(reflect.ValueOf(n))
+
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ n, err := strconv.ParseInt(s, 10, 64)
+ if err != nil || v.OverflowInt(n) {
+ d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)})
+ break
+ }
+ v.SetInt(n)
+
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ n, err := strconv.ParseUint(s, 10, 64)
+ if err != nil || v.OverflowUint(n) {
+ d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)})
+ break
+ }
+ v.SetUint(n)
+
+ case reflect.Float32, reflect.Float64:
+ n, err := strconv.ParseFloat(s, v.Type().Bits())
+ if err != nil || v.OverflowFloat(n) {
+ d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)})
+ break
+ }
+ v.SetFloat(n)
+ }
+ }
+}
+
+// The xxxInterface routines build up a value to be stored
+// in an empty interface. They are not strictly necessary,
+// but they avoid the weight of reflection in this common case.
+
+// valueInterface is like value but returns interface{}
+func (d *decodeState) valueInterface() interface{} {
+ switch d.scanWhile(scanSkipSpace) {
+ default:
+ d.error(errPhase)
+ panic("unreachable")
+ case scanBeginArray:
+ return d.arrayInterface()
+ case scanBeginObject:
+ return d.objectInterface()
+ case scanBeginLiteral:
+ return d.literalInterface()
+ }
+}
+
+// arrayInterface is like array but returns []interface{}.
+func (d *decodeState) arrayInterface() []interface{} {
+ var v = make([]interface{}, 0)
+ for {
+ // Look ahead for ] - can only happen on first iteration.
+ op := d.scanWhile(scanSkipSpace)
+ if op == scanEndArray {
+ break
+ }
+
+ // Back up so d.value can have the byte we just read.
+ d.off--
+ d.scan.undo(op)
+
+ v = append(v, d.valueInterface())
+
+ // Next token must be , or ].
+ op = d.scanWhile(scanSkipSpace)
+ if op == scanEndArray {
+ break
+ }
+ if op != scanArrayValue {
+ d.error(errPhase)
+ }
+ }
+ return v
+}
+
+// objectInterface is like object but returns map[string]interface{}.
+func (d *decodeState) objectInterface() map[string]interface{} {
+ m := make(map[string]interface{})
+ keys := map[string]bool{}
+
+ for {
+ // Read opening " of string key or closing }.
+ op := d.scanWhile(scanSkipSpace)
+ if op == scanEndObject {
+ // closing } - can only happen on first iteration.
+ break
+ }
+ if op != scanBeginLiteral {
+ d.error(errPhase)
+ }
+
+ // Read string key.
+ start := d.off - 1
+ op = d.scanWhile(scanContinue)
+ item := d.data[start : d.off-1]
+ key, ok := unquote(item)
+ if !ok {
+ d.error(errPhase)
+ }
+
+ // Check for duplicate keys.
+ _, ok = keys[key]
+ if !ok {
+ keys[key] = true
+ } else {
+ d.error(fmt.Errorf("json: duplicate key '%s' in object", key))
+ }
+
+ // Read : before value.
+ if op == scanSkipSpace {
+ op = d.scanWhile(scanSkipSpace)
+ }
+ if op != scanObjectKey {
+ d.error(errPhase)
+ }
+
+ // Read value.
+ m[key] = d.valueInterface()
+
+ // Next token must be , or }.
+ op = d.scanWhile(scanSkipSpace)
+ if op == scanEndObject {
+ break
+ }
+ if op != scanObjectValue {
+ d.error(errPhase)
+ }
+ }
+ return m
+}
+
+// literalInterface is like literal but returns an interface value.
+func (d *decodeState) literalInterface() interface{} {
+ // All bytes inside literal return scanContinue op code.
+ start := d.off - 1
+ op := d.scanWhile(scanContinue)
+
+ // Scan read one byte too far; back up.
+ d.off--
+ d.scan.undo(op)
+ item := d.data[start:d.off]
+
+ switch c := item[0]; c {
+ case 'n': // null
+ return nil
+
+ case 't', 'f': // true, false
+ return c == 't'
+
+ case '"': // string
+ s, ok := unquote(item)
+ if !ok {
+ d.error(errPhase)
+ }
+ return s
+
+ default: // number
+ if c != '-' && (c < '0' || c > '9') {
+ d.error(errPhase)
+ }
+ n, err := d.convertNumber(string(item))
+ if err != nil {
+ d.saveError(err)
+ }
+ return n
+ }
+}
+
+// getu4 decodes \uXXXX from the beginning of s, returning the hex value,
+// or it returns -1.
+func getu4(s []byte) rune {
+ if len(s) < 6 || s[0] != '\\' || s[1] != 'u' {
+ return -1
+ }
+ r, err := strconv.ParseUint(string(s[2:6]), 16, 64)
+ if err != nil {
+ return -1
+ }
+ return rune(r)
+}
+
+// unquote converts a quoted JSON string literal s into an actual string t.
+// The rules are different than for Go, so cannot use strconv.Unquote.
+func unquote(s []byte) (t string, ok bool) {
+ s, ok = unquoteBytes(s)
+ t = string(s)
+ return
+}
+
+func unquoteBytes(s []byte) (t []byte, ok bool) {
+ if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' {
+ return
+ }
+ s = s[1 : len(s)-1]
+
+ // Check for unusual characters. If there are none,
+ // then no unquoting is needed, so return a slice of the
+ // original bytes.
+ r := 0
+ for r < len(s) {
+ c := s[r]
+ if c == '\\' || c == '"' || c < ' ' {
+ break
+ }
+ if c < utf8.RuneSelf {
+ r++
+ continue
+ }
+ rr, size := utf8.DecodeRune(s[r:])
+ if rr == utf8.RuneError && size == 1 {
+ break
+ }
+ r += size
+ }
+ if r == len(s) {
+ return s, true
+ }
+
+ b := make([]byte, len(s)+2*utf8.UTFMax)
+ w := copy(b, s[0:r])
+ for r < len(s) {
+ // Out of room? Can only happen if s is full of
+ // malformed UTF-8 and we're replacing each
+ // byte with RuneError.
+ if w >= len(b)-2*utf8.UTFMax {
+ nb := make([]byte, (len(b)+utf8.UTFMax)*2)
+ copy(nb, b[0:w])
+ b = nb
+ }
+ switch c := s[r]; {
+ case c == '\\':
+ r++
+ if r >= len(s) {
+ return
+ }
+ switch s[r] {
+ default:
+ return
+ case '"', '\\', '/', '\'':
+ b[w] = s[r]
+ r++
+ w++
+ case 'b':
+ b[w] = '\b'
+ r++
+ w++
+ case 'f':
+ b[w] = '\f'
+ r++
+ w++
+ case 'n':
+ b[w] = '\n'
+ r++
+ w++
+ case 'r':
+ b[w] = '\r'
+ r++
+ w++
+ case 't':
+ b[w] = '\t'
+ r++
+ w++
+ case 'u':
+ r--
+ rr := getu4(s[r:])
+ if rr < 0 {
+ return
+ }
+ r += 6
+ if utf16.IsSurrogate(rr) {
+ rr1 := getu4(s[r:])
+ if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar {
+ // A valid pair; consume.
+ r += 6
+ w += utf8.EncodeRune(b[w:], dec)
+ break
+ }
+ // Invalid surrogate; fall back to replacement rune.
+ rr = unicode.ReplacementChar
+ }
+ w += utf8.EncodeRune(b[w:], rr)
+ }
+
+ // Quote, control characters are invalid.
+ case c == '"', c < ' ':
+ return
+
+ // ASCII
+ case c < utf8.RuneSelf:
+ b[w] = c
+ r++
+ w++
+
+ // Coerce to well-formed UTF-8.
+ default:
+ rr, size := utf8.DecodeRune(s[r:])
+ r += size
+ w += utf8.EncodeRune(b[w:], rr)
+ }
+ }
+ return b[0:w], true
+}
diff --git a/vendor/github.com/go-jose/go-jose/v4/json/encode.go b/vendor/github.com/go-jose/go-jose/v4/json/encode.go
new file mode 100644
index 0000000000..98de68ce1e
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v4/json/encode.go
@@ -0,0 +1,1197 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package json implements encoding and decoding of JSON objects as defined in
+// RFC 4627. The mapping between JSON objects and Go values is described
+// in the documentation for the Marshal and Unmarshal functions.
+//
+// See "JSON and Go" for an introduction to this package:
+// https://golang.org/doc/articles/json_and_go.html
+package json
+
+import (
+ "bytes"
+ "encoding"
+ "encoding/base64"
+ "fmt"
+ "math"
+ "reflect"
+ "runtime"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "unicode"
+ "unicode/utf8"
+)
+
+// Marshal returns the JSON encoding of v.
+//
+// Marshal traverses the value v recursively.
+// If an encountered value implements the Marshaler interface
+// and is not a nil pointer, Marshal calls its MarshalJSON method
+// to produce JSON. If no MarshalJSON method is present but the
+// value implements encoding.TextMarshaler instead, Marshal calls
+// its MarshalText method.
+// The nil pointer exception is not strictly necessary
+// but mimics a similar, necessary exception in the behavior of
+// UnmarshalJSON.
+//
+// Otherwise, Marshal uses the following type-dependent default encodings:
+//
+// Boolean values encode as JSON booleans.
+//
+// Floating point, integer, and Number values encode as JSON numbers.
+//
+// String values encode as JSON strings coerced to valid UTF-8,
+// replacing invalid bytes with the Unicode replacement rune.
+// The angle brackets "<" and ">" are escaped to "\u003c" and "\u003e"
+// to keep some browsers from misinterpreting JSON output as HTML.
+// Ampersand "&" is also escaped to "\u0026" for the same reason.
+//
+// Array and slice values encode as JSON arrays, except that
+// []byte encodes as a base64-encoded string, and a nil slice
+// encodes as the null JSON object.
+//
+// Struct values encode as JSON objects. Each exported struct field
+// becomes a member of the object unless
+// - the field's tag is "-", or
+// - the field is empty and its tag specifies the "omitempty" option.
+//
+// The empty values are false, 0, any
+// nil pointer or interface value, and any array, slice, map, or string of
+// length zero. The object's default key string is the struct field name
+// but can be specified in the struct field's tag value. The "json" key in
+// the struct field's tag value is the key name, followed by an optional comma
+// and options. Examples:
+//
+// // Field is ignored by this package.
+// Field int `json:"-"`
+//
+// // Field appears in JSON as key "myName".
+// Field int `json:"myName"`
+//
+// // Field appears in JSON as key "myName" and
+// // the field is omitted from the object if its value is empty,
+// // as defined above.
+// Field int `json:"myName,omitempty"`
+//
+// // Field appears in JSON as key "Field" (the default), but
+// // the field is skipped if empty.
+// // Note the leading comma.
+// Field int `json:",omitempty"`
+//
+// The "string" option signals that a field is stored as JSON inside a
+// JSON-encoded string. It applies only to fields of string, floating point,
+// integer, or boolean types. This extra level of encoding is sometimes used
+// when communicating with JavaScript programs:
+//
+// Int64String int64 `json:",string"`
+//
+// The key name will be used if it's a non-empty string consisting of
+// only Unicode letters, digits, dollar signs, percent signs, hyphens,
+// underscores and slashes.
+//
+// Anonymous struct fields are usually marshaled as if their inner exported fields
+// were fields in the outer struct, subject to the usual Go visibility rules amended
+// as described in the next paragraph.
+// An anonymous struct field with a name given in its JSON tag is treated as
+// having that name, rather than being anonymous.
+// An anonymous struct field of interface type is treated the same as having
+// that type as its name, rather than being anonymous.
+//
+// The Go visibility rules for struct fields are amended for JSON when
+// deciding which field to marshal or unmarshal. If there are
+// multiple fields at the same level, and that level is the least
+// nested (and would therefore be the nesting level selected by the
+// usual Go rules), the following extra rules apply:
+//
+// 1) Of those fields, if any are JSON-tagged, only tagged fields are considered,
+// even if there are multiple untagged fields that would otherwise conflict.
+// 2) If there is exactly one field (tagged or not according to the first rule), that is selected.
+// 3) Otherwise there are multiple fields, and all are ignored; no error occurs.
+//
+// Handling of anonymous struct fields is new in Go 1.1.
+// Prior to Go 1.1, anonymous struct fields were ignored. To force ignoring of
+// an anonymous struct field in both current and earlier versions, give the field
+// a JSON tag of "-".
+//
+// Map values encode as JSON objects.
+// The map's key type must be string; the map keys are used as JSON object
+// keys, subject to the UTF-8 coercion described for string values above.
+//
+// Pointer values encode as the value pointed to.
+// A nil pointer encodes as the null JSON object.
+//
+// Interface values encode as the value contained in the interface.
+// A nil interface value encodes as the null JSON object.
+//
+// Channel, complex, and function values cannot be encoded in JSON.
+// Attempting to encode such a value causes Marshal to return
+// an UnsupportedTypeError.
+//
+// JSON cannot represent cyclic data structures and Marshal does not
+// handle them. Passing cyclic structures to Marshal will result in
+// an infinite recursion.
+func Marshal(v interface{}) ([]byte, error) {
+ e := &encodeState{}
+ err := e.marshal(v)
+ if err != nil {
+ return nil, err
+ }
+ return e.Bytes(), nil
+}
+
+// MarshalIndent is like Marshal but applies Indent to format the output.
+func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
+ b, err := Marshal(v)
+ if err != nil {
+ return nil, err
+ }
+ var buf bytes.Buffer
+ err = Indent(&buf, b, prefix, indent)
+ if err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
+}
+
+// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029
+// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029
+// so that the JSON will be safe to embed inside HTML