mirror of https://github.com/status-im/consul.git
[NET-4792] Add integrations tests for jwt-auth (#18169)
This commit is contained in:
parent
62005369b5
commit
921445712e
|
@ -8,7 +8,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/hashicorp/consul/test/integration/consul-container/libs/utils"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -18,6 +17,7 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
"github.com/hashicorp/consul/sdk/testutil/retry"
|
"github.com/hashicorp/consul/sdk/testutil/retry"
|
||||||
|
"github.com/hashicorp/consul/test/integration/consul-container/libs/utils"
|
||||||
"github.com/hashicorp/serf/serf"
|
"github.com/hashicorp/serf/serf"
|
||||||
|
|
||||||
goretry "github.com/avast/retry-go"
|
goretry "github.com/avast/retry-go"
|
||||||
|
|
|
@ -167,14 +167,13 @@ func NewConnectService(ctx context.Context, sidecarCfg SidecarConfig, serviceBin
|
||||||
namePrefix := fmt.Sprintf("%s-service-connect-%s", node.GetDatacenter(), sidecarCfg.Name)
|
namePrefix := fmt.Sprintf("%s-service-connect-%s", node.GetDatacenter(), sidecarCfg.Name)
|
||||||
containerName := utils.RandName(namePrefix)
|
containerName := utils.RandName(namePrefix)
|
||||||
|
|
||||||
agentConfig := node.GetConfig()
|
|
||||||
internalAdminPort, err := node.ClaimAdminPort()
|
internalAdminPort, err := node.ClaimAdminPort()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("agent image name", agentConfig.DockerImage())
|
fmt.Println("agent image name", nodeConfig.DockerImage())
|
||||||
imageVersion := utils.SideCarVersion(agentConfig.DockerImage())
|
imageVersion := utils.SideCarVersion(nodeConfig.DockerImage())
|
||||||
req := testcontainers.ContainerRequest{
|
req := testcontainers.ContainerRequest{
|
||||||
Image: fmt.Sprintf("consul-envoy:%s", imageVersion),
|
Image: fmt.Sprintf("consul-envoy:%s", imageVersion),
|
||||||
WaitingFor: wait.ForLog("").WithStartupTimeout(100 * time.Second),
|
WaitingFor: wait.ForLog("").WithStartupTimeout(100 * time.Second),
|
||||||
|
@ -238,6 +237,21 @@ func NewConnectService(ctx context.Context, sidecarCfg SidecarConfig, serviceBin
|
||||||
req.Env["CONSUL_GRPC_ADDR"] = fmt.Sprintf("http://127.0.0.1:%d", 8502)
|
req.Env["CONSUL_GRPC_ADDR"] = fmt.Sprintf("http://127.0.0.1:%d", 8502)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if nodeConfig.ACLEnabled {
|
||||||
|
client := node.GetClient()
|
||||||
|
token, _, err := client.ACL().TokenCreate(&api.ACLToken{
|
||||||
|
ServiceIdentities: []*api.ACLServiceIdentity{
|
||||||
|
{ServiceName: sidecarCfg.ServiceID},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Env["CONSUL_HTTP_TOKEN"] = token.SecretID
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
appPortStrs []string
|
appPortStrs []string
|
||||||
adminPortStr = strconv.Itoa(internalAdminPort)
|
adminPortStr = strconv.Itoa(internalAdminPort)
|
||||||
|
|
|
@ -24,20 +24,22 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestJWTAuthConnectService summary
|
// TestJWTAuthConnectService summary:
|
||||||
// This test ensures that when we have an intention referencing a JWT, requests
|
// This test ensures that when we have an intention referencing a JWT, requests
|
||||||
// without JWT authorization headers are denied. And requests with the correct JWT
|
// without JWT authorization headers are denied and requests with the correct JWT
|
||||||
// Authorization header are successful
|
// Authorization header are successful.
|
||||||
//
|
//
|
||||||
// Steps:
|
// Steps:
|
||||||
// - Creates a single agent cluster
|
// - Creates a single agent cluster
|
||||||
|
// - Generates a JWKS and 2 JWTs with different claims
|
||||||
|
// - Generates another JWKS with a single JWT
|
||||||
|
// - Configures proxy defaults, providers and intentions
|
||||||
// - Creates a static-server and sidecar containers
|
// - Creates a static-server and sidecar containers
|
||||||
// - Registers the created static-server and sidecar with consul
|
// - Registers the created static-server and sidecar with consul
|
||||||
// - Create a static-client and sidecar containers
|
// - Create a static-client and sidecar containers
|
||||||
// - Registers the static-client and sidecar with consul
|
// - Registers the static-client and sidecar with consul
|
||||||
// - Ensure client sidecar is running as expected
|
// - Ensure client sidecar is running as expected
|
||||||
// - Make a request without the JWT Authorization header and expects 401 StatusUnauthorized
|
// - Runs a couple of scenarios to ensure jwt validation works as expected
|
||||||
// - Make a request with the JWT Authorization header and expects a 200
|
|
||||||
func TestJWTAuthConnectService(t *testing.T) {
|
func TestJWTAuthConnectService(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -47,39 +49,65 @@ func TestJWTAuthConnectService(t *testing.T) {
|
||||||
ApplyDefaultProxySettings: true,
|
ApplyDefaultProxySettings: true,
|
||||||
BuildOpts: &libcluster.BuildOptions{
|
BuildOpts: &libcluster.BuildOptions{
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
InjectAutoEncryption: true,
|
InjectCerts: true,
|
||||||
InjectGossipEncryption: true,
|
InjectGossipEncryption: false,
|
||||||
|
AllowHTTPAnyway: true,
|
||||||
|
ACLEnabled: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// generate jwks and 2 jwts with different claims for provider 1
|
||||||
|
jwksOne, privOne := makeJWKS(t)
|
||||||
|
claimsOne := makeTestClaims("https://legit.issuer.internal/", "https://consul.test")
|
||||||
|
jwtOne := makeJWT(t, privOne, claimsOne, testClaimPayload{UserType: "admin", FirstName: "admin"})
|
||||||
|
jwtOneAdmin := makeJWT(t, privOne, claimsOne, testClaimPayload{UserType: "client", FirstName: "non-admin"})
|
||||||
|
provider1 := makeTestJWTProvider("okta", jwksOne, claimsOne)
|
||||||
|
|
||||||
|
// generate another jwks and jwt for provider 2
|
||||||
|
jwksTwo, privTwo := makeJWKS(t)
|
||||||
|
claimsTwo := makeTestClaims("https://another.issuer.internal/", "https://consul.test")
|
||||||
|
jwtTwo := makeJWT(t, privTwo, claimsTwo, testClaimPayload{})
|
||||||
|
provider2 := makeTestJWTProvider("auth0", jwksTwo, claimsTwo)
|
||||||
|
|
||||||
|
// configure proxy-defaults, jwt providers and intentions
|
||||||
|
configureProxyDefaults(t, cluster)
|
||||||
|
configureJWTProviders(t, cluster, provider1, provider2)
|
||||||
|
configureIntentions(t, cluster, provider1, provider2)
|
||||||
|
|
||||||
clientService := createServices(t, cluster)
|
clientService := createServices(t, cluster)
|
||||||
_, clientPort := clientService.GetAddr()
|
_, clientPort := clientService.GetAddr()
|
||||||
_, clientAdminPort := clientService.GetAdminAddr()
|
_, adminPort := clientService.GetAdminAddr()
|
||||||
|
|
||||||
libassert.AssertUpstreamEndpointStatus(t, clientAdminPort, "static-server.default", "HEALTHY", 1)
|
|
||||||
libassert.AssertContainerState(t, clientService, "running")
|
libassert.AssertContainerState(t, clientService, "running")
|
||||||
libassert.AssertFortioName(t, fmt.Sprintf("http://localhost:%d", clientPort), "static-server", "")
|
libassert.AssertUpstreamEndpointStatus(t, adminPort, "static-server.default", "HEALTHY", 1)
|
||||||
|
|
||||||
claims := jwt.Claims{
|
// request to restricted endpoint with no jwt should be denied
|
||||||
Subject: "r3qXcK2bix9eFECzsU3Sbmh0K16fatW6@clients",
|
doRequest(t, fmt.Sprintf("http://localhost:%d/restricted/foo", clientPort), http.StatusForbidden, "")
|
||||||
Audience: jwt.Audience{"https://consul.test"},
|
|
||||||
Issuer: "https://legit.issuer.internal/",
|
|
||||||
NotBefore: jwt.NewNumericDate(time.Now().Add(-5 * time.Second)),
|
|
||||||
Expiry: jwt.NewNumericDate(time.Now().Add(60 * time.Minute)),
|
|
||||||
}
|
|
||||||
|
|
||||||
jwks, jwt := makeJWKSAndJWT(t, claims)
|
// request with jwt 1 /restricted/foo should be disallowed
|
||||||
|
doRequest(t, fmt.Sprintf("http://localhost:%d/restricted/foo", clientPort), http.StatusForbidden, jwtOne)
|
||||||
|
|
||||||
// configure proxy-defaults, jwt-provider and intention
|
// request with jwt 1 /other/foo should be allowed
|
||||||
configureProxyDefaults(t, cluster)
|
libassert.HTTPServiceEchoesWithHeaders(t, "localhost", clientPort, "other/foo", makeAuthHeaders(jwtOne))
|
||||||
configureJWTProvider(t, cluster, jwks, claims)
|
|
||||||
configureIntentions(t, cluster)
|
|
||||||
|
|
||||||
baseURL := fmt.Sprintf("http://localhost:%d", clientPort)
|
// request with jwt 1 /other/foo with mismatched claims should be disallowed
|
||||||
// TODO(roncodingenthusiast): update test to reflect jwt-auth filter in metadata mode
|
doRequest(t, fmt.Sprintf("http://localhost:%d/other/foo", clientPort), http.StatusForbidden, jwtOneAdmin)
|
||||||
doRequest(t, baseURL, http.StatusOK, "")
|
|
||||||
// succeeds with jwt
|
// request with provider 1 /foo should be allowed
|
||||||
doRequest(t, baseURL, http.StatusOK, jwt)
|
libassert.HTTPServiceEchoesWithHeaders(t, "localhost", clientPort, "foo", makeAuthHeaders(jwtOne))
|
||||||
|
|
||||||
|
// request with jwt 2 to /foo should be denied
|
||||||
|
doRequest(t, fmt.Sprintf("http://localhost:%d/foo", clientPort), http.StatusForbidden, jwtTwo)
|
||||||
|
|
||||||
|
// request with jwt 2 to /restricted/foo should be allowed
|
||||||
|
libassert.HTTPServiceEchoesWithHeaders(t, "localhost", clientPort, "restricted/foo", makeAuthHeaders(jwtTwo))
|
||||||
|
|
||||||
|
// request with jwt 2 to /other/foo should be denied
|
||||||
|
doRequest(t, fmt.Sprintf("http://localhost:%d/other/foo", clientPort), http.StatusForbidden, jwtTwo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeAuthHeaders(jwt string) map[string]string {
|
||||||
|
return map[string]string{"Authorization": fmt.Sprintf("Bearer %s", jwt)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createServices(t *testing.T, cluster *libcluster.Cluster) libservice.Service {
|
func createServices(t *testing.T, cluster *libcluster.Cluster) libservice.Service {
|
||||||
|
@ -92,25 +120,25 @@ func createServices(t *testing.T, cluster *libcluster.Cluster) libservice.Servic
|
||||||
HTTPPort: 8080,
|
HTTPPort: 8080,
|
||||||
GRPCPort: 8079,
|
GRPCPort: 8079,
|
||||||
}
|
}
|
||||||
|
apiOpts := &api.QueryOptions{Token: cluster.TokenBootstrap}
|
||||||
|
|
||||||
// Create a service and proxy instance
|
// Create a service and proxy instance
|
||||||
_, _, err := libservice.CreateAndRegisterStaticServerAndSidecar(node, serviceOpts)
|
_, _, err := libservice.CreateAndRegisterStaticServerAndSidecar(node, serviceOpts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
libassert.CatalogServiceExists(t, client, "static-server-sidecar-proxy", nil)
|
libassert.CatalogServiceExists(t, client, "static-server-sidecar-proxy", apiOpts)
|
||||||
libassert.CatalogServiceExists(t, client, libservice.StaticServerServiceName, nil)
|
libassert.CatalogServiceExists(t, client, libservice.StaticServerServiceName, apiOpts)
|
||||||
|
|
||||||
// Create a client proxy instance with the server as an upstream
|
// Create a client proxy instance with the server as an upstream
|
||||||
clientConnectProxy, err := libservice.CreateAndRegisterStaticClientSidecar(node, "", false, false)
|
clientConnectProxy, err := libservice.CreateAndRegisterStaticClientSidecar(node, "", false, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
libassert.CatalogServiceExists(t, client, "static-client-sidecar-proxy", nil)
|
libassert.CatalogServiceExists(t, client, "static-client-sidecar-proxy", apiOpts)
|
||||||
|
|
||||||
return clientConnectProxy
|
return clientConnectProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates a JWKS and JWT that will be used for validation
|
func makeJWKS(t *testing.T) (string, string) {
|
||||||
func makeJWKSAndJWT(t *testing.T, claims jwt.Claims) (string, string) {
|
|
||||||
pub, priv, err := libutils.GenerateKey()
|
pub, priv, err := libutils.GenerateKey()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -120,46 +148,36 @@ func makeJWKSAndJWT(t *testing.T, claims jwt.Claims) (string, string) {
|
||||||
jwksJson, err := json.Marshal(jwks)
|
jwksJson, err := json.Marshal(jwks)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
type orgs struct {
|
return string(jwksJson), priv
|
||||||
Primary string `json:"primary"`
|
}
|
||||||
}
|
|
||||||
privateCl := struct {
|
|
||||||
FirstName string `json:"first_name"`
|
|
||||||
Org orgs `json:"org"`
|
|
||||||
Groups []string `json:"groups"`
|
|
||||||
}{
|
|
||||||
FirstName: "jeff2",
|
|
||||||
Org: orgs{"engineering"},
|
|
||||||
Groups: []string{"foo", "bar"},
|
|
||||||
}
|
|
||||||
|
|
||||||
jwt, err := libutils.SignJWT(priv, claims, privateCl)
|
type testClaimPayload struct {
|
||||||
|
UserType string
|
||||||
|
FirstName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeJWT(t *testing.T, priv string, claims jwt.Claims, payload testClaimPayload) string {
|
||||||
|
jwt, err := libutils.SignJWT(priv, claims, payload)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return string(jwksJson), jwt
|
|
||||||
|
return jwt
|
||||||
}
|
}
|
||||||
|
|
||||||
// configures the protocol to http as this is needed for jwt-auth
|
// configures the protocol to http as this is needed for jwt-auth
|
||||||
func configureProxyDefaults(t *testing.T, cluster *libcluster.Cluster) {
|
func configureProxyDefaults(t *testing.T, cluster *libcluster.Cluster) {
|
||||||
client := cluster.Agents[0].GetClient()
|
require.NoError(t, cluster.ConfigEntryWrite(&api.ProxyConfigEntry{
|
||||||
|
|
||||||
ok, _, err := client.ConfigEntries().Set(&api.ProxyConfigEntry{
|
|
||||||
Kind: api.ProxyDefaults,
|
Kind: api.ProxyDefaults,
|
||||||
Name: api.ProxyConfigGlobal,
|
Name: api.ProxyConfigGlobal,
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
"protocol": "http",
|
"protocol": "http",
|
||||||
},
|
},
|
||||||
}, nil)
|
}))
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates a JWT local provider
|
func makeTestJWTProvider(name string, jwks string, claims jwt.Claims) *api.JWTProviderConfigEntry {
|
||||||
func configureJWTProvider(t *testing.T, cluster *libcluster.Cluster, jwks string, claims jwt.Claims) {
|
return &api.JWTProviderConfigEntry{
|
||||||
client := cluster.Agents[0].GetClient()
|
|
||||||
|
|
||||||
ok, _, err := client.ConfigEntries().Set(&api.JWTProviderConfigEntry{
|
|
||||||
Kind: api.JWTProvider,
|
Kind: api.JWTProvider,
|
||||||
Name: "test-jwt",
|
Name: name,
|
||||||
JSONWebKeySet: &api.JSONWebKeySet{
|
JSONWebKeySet: &api.JSONWebKeySet{
|
||||||
Local: &api.LocalJWKS{
|
Local: &api.LocalJWKS{
|
||||||
JWKS: base64.StdEncoding.EncodeToString([]byte(jwks)),
|
JWKS: base64.StdEncoding.EncodeToString([]byte(jwks)),
|
||||||
|
@ -167,42 +185,85 @@ func configureJWTProvider(t *testing.T, cluster *libcluster.Cluster, jwks string
|
||||||
},
|
},
|
||||||
Issuer: claims.Issuer,
|
Issuer: claims.Issuer,
|
||||||
Audiences: claims.Audience,
|
Audiences: claims.Audience,
|
||||||
}, nil)
|
}
|
||||||
require.NoError(t, err)
|
}
|
||||||
require.True(t, ok)
|
|
||||||
|
// creates a JWT local provider
|
||||||
|
func configureJWTProviders(t *testing.T, cluster *libcluster.Cluster, providers ...*api.JWTProviderConfigEntry) {
|
||||||
|
for _, prov := range providers {
|
||||||
|
require.NoError(t, cluster.ConfigEntryWrite(prov))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates an intention referencing the jwt provider
|
// creates an intention referencing the jwt provider
|
||||||
func configureIntentions(t *testing.T, cluster *libcluster.Cluster) {
|
func configureIntentions(t *testing.T, cluster *libcluster.Cluster, provider1, provider2 *api.JWTProviderConfigEntry) {
|
||||||
client := cluster.Agents[0].GetClient()
|
intention := api.ServiceIntentionsConfigEntry{
|
||||||
|
|
||||||
ok, _, err := client.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{
|
|
||||||
Kind: "service-intentions",
|
Kind: "service-intentions",
|
||||||
Name: libservice.StaticServerServiceName,
|
Name: libservice.StaticServerServiceName,
|
||||||
Sources: []*api.SourceIntention{
|
Sources: []*api.SourceIntention{
|
||||||
{
|
{
|
||||||
Name: libservice.StaticClientServiceName,
|
Name: libservice.StaticClientServiceName,
|
||||||
Action: api.IntentionActionAllow,
|
Permissions: []*api.IntentionPermission{
|
||||||
|
{
|
||||||
|
Action: api.IntentionActionAllow,
|
||||||
|
HTTP: &api.IntentionHTTPPermission{
|
||||||
|
PathPrefix: "/restricted/",
|
||||||
|
},
|
||||||
|
JWT: &api.IntentionJWTRequirement{
|
||||||
|
Providers: []*api.IntentionJWTProvider{
|
||||||
|
{
|
||||||
|
Name: provider2.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: api.IntentionActionAllow,
|
||||||
|
HTTP: &api.IntentionHTTPPermission{
|
||||||
|
PathPrefix: "/",
|
||||||
|
},
|
||||||
|
JWT: &api.IntentionJWTRequirement{
|
||||||
|
Providers: []*api.IntentionJWTProvider{
|
||||||
|
{
|
||||||
|
Name: provider1.Name,
|
||||||
|
VerifyClaims: []*api.IntentionJWTClaimVerification{
|
||||||
|
{
|
||||||
|
Path: []string{"UserType"},
|
||||||
|
Value: "admin",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
JWT: &api.IntentionJWTRequirement{
|
Name: "other-client",
|
||||||
Providers: []*api.IntentionJWTProvider{
|
Permissions: []*api.IntentionPermission{
|
||||||
{
|
{
|
||||||
Name: "test-jwt",
|
Action: api.IntentionActionAllow,
|
||||||
VerifyClaims: []*api.IntentionJWTClaimVerification{},
|
HTTP: &api.IntentionHTTPPermission{
|
||||||
|
PathPrefix: "/other/",
|
||||||
|
},
|
||||||
|
JWT: &api.IntentionJWTRequirement{
|
||||||
|
Providers: []*api.IntentionJWTProvider{
|
||||||
|
{
|
||||||
|
Name: provider2.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, nil)
|
}
|
||||||
require.NoError(t, err)
|
require.NoError(t, cluster.ConfigEntryWrite(&intention))
|
||||||
require.True(t, ok)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func doRequest(t *testing.T, url string, expStatus int, jwt string) {
|
func doRequest(t *testing.T, url string, expStatus int, jwt string) {
|
||||||
retry.RunWith(&retry.Timer{Timeout: 5 * time.Second, Wait: time.Second}, t, func(r *retry.R) {
|
retry.RunWith(&retry.Timer{Timeout: 5 * time.Second, Wait: time.Second}, t, func(r *retry.R) {
|
||||||
|
|
||||||
client := cleanhttp.DefaultClient()
|
client := cleanhttp.DefaultClient()
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
require.NoError(r, err)
|
require.NoError(r, err)
|
||||||
if jwt != "" {
|
if jwt != "" {
|
||||||
|
@ -213,3 +274,13 @@ func doRequest(t *testing.T, url string, expStatus int, jwt string) {
|
||||||
require.Equal(r, expStatus, resp.StatusCode)
|
require.Equal(r, expStatus, resp.StatusCode)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeTestClaims(issuer, audience string) jwt.Claims {
|
||||||
|
return jwt.Claims{
|
||||||
|
Subject: "r3qXcK2bix9eFECzsU3Sbmh0K16fatW6@clients",
|
||||||
|
Audience: jwt.Audience{audience},
|
||||||
|
Issuer: issuer,
|
||||||
|
NotBefore: jwt.NewNumericDate(time.Now().Add(-5 * time.Second)),
|
||||||
|
Expiry: jwt.NewNumericDate(time.Now().Add(60 * time.Minute)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue