From d2dd7525d6ede7322abce8169178196cf52ab307 Mon Sep 17 00:00:00 2001 From: stackman27 Date: Thu, 26 Feb 2026 12:13:42 -0800 Subject: [PATCH 1/5] Add additional providers --- chain/canton/provider/authentication/oauth.go | 170 ++++++++++++++++++ engine/cld/chains/chains.go | 56 +++++- engine/cld/config/env/config.go | 24 ++- 3 files changed, 240 insertions(+), 10 deletions(-) create mode 100644 chain/canton/provider/authentication/oauth.go diff --git a/chain/canton/provider/authentication/oauth.go b/chain/canton/provider/authentication/oauth.go new file mode 100644 index 000000000..b7b5a5818 --- /dev/null +++ b/chain/canton/provider/authentication/oauth.go @@ -0,0 +1,170 @@ +package authentication + +import ( + "context" + "crypto/rand" + "crypto/tls" + "encoding/base64" + "fmt" + "net" + "net/http" + "os/exec" + "runtime" + "time" + + "golang.org/x/oauth2" + "golang.org/x/oauth2/clientcredentials" + "google.golang.org/grpc/credentials" +) + +var _ Provider = (*OIDCProvider)(nil) + +// OIDCProvider implements Provider using OAuth2/OIDC token flows (client credentials or authorization code). +type OIDCProvider struct { + tokenSource oauth2.TokenSource +} + +// NewClientCredentialsProvider creates a provider that fetches tokens using the OAuth2 client credentials flow. +// Use in CI where ClientID, ClientSecret and AuthURL are available; tokens are obtained automatically. +func NewClientCredentialsProvider(ctx context.Context, authURL, clientID, clientSecret string) (*OIDCProvider, error) { + tokenURL := fmt.Sprintf("%s/v1/token", authURL) + + oauthCfg := &clientcredentials.Config{ + ClientID: clientID, + ClientSecret: clientSecret, + TokenURL: tokenURL, + Scopes: []string{"daml_ledger_api"}, + } + + tokenSource := oauthCfg.TokenSource(ctx) + + return &OIDCProvider{ + tokenSource: tokenSource, + }, nil +} + +// NewAuthorizationCodeProvider creates a provider that uses the OAuth2 authorization code flow with PKCE. +// It starts a local callback server, opens the browser to the auth URL, and exchanges the code for a token. +// Use locally to skip canton-login; only ClientID and AuthURL are required. +func NewAuthorizationCodeProvider(ctx context.Context, authURL, clientID string) (*OIDCProvider, error) { + verifier := oauth2.GenerateVerifier() + + port := 8400 + authEndpoint := fmt.Sprintf("%s/v1/authorize", authURL) + tokenEndpoint := fmt.Sprintf("%s/v1/token", authURL) + redirectURL := fmt.Sprintf("http://localhost:%d", port) + + oauthCfg := &oauth2.Config{ + ClientID: clientID, + RedirectURL: redirectURL + "/callback", + Scopes: []string{"openid", "daml_ledger_api"}, + Endpoint: oauth2.Endpoint{AuthURL: authEndpoint, TokenURL: tokenEndpoint}, + } + + state := generateState() + authCodeURL := oauthCfg.AuthCodeURL(state, oauth2.S256ChallengeOption(verifier)) + + callbackChan := make(chan *oauth2.Token) + + serveMux := http.NewServeMux() + serveMux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + code := q.Get("code") + receivedState := q.Get("state") + + if receivedState != state { + http.Error(w, "Invalid state parameter", http.StatusBadRequest) + return + } + + token, err := oauthCfg.Exchange(ctx, code, oauth2.VerifierOption(verifier)) + if err != nil { + http.Error(w, "Token exchange failed: "+err.Error(), http.StatusInternalServerError) + return + } + + callbackChan <- token + + html := ` + +Authentication Complete + +

Authentication complete!

+

