Files
ziti/tests/api_client_client.go
T
Andrew Martinez 6515e3615c fixes #3680 add revocation management API, CLI, and enforcement (#3789)
* fixes #3680 add revocation management API, CLI, and enforcement

- adds Management API endpoints for revocations (POST, GET, LIST) with
  type-aware validation (JTI/API_SESSION require UUID, IDENTITY requires
  existing identity)
- adds CLI commands: ziti edge create revocation identity|api-session|jti
- adds revocation checks to resolveOidcSession in security_ctx.go so the
  REST API returns 401 for revoked OIDC tokens. Previously only
  ValidateAccessToken (router ctrl channel path) checked revocations,
  so revoked tokens still received 200 OK from the management and client
  HTTP APIs
- adds api-session revocation check to ValidateAccessToken, which only
  checked JTI and identity revocations
- adds Type field to Revocation model, store, and protobuf message
- adds integration tests covering CRUD, input validation, and token
  enforcement for all three revocation types
- use release edge-api@v0.28.1
2026-04-14 15:53:03 -04:00

811 lines
24 KiB
Go

package tests
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"fmt"
"math/big"
"net/http"
"net/url"
"strings"
"time"
"github.com/dgryski/dgoogauth"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"github.com/openziti/edge-api/rest_client_api_client"
clientAuthentication "github.com/openziti/edge-api/rest_client_api_client/authentication"
clientCurrentApiSession "github.com/openziti/edge-api/rest_client_api_client/current_api_session"
clientCurrentIdentity "github.com/openziti/edge-api/rest_client_api_client/current_identity"
clientEnroll "github.com/openziti/edge-api/rest_client_api_client/enroll"
"github.com/openziti/edge-api/rest_model"
"github.com/openziti/edge-api/rest_util"
nfPem "github.com/openziti/foundation/v2/pem"
"github.com/openziti/identity/certtools"
edgeApis "github.com/openziti/sdk-golang/edge-apis"
"github.com/openziti/sdk-golang/ziti"
"github.com/openziti/ziti/v2/common"
"github.com/zitadel/oidc/v3/pkg/oidc"
)
type ClientHelperClient struct {
*edgeApis.ClientApiClient
testCtx *TestContext
}
func (helper *ClientHelperClient) GetVersion() (*rest_model.Version, error) {
resp, err := helper.API.Informational.ListVersion(nil)
if err != nil {
return nil, rest_util.WrapErr(err)
}
return resp.Payload.Data, nil
}
func (helper *ClientHelperClient) CompleteJwtTokenEnrollmentToCertAuth(enrollmentJwt string) (*edgeApis.CertCredentials, error) {
privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil {
return nil, err
}
template := &x509.CertificateRequest{}
newCsr, err := x509.CreateCertificateRequest(rand.Reader, template, privateKey)
if err != nil {
return nil, err
}
newCsrPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: newCsr})
params := clientEnroll.NewEnrollTokenParams()
params.TokenEnrollmentRequest = &rest_model.TokenEnrollmentRequest{
ClientCsr: string(newCsrPem),
}
params.Authorization = "Bearer " + enrollmentJwt
resp, err := helper.API.Enroll.EnrollToken(params)
if err != nil {
return nil, rest_util.WrapErr(err)
}
certs := nfPem.PemStringToCertificates(resp.Payload.Data.Cert)
if len(certs) == 0 {
return nil, fmt.Errorf("no certificates returned from enrollment")
}
creds := edgeApis.NewCertCredentials(certs, privateKey)
return creds, nil
}
func (helper *ClientHelperClient) CompleteJwtTokenEnrollmentToTokenAuth(enrollmentJwt string) error {
params := clientEnroll.NewEnrollTokenParams()
params.TokenEnrollmentRequest = &rest_model.TokenEnrollmentRequest{}
params.Authorization = "Bearer " + enrollmentJwt
_, err := helper.API.Enroll.EnrollToken(params)
if err != nil {
return rest_util.WrapErr(err)
}
return nil
}
func (helper *ClientHelperClient) CompleteOttEnrollment(enrollmentToken string) (*edgeApis.CertCredentials, error) {
token := enrollmentToken
if IsJwt(token) {
jwtParser := jwt.NewParser()
enrollmentClaims := &ziti.EnrollmentClaims{}
_, _, err := jwtParser.ParseUnverified(token, enrollmentClaims)
if err != nil {
return nil, fmt.Errorf("could not parse enrollment JWT: %w", rest_util.WrapErr(err))
}
token = enrollmentClaims.ID
}
privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil {
return nil, err
}
template := &x509.CertificateRequest{}
csr, err := x509.CreateCertificateRequest(rand.Reader, template, privateKey)
if err != nil {
return nil, rest_util.WrapErr(err)
}
csrPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csr})
params := clientEnroll.NewEnrollOttParams()
params.OttEnrollmentRequest = &rest_model.OttEnrollmentRequest{
ClientCsr: string(csrPem),
Token: token,
}
resp, err := helper.API.Enroll.EnrollOtt(params)
if err != nil {
return nil, rest_util.WrapErr(err)
}
certs := nfPem.PemStringToCertificates(resp.Payload.Data.Cert)
if len(certs) == 0 {
return nil, fmt.Errorf("no certificates returned from enrollment")
}
caCerts := nfPem.PemStringToCertificates(resp.Payload.Data.Ca)
var caPool *x509.CertPool
if len(caCerts) == 0 {
caPool = helper.testCtx.ControllerCaPool()
} else {
caPool = x509.NewCertPool()
for _, caCert := range caCerts {
caPool.AddCert(caCert)
}
}
return &edgeApis.CertCredentials{
BaseCredentials: edgeApis.BaseCredentials{
CaPool: caPool,
},
Certs: certs,
Key: privateKey,
}, nil
}
func (helper *ClientHelperClient) CompleteOttGenericEnrollment(enrollmentToken string) (*edgeApis.CertCredentials, error) {
token := enrollmentToken
if IsJwt(token) {
jwtParser := jwt.NewParser()
enrollmentClaims := &ziti.EnrollmentClaims{}
_, _, err := jwtParser.ParseUnverified(token, enrollmentClaims)
if err != nil {
return nil, fmt.Errorf("could not parse enrollment JWT: %w", rest_util.WrapErr(err))
}
token = enrollmentClaims.ID
}
privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil {
return nil, err
}
template := &x509.CertificateRequest{}
csr, err := x509.CreateCertificateRequest(rand.Reader, template, privateKey)
if err != nil {
return nil, rest_util.WrapErr(err)
}
csrPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csr})
params := clientEnroll.NewEnrollParams()
params.Method = ToPtr(rest_model.EnrollmentCreateMethodOtt)
params.Token = ToPtr(strfmt.UUID(token))
params.Body = &rest_model.GenericEnroll{
ClientCsr: string(csrPem),
}
resp, err := helper.API.Enroll.Enroll(params, func(operation *runtime.ClientOperation) {
operation.ConsumesMediaTypes = []string{"text/plain"}
})
if err != nil {
return nil, rest_util.WrapErr(err)
}
certs := nfPem.PemStringToCertificates(resp.Payload.Data.Cert)
if len(certs) == 0 {
return nil, fmt.Errorf("no certificates returned from enrollment")
}
return &edgeApis.CertCredentials{
BaseCredentials: edgeApis.BaseCredentials{},
Certs: certs,
Key: privateKey,
}, nil
}
func (helper *ClientHelperClient) CompleteOttCaEnrollment(enrollmentToken string, clientCert []*x509.Certificate, clientKey crypto.PrivateKey) (*edgeApis.CertCredentials, error) {
token := enrollmentToken
if len(clientCert) == 0 {
return nil, fmt.Errorf("no client certificates returned from enrollment")
}
var rawCerts [][]byte
for _, cert := range clientCert {
rawCerts = append(rawCerts, cert.Raw)
}
tlsCerts := []tls.Certificate{
{
Certificate: rawCerts,
PrivateKey: clientKey,
Leaf: clientCert[0],
},
}
// Replace the TLS config with a new one that includes the client certs.
// We must also close idle connections and replace the transport on the
// HttpClient to ensure no previously established TLS connections (which
// lack the client cert) are reused. This avoids a race with the background
// doOnceCacheVersionInfo goroutine which may have opened a connection
// before the client cert was configured.
oldTlsConfig := helper.TlsAwareTransport.GetTlsClientConfig()
newTlsConfig := oldTlsConfig.Clone()
newTlsConfig.Certificates = tlsCerts
helper.TlsAwareTransport.CloseIdleConnections()
newTransport := edgeApis.NewTlsAwareHttpTransport(nil)
newTransport.SetTlsClientConfig(newTlsConfig)
helper.TlsAwareTransport = newTransport
helper.HttpClient.Transport = newTransport
if IsJwt(token) {
jwtParser := jwt.NewParser()
enrollmentClaims := &ziti.EnrollmentClaims{}
_, _, err := jwtParser.ParseUnverified(token, enrollmentClaims)
if err != nil {
return nil, fmt.Errorf("could not parse enrollment JWT: %w", rest_util.WrapErr(err))
}
token = enrollmentClaims.ID
}
params := clientEnroll.NewEnrollOttCaParams()
params.OttEnrollmentRequest = &rest_model.OttEnrollmentRequest{
Token: token,
}
certCreds := &edgeApis.CertCredentials{
BaseCredentials: edgeApis.BaseCredentials{},
Certs: clientCert,
Key: clientKey,
}
helper.Credentials = certCreds
_, err := helper.API.Enroll.EnrollOttCa(params)
if err != nil {
return nil, rest_util.WrapErr(err)
}
return certCreds, nil
}
func (helper *ClientHelperClient) CompleteUpdbEnrollment(enrollmentToken string, username string, password string) (*edgeApis.UpdbCredentials, error) {
token := enrollmentToken
if IsJwt(token) {
jwtParser := jwt.NewParser()
enrollmentClaims := &ziti.EnrollmentClaims{}
_, _, err := jwtParser.ParseUnverified(token, enrollmentClaims)
if err != nil {
return nil, fmt.Errorf("could not parse enrollment JWT: %w", rest_util.WrapErr(err))
}
token = enrollmentClaims.ID
}
params := clientEnroll.NewEnrollUpdbParams()
params.Token = strfmt.UUID(token)
params.UpdbCredentials = clientEnroll.EnrollUpdbBody{
Password: rest_model.Password(password),
Username: rest_model.Username(username),
}
_, err := helper.API.Enroll.EnrollUpdb(params)
if err != nil {
return nil, rest_util.WrapErr(err)
}
return &edgeApis.UpdbCredentials{
BaseCredentials: edgeApis.BaseCredentials{},
Username: username,
Password: password,
}, nil
}
func (helper *ClientHelperClient) ExtendCertsWithAuthenticatorId(authenticatorId string) (*edgeApis.CertCredentials, error) {
request, err := certtools.NewCertRequest(map[string]string{
"C": "US", "O": "NetFoundry-API-Test", "CN": uuid.NewString(),
}, nil)
if err != nil {
return nil, fmt.Errorf("could not create base CSR values: %w", err)
}
newPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, fmt.Errorf("could not generate private key: %w", err)
}
csr, err := x509.CreateCertificateRequest(rand.Reader, request, newPrivateKey)
if err != nil {
return nil, fmt.Errorf("could not create CSR: %w", err)
}
csrPem := string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csr}))
extendParams := &clientCurrentApiSession.ExtendCurrentIdentityAuthenticatorParams{
Extend: &rest_model.IdentityExtendEnrollmentRequest{
ClientCertCsr: &csrPem,
},
ID: authenticatorId,
}
extendResp, err := helper.API.CurrentAPISession.ExtendCurrentIdentityAuthenticator(extendParams, nil)
if err != nil {
return nil, fmt.Errorf("could not extend current identity authenticator: %w", err)
}
caCerts := nfPem.PemStringToCertificates(extendResp.Payload.Data.Ca)
caPool := x509.NewCertPool()
for _, c := range caCerts {
caPool.AddCert(c)
}
newCerts := nfPem.PemStringToCertificates(extendResp.Payload.Data.ClientCert)
newCreds := &edgeApis.CertCredentials{
BaseCredentials: edgeApis.BaseCredentials{
CaPool: caPool,
},
Certs: newCerts,
Key: newPrivateKey,
}
verifyParams := &clientCurrentApiSession.ExtendVerifyCurrentIdentityAuthenticatorParams{
Extend: &rest_model.IdentityExtendValidateEnrollmentRequest{
ClientCert: &extendResp.Payload.Data.ClientCert,
},
ID: authenticatorId,
}
_, err = helper.API.CurrentAPISession.ExtendVerifyCurrentIdentityAuthenticator(verifyParams, nil)
if err != nil {
return nil, fmt.Errorf("could not verify the extension of the authenticator: %w", rest_util.WrapErr(err))
}
return newCreds, nil
}
func (helper *ClientHelperClient) GetCurrentApiSessionDetail() (*rest_model.CurrentAPISessionDetail, error) {
params := &clientCurrentApiSession.GetCurrentAPISessionParams{}
resp, err := helper.API.CurrentAPISession.GetCurrentAPISession(params, nil)
if err != nil {
return nil, fmt.Errorf("could not get current api session detail: %w", rest_util.WrapErr(err))
}
return resp.Payload.Data, nil
}
func (helper *ClientHelperClient) GetTotpMfa() (*rest_model.DetailMfa, error) {
params := &clientCurrentIdentity.DetailMfaParams{}
resp, err := helper.API.CurrentIdentity.DetailMfa(params, nil)
if err != nil {
return nil, fmt.Errorf("could not get totp mfa: %w", err)
}
return resp.Payload.Data, nil
}
func (helper *ClientHelperClient) CreateTotpMfaEnrollment() (*rest_model.DetailMfa, error) {
params := &clientCurrentIdentity.EnrollMfaParams{}
_, err := helper.API.CurrentIdentity.EnrollMfa(params, nil)
if err != nil {
return nil, fmt.Errorf("could not create totp mfa enrollment: %w", err)
}
return helper.GetTotpMfa()
}
func (helper *ClientHelperClient) VerifyTotpMfaEnrollment(code string) (*rest_model.DetailMfa, error) {
params := &clientCurrentIdentity.VerifyMfaParams{
MfaValidation: &rest_model.MfaCode{
Code: ToPtr(code),
},
Context: nil,
HTTPClient: nil,
}
_, err := helper.API.CurrentIdentity.VerifyMfa(params, nil)
if err != nil {
return nil, fmt.Errorf("could not verify totp mfa enrollment: %w", err)
}
return helper.GetTotpMfa()
}
func (helper *ClientHelperClient) EnrollTotpMfa() (*TotpProvider, *rest_model.DetailMfa, error) {
totpMfaEnrollment, err := helper.CreateTotpMfaEnrollment()
if err != nil {
return nil, nil, fmt.Errorf("could not create totp mfa enrollment: %w", err)
}
totpProvider := &TotpProvider{}
err = totpProvider.ApplyProvisioningUrl(totpMfaEnrollment.ProvisioningURL)
if err != nil {
return nil, nil, fmt.Errorf("could not apply provisioning url: %w", err)
}
curCode := totpProvider.Code()
totpMfaEnrollment, err = helper.VerifyTotpMfaEnrollment(curCode)
if err != nil {
return nil, nil, fmt.Errorf("could not verify totp mfa enrollment: %w", err)
}
return totpProvider, totpMfaEnrollment, err
}
func (helper *ClientHelperClient) GetTotpToken(code string) (*rest_model.TotpToken, error) {
params := clientCurrentApiSession.CreateTotpTokenParams{
MfaValidation: &rest_model.MfaCode{
Code: ToPtr(code),
},
}
resp, err := helper.API.CurrentAPISession.CreateTotpToken(&params, nil)
if err != nil {
return nil, fmt.Errorf("could not get totp token: %w", err)
}
return resp.Payload.Data, nil
}
func (helper *ClientHelperClient) QueryCurrentApiSession() (*rest_model.CurrentAPISessionDetail, error) {
params := &clientCurrentApiSession.GetCurrentAPISessionParams{}
resp, err := helper.API.CurrentAPISession.GetCurrentAPISession(params, nil)
if err != nil {
return nil, rest_util.WrapErr(err)
}
return resp.Payload.Data, nil
}
// RawOidcAuthRequest performs a raw OIDC PKCE authentication flow with no TOTP providers,
// returning the resulting tokens, all intermediate HTTP responses, and any error.
func (helper *ClientHelperClient) RawOidcAuthRequest(credentials edgeApis.Credentials) (*oidc.Tokens[*oidc.IDTokenClaims], *edgeApis.OidcAuthResponses, error) {
return helper.RawOidcAuthRequestWithProviders(credentials, nil, nil)
}
// RawOidcAuthRequestWithProviders performs a raw OIDC PKCE authentication flow with optional TOTP
// enrollment and code providers, returning the resulting tokens, all intermediate HTTP responses,
// and any error. Nil providers are ignored; supply only those needed for the scenario under test.
func (helper *ClientHelperClient) RawOidcAuthRequestWithProviders(credentials edgeApis.Credentials, enrollProvider edgeApis.TotpEnrollmentProvider, codeProvider edgeApis.TotpCodeProvider) (*oidc.Tokens[*oidc.IDTokenClaims], *edgeApis.OidcAuthResponses, error) {
httpTransport := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: credentials.GetCaPool(),
Certificates: credentials.TlsCerts(),
},
Proxy: http.ProxyFromEnvironment,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
httpClient := &http.Client{
Transport: httpTransport,
Timeout: time.Second * 10,
}
clientRuntime := edgeApis.NewRuntime(helper.testCtx.ClientApiUrl(), rest_client_api_client.DefaultSchemes, httpClient)
apiClientTransport := &edgeApis.ApiClientTransport{
ClientTransport: clientRuntime,
ApiUrl: helper.testCtx.ClientApiUrl(),
}
config := &edgeApis.EdgeOidcAuthConfig{
ClientTransportPool: &SingularClientTransportPool{ApiClientTransport: apiClientTransport},
Credentials: credentials,
HttpClient: httpClient,
TotpEnrollmentProvider: enrollProvider,
TotpCodeProvider: codeProvider,
RedirectUri: edgeApis.DefaultOidcRedirectUri,
ApiHost: helper.testCtx.ApiHost,
}
return edgeApis.NewEdgeOidcAuthenticator(config).AuthenticateWithResponses()
}
// OidcAuthorize starts an OIDC PKCE authorization flow and returns an OidcAuthorizeResult
// for use when tests need to make raw HTTP calls to the OP login endpoints. The Exchange
// function in the result completes the flow by trading an authorization code for OIDC tokens.
func (helper *ClientHelperClient) OidcAuthorize(credentials edgeApis.Credentials) (*edgeApis.OidcAuthorizeResult, error) {
httpTransport := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: credentials.GetCaPool(),
Certificates: credentials.TlsCerts(),
},
Proxy: http.ProxyFromEnvironment,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
httpClient := &http.Client{
Transport: httpTransport,
Timeout: time.Second * 10,
}
clientRuntime := edgeApis.NewRuntime(helper.testCtx.ClientApiUrl(), rest_client_api_client.DefaultSchemes, httpClient)
apiClientTransport := &edgeApis.ApiClientTransport{
ClientTransport: clientRuntime,
ApiUrl: helper.testCtx.ClientApiUrl(),
}
config := &edgeApis.EdgeOidcAuthConfig{
ClientTransportPool: &SingularClientTransportPool{ApiClientTransport: apiClientTransport},
Credentials: credentials,
HttpClient: httpClient,
RedirectUri: edgeApis.DefaultOidcRedirectUri,
ApiHost: helper.testCtx.ApiHost,
}
return edgeApis.NewEdgeOidcAuthenticator(config).Authorize()
}
func (helper *ClientHelperClient) RawLegacyAuthRequest(credentials edgeApis.Credentials) (*clientAuthentication.AuthenticateOK, error) {
params := clientAuthentication.NewAuthenticateParams()
params.Method = string(credentials.Method())
httpTransport := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: credentials.GetCaPool(),
Certificates: credentials.TlsCerts(),
},
Proxy: http.ProxyFromEnvironment,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
httpClient := &http.Client{
Transport: httpTransport,
CheckRedirect: nil,
Timeout: time.Second * 10,
}
params.HTTPClient = httpClient
return helper.API.Authentication.Authenticate(params, func(operation *runtime.ClientOperation) {
operation.AuthInfo = credentials
})
}
func generateCaSignedClientCert(caCert *x509.Certificate, caSigner crypto.Signer, commonName string) (*x509.Certificate, crypto.Signer, error) {
id, _ := rand.Int(rand.Reader, big.NewInt(100000000000000000))
verificationCert := &x509.Certificate{
SerialNumber: id,
Subject: pkix.Name{
CommonName: commonName,
Organization: []string{"Ziti CLI Generated API Test Cert"},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Minute * 10),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
}
verificationKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
if err != nil {
return nil, nil, fmt.Errorf("could not generate private key for certificate (%v)", err)
}
signedCertBytes, err := x509.CreateCertificate(rand.Reader, verificationCert, caCert, verificationKey.Public(), caSigner)
if err != nil {
return nil, nil, fmt.Errorf("could not sign certificate with CA (%v)", err)
}
verificationCert, _ = x509.ParseCertificate(signedCertBytes)
return verificationCert, verificationKey, nil
}
func IsJwt(token string) bool {
if strings.HasPrefix(token, "eY") {
parts := strings.Split(token, ",")
return len(parts) == 3 && len(parts[0]) > 0 && len(parts[1]) > 0 && len(parts[2]) > 0
}
return false
}
type TotpProvider struct {
Secret string
Issuer string
Name string
ProvisioningUrl string
}
func (t *TotpProvider) FuncProvider() func(chan string) {
return func(ch chan string) {
ch <- t.Code()
}
}
func (t *TotpProvider) ApplyProvisioningUrl(provisioningUrl string) error {
parsedUrl, err := url.Parse(provisioningUrl)
if err != nil {
return fmt.Errorf("could not parse provisioning url: %w", err)
}
if parsedUrl.Scheme != "otpauth" {
return fmt.Errorf("provisioning url must be an otpauth url")
}
queryParams, err := url.ParseQuery(parsedUrl.RawQuery)
if err != nil {
return fmt.Errorf("could not parse query params from provisioning url: %w", err)
}
secrets, ok := queryParams["secret"]
if !ok {
return errors.New("could not find secret in provisioning url")
}
if len(secrets) != 1 {
return fmt.Errorf("expected 1 secret in provisioning url, got %d", len(secrets))
}
t.Secret = secrets[0]
t.ProvisioningUrl = provisioningUrl
return nil
}
func (t *TotpProvider) Code() string {
if t.ProvisioningUrl == "" {
panic(errors.New("no provisioning url set"))
}
if t.Secret == "" {
panic(errors.New("secret not set"))
}
now := time.Now().UTC().Unix() / 30
code := dgoogauth.ComputeCode(t.Secret, now)
//pad leading 0s to 6 characters
return fmt.Sprintf("%06d", code)
}
// SingularClientTransportPool is a transport pool that only allows a single transport to be used, implements ClientTransportPool
type SingularClientTransportPool struct {
ApiClientTransport *edgeApis.ApiClientTransport
}
func (s *SingularClientTransportPool) Submit(operation *runtime.ClientOperation) (any, error) {
return s.ApiClientTransport.Submit(operation)
}
func (s *SingularClientTransportPool) Add(apiUrl *url.URL, transport runtime.ClientTransport) {
s.ApiClientTransport = &edgeApis.ApiClientTransport{
ClientTransport: transport,
ApiUrl: apiUrl,
}
}
func (s *SingularClientTransportPool) Remove(apiUrl *url.URL) {
//do nothing
}
func (s *SingularClientTransportPool) GetActiveTransport() *edgeApis.ApiClientTransport {
return s.ApiClientTransport
}
func (s *SingularClientTransportPool) SetActiveTransport(transport *edgeApis.ApiClientTransport) {
s.ApiClientTransport = transport
}
func (s *SingularClientTransportPool) GetApiUrls() []*url.URL {
return []*url.URL{s.ApiClientTransport.ApiUrl}
}
func (s *SingularClientTransportPool) IterateTransportsRandomly() chan<- *edgeApis.ApiClientTransport {
ch := make(chan *edgeApis.ApiClientTransport, 1)
ch <- s.ApiClientTransport
return ch
}
func (s *SingularClientTransportPool) TryTransportsForOp(operation *runtime.ClientOperation) (any, error) {
return s.ApiClientTransport.Submit(operation)
}
func (s *SingularClientTransportPool) TryTransportForF(cb func(*edgeApis.ApiClientTransport) (any, error)) (any, error) {
return cb(s.ApiClientTransport)
}
// OidcAccessToken authenticates via OIDC with the given credentials and returns
// the raw access token string plus its parsed claims. The caller must set CaPool
// on the credentials before calling if the controller uses a self-signed certificate.
func (helper *ClientHelperClient) OidcAccessToken(credentials edgeApis.Credentials) (string, *common.AccessClaims, error) {
tokens, _, err := helper.RawOidcAuthRequest(credentials)
if err != nil {
return "", nil, err
}
if tokens.AccessToken == "" {
return "", nil, errors.New("empty access token from OIDC auth")
}
return parseAccessToken(tokens.AccessToken)
}
// parseAccessToken parses an access token string into AccessClaims without
// verifying the signature.
func parseAccessToken(tokenStr string) (string, *common.AccessClaims, error) {
claims := &common.AccessClaims{}
parser := jwt.NewParser()
_, _, err := parser.ParseUnverified(tokenStr, claims)
if err != nil {
return "", nil, err
}
return tokenStr, claims, nil
}