You can safely close this window.

+ + +` + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(html)) + }) + + server := http.Server{ + Addr: fmt.Sprintf(":%d", port), + Handler: serveMux, + ReadHeaderTimeout: 5 * time.Second, + } + + listener, err := new(net.ListenConfig).Listen(ctx, "tcp", server.Addr) + if err != nil { + return nil, fmt.Errorf("listening on port %d: %w", port, err) + } + + serverErr := make(chan error, 1) + go func() { + serverErr <- server.Serve(listener) + }() + + openBrowser(authCodeURL) + + select { + case err := <-serverErr: + _ = server.Shutdown(ctx) + return nil, fmt.Errorf("callback server error: %w", err) + case token := <-callbackChan: + tokenSource := oauthCfg.TokenSource(ctx, token) + _ = server.Shutdown(ctx) + return &OIDCProvider{ + tokenSource: tokenSource, + }, nil + case <-ctx.Done(): + _ = server.Shutdown(ctx) + return nil, ctx.Err() + } +} + +func (p *OIDCProvider) TokenSource() oauth2.TokenSource { + return p.tokenSource +} + +func (p *OIDCProvider) TransportCredentials() credentials.TransportCredentials { + return credentials.NewTLS(&tls.Config{ + MinVersion: tls.VersionTLS12, + }) +} + +func (p *OIDCProvider) PerRPCCredentials() credentials.PerRPCCredentials { + return secureTokenSource{ + TokenSource: p.tokenSource, + } +} + +func generateState() string { + b := make([]byte, 16) + if _, err := rand.Read(b); err != nil { + panic(err) + } + return base64.RawURLEncoding.EncodeToString(b) +} + +// openBrowser opens the default browser to url on supported platforms; otherwise it is a no-op. +func openBrowser(url string) { + switch runtime.GOOS { + case "darwin": + _ = exec.Command("open", url).Start() + case "linux": + _ = exec.Command("xdg-open", url).Start() + case "windows": + _ = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() + } +} diff --git a/engine/cld/chains/chains.go b/engine/cld/chains/chains.go index c4e820ef1..910da0a59 100644 --- a/engine/cld/chains/chains.go +++ b/engine/cld/chains/chains.go @@ -217,10 +217,10 @@ func newChainLoaders( lggr.Info("Skipping Ton chains, no private key found in secrets") } - if cfg.Canton.JWTToken != "" { + if cantonAuthConfigured(cfg.Canton) { loaders[chainsel.FamilyCanton] = newChainLoaderCanton(networks, cfg) } else { - lggr.Info("Skipping Canton chains, no JWT token found in secrets") + lggr.Info("Skipping Canton chains, no Canton auth configured (set auth_type and jwt_token, or auth_url+client_id for OAuth)") } return loaders @@ -747,13 +747,11 @@ func (l *chainLoaderCanton) Load(ctx context.Context, selector uint64) (fchain.B return nil, fmt.Errorf("canton network %d: no participants found in metadata", selector) } - if l.cfg.Canton.JWTToken == "" { - return nil, fmt.Errorf("canton network %d: JWT token is required", selector) + authProvider, err := l.cantonAuthProvider(ctx, selector) + if err != nil { + return nil, err } - // Use TLS-enforcing auth provider for Canton participant endpoints. - authProvider := cantonauth.NewStaticProvider(l.cfg.Canton.JWTToken) - participants := make([]cantonprov.ParticipantConfig, len(md.Participants)) for i, participantMD := range md.Participants { participants[i] = cantonprov.ParticipantConfig{ @@ -779,6 +777,50 @@ func (l *chainLoaderCanton) Load(ctx context.Context, selector uint64) (fchain.B return c, nil } +// cantonAuthConfigured returns true if Canton auth is configured for at least one scheme (static, client_credentials, or authorization_code). +func cantonAuthConfigured(c cfgenv.CantonConfig) bool { + switch c.AuthType { + case cfgenv.CantonAuthTypeClientCredentials: + return c.AuthURL != "" && c.ClientID != "" && c.ClientSecret != "" + case cfgenv.CantonAuthTypeAuthorizationCode: + return c.AuthURL != "" && c.ClientID != "" + default: + // static or empty (backward compat: jwt_token alone enables Canton) + return c.JWTToken != "" + } +} + +// cantonAuthProvider builds a Canton auth Provider from config. Caller must ensure cantonAuthConfigured(cfg.Canton) is true. +func (l *chainLoaderCanton) cantonAuthProvider(ctx context.Context, selector uint64) (cantonauth.Provider, error) { + c := l.cfg.Canton + switch c.AuthType { + case cfgenv.CantonAuthTypeClientCredentials: + if c.AuthURL == "" || c.ClientID == "" || c.ClientSecret == "" { + return nil, fmt.Errorf("canton network %d: client_credentials requires auth_url, client_id, and client_secret", selector) + } + oidc, err := cantonauth.NewClientCredentialsProvider(ctx, c.AuthURL, c.ClientID, c.ClientSecret) + if err != nil { + return nil, fmt.Errorf("canton network %d: client_credentials auth: %w", selector, err) + } + return oidc, nil + case cfgenv.CantonAuthTypeAuthorizationCode: + if c.AuthURL == "" || c.ClientID == "" { + return nil, fmt.Errorf("canton network %d: authorization_code requires auth_url and client_id", selector) + } + oidc, err := cantonauth.NewAuthorizationCodeProvider(ctx, c.AuthURL, c.ClientID) + if err != nil { + return nil, fmt.Errorf("canton network %d: authorization_code auth: %w", selector, err) + } + return oidc, nil + default: + // static or empty + if c.JWTToken == "" { + return nil, fmt.Errorf("canton network %d: JWT token is required for static auth", selector) + } + return cantonauth.NewStaticProvider(c.JWTToken), nil + } +} + // useKMS returns true if both KeyID and KeyRegion are set in the provided KMS config. func useKMS(kmsCfg cfgenv.KMSConfig) bool { return kmsCfg.KeyID != "" && kmsCfg.KeyRegion != "" diff --git a/engine/cld/config/env/config.go b/engine/cld/config/env/config.go index fb5d66b00..a6bd4bf82 100644 --- a/engine/cld/config/env/config.go +++ b/engine/cld/config/env/config.go @@ -82,14 +82,28 @@ type TronConfig struct { DeployerKey string `mapstructure:"deployer_key" yaml:"deployer_key"` // Secret: The private key of the deployer account. } +// CantonAuthType is the authentication scheme for Canton participant APIs. +const ( + CantonAuthTypeStatic = "static" // Pre-obtained JWT (e.g. from canton-login). + CantonAuthTypeClientCredentials = "client_credentials" // CI: fetch token with client_id + client_secret + auth_url. + CantonAuthTypeAuthorizationCode = "authorization_code" // Local: browser flow with client_id + auth_url. +) + // CantonConfig is the configuration for the Canton Chains. // // WARNING: This data type contains sensitive fields and should not be logged or set in file // configuration. type CantonConfig struct { - // JWT token for authenticating with Canton participants. This token will be used for all participants. - // For more complex scenarios with different tokens per participant, use the network metadata. - JWTToken string `mapstructure:"jwt_token" yaml:"jwt_token"` // Secret: JWT token for Canton participant authentication. + // AuthType selects how to obtain the token: "static" (jwt_token), "client_credentials" (CI), or "authorization_code" (local browser). + AuthType string `mapstructure:"auth_type" yaml:"auth_type"` + // JWT token for static auth. Used when auth_type is "static". + JWTToken string `mapstructure:"jwt_token" yaml:"jwt_token"` // Secret + // AuthURL is the OIDC base URL (e.g. https://auth.example.com). Token URL is AuthURL/v1/token, authorize is AuthURL/v1/authorize. + AuthURL string `mapstructure:"auth_url" yaml:"auth_url"` + // ClientID is the OAuth2 client ID. Used for client_credentials and authorization_code. + ClientID string `mapstructure:"client_id" yaml:"client_id"` // Secret + // ClientSecret is the OAuth2 client secret. Required only for client_credentials (CI). + ClientSecret string `mapstructure:"client_secret" yaml:"client_secret"` // Secret } // JobDistributorConfig is the configuration for connecting and authenticating to the Job @@ -247,7 +261,11 @@ var ( "onchain.stellar.deployer_key": {"ONCHAIN_STELLAR_DEPLOYER_KEY"}, "onchain.ton.deployer_key": {"ONCHAIN_TON_DEPLOYER_KEY", "TON_DEPLOYER_KEY"}, "onchain.ton.wallet_version": {"ONCHAIN_TON_WALLET_VERSION", "TON_WALLET_VERSION"}, + "onchain.canton.auth_type": {"ONCHAIN_CANTON_AUTH_TYPE"}, "onchain.canton.jwt_token": {"ONCHAIN_CANTON_JWT_TOKEN"}, + "onchain.canton.auth_url": {"ONCHAIN_CANTON_AUTH_URL"}, + "onchain.canton.client_id": {"ONCHAIN_CANTON_CLIENT_ID"}, + "onchain.canton.client_secret": {"ONCHAIN_CANTON_CLIENT_SECRET"}, "offchain.job_distributor.auth.cognito_app_client_id": {"OFFCHAIN_JD_AUTH_COGNITO_APP_CLIENT_ID", "JD_AUTH_COGNITO_APP_CLIENT_ID"}, "offchain.job_distributor.auth.cognito_app_client_secret": {"OFFCHAIN_JD_AUTH_COGNITO_APP_CLIENT_SECRET", "JD_AUTH_COGNITO_APP_CLIENT_SECRET"}, "offchain.job_distributor.auth.aws_region": {"OFFCHAIN_JD_AUTH_AWS_REGION", "JD_AUTH_AWS_REGION"}, From f2eb21d4a990075b249f86354fd42f5be845e1aa Mon Sep 17 00:00:00 2001 From: stackman27 Date: Thu, 26 Feb 2026 13:04:44 -0800 Subject: [PATCH 2/5] fix ci --- chain/canton/provider/authentication/oauth.go | 24 +++++++++++-------- engine/cld/chains/chains.go | 1 + engine/cld/config/env/config_test.go | 12 ++++++++-- engine/cld/config/env/testdata/config.yml | 4 ++++ .../testdata/config_with_optional_values.yml | 4 ++++ 5 files changed, 33 insertions(+), 12 deletions(-) diff --git a/chain/canton/provider/authentication/oauth.go b/chain/canton/provider/authentication/oauth.go index b7b5a5818..d8e0fc1ba 100644 --- a/chain/canton/provider/authentication/oauth.go +++ b/chain/canton/provider/authentication/oauth.go @@ -10,6 +10,7 @@ import ( "net/http" "os/exec" "runtime" + "strconv" "time" "golang.org/x/oauth2" @@ -27,7 +28,7 @@ type OIDCProvider struct { // NewClientCredentialsProvider creates a provider that fetches tokens using the OAuth2 client credentials flow. // Use in CI where ClientID, ClientSecret and AuthURL are available; tokens are obtained automatically. func NewClientCredentialsProvider(ctx context.Context, authURL, clientID, clientSecret string) (*OIDCProvider, error) { - tokenURL := fmt.Sprintf("%s/v1/token", authURL) + tokenURL := authURL + "/v1/token" oauthCfg := &clientcredentials.Config{ ClientID: clientID, @@ -50,9 +51,9 @@ func NewAuthorizationCodeProvider(ctx context.Context, authURL, clientID string) verifier := oauth2.GenerateVerifier() port := 8400 - authEndpoint := fmt.Sprintf("%s/v1/authorize", authURL) - tokenEndpoint := fmt.Sprintf("%s/v1/token", authURL) - redirectURL := fmt.Sprintf("http://localhost:%d", port) + authEndpoint := authURL + "/v1/authorize" + tokenEndpoint := authURL + "/v1/token" + redirectURL := "http://localhost:" + strconv.Itoa(port) oauthCfg := &oauth2.Config{ ClientID: clientID, @@ -100,7 +101,7 @@ func NewAuthorizationCodeProvider(ctx context.Context, authURL, clientID string) }) server := http.Server{ - Addr: fmt.Sprintf(":%d", port), + Addr: ":" + strconv.Itoa(port), Handler: serveMux, ReadHeaderTimeout: 5 * time.Second, } @@ -115,15 +116,17 @@ func NewAuthorizationCodeProvider(ctx context.Context, authURL, clientID string) serverErr <- server.Serve(listener) }() - openBrowser(authCodeURL) + openBrowser(ctx, authCodeURL) select { case err := <-serverErr: _ = server.Shutdown(ctx) + return nil, fmt.Errorf("callback server error: %w", err) case token := <-callbackChan: tokenSource := oauthCfg.TokenSource(ctx, token) _ = server.Shutdown(ctx) + return &OIDCProvider{ tokenSource: tokenSource, }, nil @@ -144,6 +147,7 @@ func (p *OIDCProvider) TransportCredentials() credentials.TransportCredentials { } func (p *OIDCProvider) PerRPCCredentials() credentials.PerRPCCredentials { + return secureTokenSource{ TokenSource: p.tokenSource, } @@ -158,13 +162,13 @@ func generateState() string { } // openBrowser opens the default browser to url on supported platforms; otherwise it is a no-op. -func openBrowser(url string) { +func openBrowser(ctx context.Context, url string) { switch runtime.GOOS { case "darwin": - _ = exec.Command("open", url).Start() + _ = exec.CommandContext(ctx, "open", url).Start() case "linux": - _ = exec.Command("xdg-open", url).Start() + _ = exec.CommandContext(ctx, "xdg-open", url).Start() case "windows": - _ = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() + _ = exec.CommandContext(ctx, "rundll32", "url.dll,FileProtocolHandler", url).Start() } } diff --git a/engine/cld/chains/chains.go b/engine/cld/chains/chains.go index 910da0a59..58795e9f3 100644 --- a/engine/cld/chains/chains.go +++ b/engine/cld/chains/chains.go @@ -802,6 +802,7 @@ func (l *chainLoaderCanton) cantonAuthProvider(ctx context.Context, selector uin if err != nil { return nil, fmt.Errorf("canton network %d: client_credentials auth: %w", selector, err) } + return oidc, nil case cfgenv.CantonAuthTypeAuthorizationCode: if c.AuthURL == "" || c.ClientID == "" { diff --git a/engine/cld/config/env/config_test.go b/engine/cld/config/env/config_test.go index 0d382cdc3..b4bf6d206 100644 --- a/engine/cld/config/env/config_test.go +++ b/engine/cld/config/env/config_test.go @@ -45,7 +45,11 @@ var ( DeployerKey: "0x567", }, Canton: CantonConfig{ - JWTToken: "", + AuthType: "", + JWTToken: "", + AuthURL: "", + ClientID: "", + ClientSecret: "", }, }, Offchain: OffchainConfig{ @@ -166,7 +170,11 @@ var ( WalletVersion: "V5R1", }, Canton: CantonConfig{ - JWTToken: "", + AuthType: "", + JWTToken: "", + AuthURL: "", + ClientID: "", + ClientSecret: "", }, }, Offchain: OffchainConfig{ diff --git a/engine/cld/config/env/testdata/config.yml b/engine/cld/config/env/testdata/config.yml index 6e4da91ba..28bba081f 100644 --- a/engine/cld/config/env/testdata/config.yml +++ b/engine/cld/config/env/testdata/config.yml @@ -25,7 +25,11 @@ onchain: stellar: deployer_key: "0x567" canton: + auth_type: "" jwt_token: "" + auth_url: "" + client_id: "" + client_secret: "" offchain: job_distributor: endpoints: diff --git a/engine/cld/config/env/testdata/config_with_optional_values.yml b/engine/cld/config/env/testdata/config_with_optional_values.yml index 00e87f011..2cd84dd8a 100644 --- a/engine/cld/config/env/testdata/config_with_optional_values.yml +++ b/engine/cld/config/env/testdata/config_with_optional_values.yml @@ -20,7 +20,11 @@ onchain: stellar: deployer_key: "0x567" canton: + auth_type: "" jwt_token: "" + auth_url: "" + client_id: "" + client_secret: "" offchain: job_distributor: endpoints: From 86bf16830c98515e7d5a0950e625b374e1796ba8 Mon Sep 17 00:00:00 2001 From: stackman27 Date: Thu, 26 Feb 2026 13:06:03 -0800 Subject: [PATCH 3/5] lint fix --- chain/canton/provider/authentication/oauth.go | 1 + engine/cld/config/env/config.go | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/chain/canton/provider/authentication/oauth.go b/chain/canton/provider/authentication/oauth.go index d8e0fc1ba..11d2953cc 100644 --- a/chain/canton/provider/authentication/oauth.go +++ b/chain/canton/provider/authentication/oauth.go @@ -132,6 +132,7 @@ func NewAuthorizationCodeProvider(ctx context.Context, authURL, clientID string) }, nil case <-ctx.Done(): _ = server.Shutdown(ctx) + return nil, ctx.Err() } } diff --git a/engine/cld/config/env/config.go b/engine/cld/config/env/config.go index a6bd4bf82..9a650ea06 100644 --- a/engine/cld/config/env/config.go +++ b/engine/cld/config/env/config.go @@ -84,9 +84,9 @@ type TronConfig struct { // CantonAuthType is the authentication scheme for Canton participant APIs. const ( - CantonAuthTypeStatic = "static" // Pre-obtained JWT (e.g. from canton-login). - CantonAuthTypeClientCredentials = "client_credentials" // CI: fetch token with client_id + client_secret + auth_url. - CantonAuthTypeAuthorizationCode = "authorization_code" // Local: browser flow with client_id + auth_url. + CantonAuthTypeStatic = "static" // Pre-obtained JWT (e.g. from canton-login). + CantonAuthTypeClientCredentials = "client_credentials" // CI: fetch token with client_id + client_secret + auth_url. + CantonAuthTypeAuthorizationCode = "authorization_code" // Local: browser flow with client_id + auth_url. ) // CantonConfig is the configuration for the Canton Chains. @@ -264,8 +264,8 @@ var ( "onchain.canton.auth_type": {"ONCHAIN_CANTON_AUTH_TYPE"}, "onchain.canton.jwt_token": {"ONCHAIN_CANTON_JWT_TOKEN"}, "onchain.canton.auth_url": {"ONCHAIN_CANTON_AUTH_URL"}, - "onchain.canton.client_id": {"ONCHAIN_CANTON_CLIENT_ID"}, - "onchain.canton.client_secret": {"ONCHAIN_CANTON_CLIENT_SECRET"}, + "onchain.canton.client_id": {"ONCHAIN_CANTON_CLIENT_ID"}, + "onchain.canton.client_secret": {"ONCHAIN_CANTON_CLIENT_SECRET"}, "offchain.job_distributor.auth.cognito_app_client_id": {"OFFCHAIN_JD_AUTH_COGNITO_APP_CLIENT_ID", "JD_AUTH_COGNITO_APP_CLIENT_ID"}, "offchain.job_distributor.auth.cognito_app_client_secret": {"OFFCHAIN_JD_AUTH_COGNITO_APP_CLIENT_SECRET", "JD_AUTH_COGNITO_APP_CLIENT_SECRET"}, "offchain.job_distributor.auth.aws_region": {"OFFCHAIN_JD_AUTH_AWS_REGION", "JD_AUTH_AWS_REGION"}, From 2a2621c7453c8aad407e71f3668c6f48a930f9c1 Mon Sep 17 00:00:00 2001 From: stackman27 Date: Thu, 26 Feb 2026 14:01:58 -0800 Subject: [PATCH 4/5] fix lint --- chain/canton/provider/authentication/oauth.go | 2 +- engine/cld/chains/chains.go | 2 ++ engine/cld/config/env/config_test.go | 16 ++++++++-------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/chain/canton/provider/authentication/oauth.go b/chain/canton/provider/authentication/oauth.go index 11d2953cc..4f46d47b4 100644 --- a/chain/canton/provider/authentication/oauth.go +++ b/chain/canton/provider/authentication/oauth.go @@ -148,7 +148,6 @@ func (p *OIDCProvider) TransportCredentials() credentials.TransportCredentials { } func (p *OIDCProvider) PerRPCCredentials() credentials.PerRPCCredentials { - return secureTokenSource{ TokenSource: p.tokenSource, } @@ -159,6 +158,7 @@ func generateState() string { if _, err := rand.Read(b); err != nil { panic(err) } + return base64.RawURLEncoding.EncodeToString(b) } diff --git a/engine/cld/chains/chains.go b/engine/cld/chains/chains.go index 58795e9f3..75f445b6b 100644 --- a/engine/cld/chains/chains.go +++ b/engine/cld/chains/chains.go @@ -812,12 +812,14 @@ func (l *chainLoaderCanton) cantonAuthProvider(ctx context.Context, selector uin if err != nil { return nil, fmt.Errorf("canton network %d: authorization_code auth: %w", selector, err) } + return oidc, nil default: // static or empty if c.JWTToken == "" { return nil, fmt.Errorf("canton network %d: JWT token is required for static auth", selector) } + return cantonauth.NewStaticProvider(c.JWTToken), nil } } diff --git a/engine/cld/config/env/config_test.go b/engine/cld/config/env/config_test.go index b4bf6d206..46a115904 100644 --- a/engine/cld/config/env/config_test.go +++ b/engine/cld/config/env/config_test.go @@ -45,10 +45,10 @@ var ( DeployerKey: "0x567", }, Canton: CantonConfig{ - AuthType: "", - JWTToken: "", - AuthURL: "", - ClientID: "", + AuthType: "", + JWTToken: "", + AuthURL: "", + ClientID: "", ClientSecret: "", }, }, @@ -170,10 +170,10 @@ var ( WalletVersion: "V5R1", }, Canton: CantonConfig{ - AuthType: "", - JWTToken: "", - AuthURL: "", - ClientID: "", + AuthType: "", + JWTToken: "", + AuthURL: "", + ClientID: "", ClientSecret: "", }, }, From d724bf9473748d5959dca2c8dd912283fd35f12e Mon Sep 17 00:00:00 2001 From: stackman27 Date: Thu, 26 Feb 2026 17:05:52 -0800 Subject: [PATCH 5/5] add additional providers --- .changeset/canton-chain-support.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/canton-chain-support.md diff --git a/.changeset/canton-chain-support.md b/.changeset/canton-chain-support.md new file mode 100644 index 000000000..7497ab4fc --- /dev/null +++ b/.changeset/canton-chain-support.md @@ -0,0 +1,5 @@ +--- +"chainlink-deployments-framework": minor +--- + +Add Canton as a supported chain: config (static, client_credentials, authorization_code auth), chain loader in CLD engine, and OAuth providers for CI and local use.