Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 38 additions & 15 deletions ccip/devenv/cldf.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,19 @@ func NewCLDF(ctx context.Context, b *blockchain.Input) (cldf_chain.BlockChain, u
}

for i, config := range b.Out.NetworkSpecificData.CantonData.ExternalEndpoints.Participants {
authProvider := authentication.NewInsecureStaticProvider(config.JWT)
// Get Primary Party for user
ledgerApiConn, err := grpc.NewClient(
config.GRPCLedgerAPIURL,
grpc.WithTransportCredentials(authProvider.TransportCredentials()),
grpc.WithPerRPCCredentials(authProvider.PerRPCCredentials()),
)
authCfg, err := buildParticipantAuthConfig(config)
if err != nil {
return nil, 0, fmt.Errorf("failed to create gRPC connection to Ledger API for Canton participant %d: %w", i+1, err)
return nil, 0, fmt.Errorf("participant %d auth config: %w", i+1, err)
}
userResp, err := adminv2.NewUserManagementServiceClient(ledgerApiConn).GetUser(context.Background(), &adminv2.GetUserRequest{UserId: config.UserID})
authProvider, err := participantAuthProvider(ctx, authCfg)
if err != nil {
return nil, 0, fmt.Errorf("failed to get user info for user %s for Canton participant %d: %w", config.UserID, i+1, err)
return nil, 0, fmt.Errorf("participant %d auth provider: %w", i+1, err)
}
party := userResp.GetUser().GetPrimaryParty()
if party == "" {
return nil, 0, fmt.Errorf("no primary party found for user %s for Canton participant %d", config.UserID, i+1)

party, err := primaryPartyFromLedgerAPI(ctx, i+1, config.GRPCLedgerAPIURL, config.UserID, authProvider)
if err != nil {
return nil, 0, err
}
_ = ledgerApiConn.Close()

providerConfig.Participants[i] = cldf_canton_provider.ParticipantConfig{
Endpoints: cldf_canton_provider.Endpoints{
Expand All @@ -63,10 +57,39 @@ func NewCLDF(ctx context.Context, b *blockchain.Input) (cldf_chain.BlockChain, u
AuthProvider: authProvider,
}
}
p, err := cldf_canton_provider.NewRPCChainProvider(d.ChainSelector, providerConfig).Initialize(context.TODO())
p, err := cldf_canton_provider.NewRPCChainProvider(d.ChainSelector, providerConfig).Initialize(ctx)
if err != nil {
return nil, 0, err
}

return p, d.ChainSelector, nil
}

func primaryPartyFromLedgerAPI(
ctx context.Context,
participantIndex int,
grpcLedgerAPIURL string,
userID string,
authProvider authentication.Provider,
) (string, error) {
conn, err := grpc.NewClient(
grpcLedgerAPIURL,
grpc.WithTransportCredentials(authProvider.TransportCredentials()),
grpc.WithPerRPCCredentials(authProvider.PerRPCCredentials()),
)
if err != nil {
return "", fmt.Errorf("failed to create gRPC connection to Ledger API for Canton participant %d: %w", participantIndex, err)
}
defer conn.Close()

userResp, err := adminv2.NewUserManagementServiceClient(conn).GetUser(ctx, &adminv2.GetUserRequest{UserId: userID})
if err != nil {
return "", fmt.Errorf("failed to get user info for user %s for Canton participant %d: %w", userID, participantIndex, err)
}
party := userResp.GetUser().GetPrimaryParty()
if party == "" {
return "", fmt.Errorf("no primary party found for user %s for Canton participant %d", userID, participantIndex)
}

return party, nil
}
30 changes: 19 additions & 11 deletions ccip/devenv/modifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@ import (
ledgerv2admin "github.com/digital-asset/dazl-client/v8/go/api/com/daml/ledger/api/v2/admin"
"github.com/testcontainers/testcontainers-go"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"

chainsel "github.com/smartcontractkit/chain-selectors"
"github.com/smartcontractkit/chainlink-ccv/build/devenv/services/committeeverifier"
"github.com/smartcontractkit/chainlink-ccv/build/devenv/util"
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain"
"github.com/smartcontractkit/go-daml/pkg/auth"

"github.com/smartcontractkit/chainlink-canton/ccip"
"github.com/smartcontractkit/chainlink-canton/ccip/sourcereader"
Expand Down Expand Up @@ -113,22 +111,32 @@ func hydrateAndMarshalCantonConfig(in *committeeverifier.Input, outputs []*block

// Get the full party ID (name + hex id) from the canton participant.
// TODO: how to support multiple participants?
grpcURL := output.NetworkSpecificData.CantonData.ExternalEndpoints.Participants[0].GRPCLedgerAPIURL
jwt := output.NetworkSpecificData.CantonData.ExternalEndpoints.Participants[0].JWT
if grpcURL == "" || jwt == "" {
return nil, fmt.Errorf("GRPC ledger API URL or JWT is not set for chain %s, please update the config appropriately if you're using canton", strSelector)
participant := output.NetworkSpecificData.CantonData.ExternalEndpoints.Participants[0]
grpcURL := participant.GRPCLedgerAPIURL
if grpcURL == "" {
return nil, fmt.Errorf("GRPC ledger API URL is not set for chain %s, please update the config appropriately if you're using canton", strSelector)
}

authCfg, err := buildParticipantAuthConfig(participant)
if err != nil {
return nil, fmt.Errorf("participant auth config for chain %s: %w", strSelector, err)
}
authProvider, err := participantAuthProvider(context.Background(), authCfg)
if err != nil {
return nil, fmt.Errorf("participant auth provider for chain %s: %w", strSelector, err)
}

cantonConfigs.BlockchainInfos[strSelector] = ccip.BlockchainInfo{
GRPCLedgerAPIURL: output.NetworkSpecificData.CantonData.InternalEndpoints.Participants[0].GRPCLedgerAPIURL,
Auth: commonconfig.AuthConfig{
Type: commonconfig.AuthTypeInsecureStatic,
JWT: jwt,
},
Auth: authCfg,
}

// find the party that starts with the prefix that is listed in the canton config.
conn, err := grpc.NewClient(grpcURL, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithPerRPCCredentials(auth.NewBearerToken(jwt)))
conn, err := grpc.NewClient(
grpcURL,
grpc.WithTransportCredentials(authProvider.TransportCredentials()),
grpc.WithPerRPCCredentials(authProvider.PerRPCCredentials()),
)
if err != nil {
return nil, fmt.Errorf("failed to create gRPC connection: %w", err)
}
Expand Down
143 changes: 143 additions & 0 deletions ccip/devenv/participant_auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package devenv

import (
"context"
"fmt"
"os"
"strings"

"github.com/smartcontractkit/chainlink-deployments-framework/chain/canton/provider/authentication"
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain"

"github.com/smartcontractkit/chainlink-canton/commonconfig"
)

const (
envCantonAuthType = "CANTON_AUTH_TYPE"
envCantonAuthURL = "CANTON_AUTH_URL"
envCantonOAuthClientID = "CANTON_OAUTH_CLIENT_ID"
envCantonOAuthClientSecret = "CANTON_OAUTH_CLIENT_SECRET"

Check failure on line 19 in ccip/devenv/participant_auth.go

View workflow job for this annotation

GitHub Actions / lint (.)

G101: Potential hardcoded credentials (gosec)
envOnchainCantonJWT = "ONCHAIN_CANTON_JWT_TOKEN"
envClientID = "CLIENT_ID"
envClientSecret = "CLIENT_SECRET"
)

// buildParticipantAuthConfig returns an AuthConfig with an explicit type.
// Devenv participants with a JWT and a local (non-TLS) gRPC endpoint use insecureStatic.
// Real-chain connections default to clientCredentials from env (same as canton-login --ci).
// Static auth is used only when explicitly requested via CANTON_AUTH_TYPE=static.
func buildParticipantAuthConfig(participant blockchain.CantonParticipantEndpoints) (commonconfig.AuthConfig, error) {
if isDevenvParticipant(participant) {
return commonconfig.AuthConfig{
Type: commonconfig.AuthTypeInsecureStatic,
JWT: participant.JWT,
}, nil
}

authType := strings.TrimSpace(os.Getenv(envCantonAuthType))
if authType == "" {
authType = commonconfig.AuthTypeClientCredentials
}

switch authType {
case commonconfig.AuthTypeInsecureStatic:
jwt := participant.JWT
if jwt == "" {
return commonconfig.AuthConfig{}, fmt.Errorf("insecureStatic auth requires a JWT on the participant config")
}

return commonconfig.AuthConfig{
Type: commonconfig.AuthTypeInsecureStatic,
JWT: jwt,
}, nil

case commonconfig.AuthTypeStatic:
jwt := participant.JWT
if override := strings.TrimSpace(os.Getenv(envOnchainCantonJWT)); override != "" {
jwt = override
}
if jwt == "" {
return commonconfig.AuthConfig{}, fmt.Errorf("static auth requires jwt on participant config or %s", envOnchainCantonJWT)
}

return commonconfig.AuthConfig{
Type: commonconfig.AuthTypeStatic,
JWT: jwt,
}, nil

case commonconfig.AuthTypeAuthorizationCode:
return commonconfig.AuthConfig{
Type: commonconfig.AuthTypeAuthorizationCode,
AuthURL: strings.TrimSpace(os.Getenv(envCantonAuthURL)),
ClientID: oauthClientID(),
}, nil

case commonconfig.AuthTypeClientCredentials:
return commonconfig.AuthConfig{
Type: commonconfig.AuthTypeClientCredentials,
AuthURL: strings.TrimSpace(os.Getenv(envCantonAuthURL)),
ClientID: oauthClientID(),
ClientSecret: oauthClientSecret(),
}, nil

default:
return commonconfig.AuthConfig{}, fmt.Errorf("unsupported %s: %q", envCantonAuthType, authType)
}
}

func oauthClientID() string {
return firstNonEmpty(
strings.TrimSpace(os.Getenv(envCantonOAuthClientID)),
strings.TrimSpace(os.Getenv(envClientID)),
)
}

func oauthClientSecret() string {
return firstNonEmpty(
strings.TrimSpace(os.Getenv(envCantonOAuthClientSecret)),
strings.TrimSpace(os.Getenv(envClientSecret)),
)
}

func isDevenvParticipant(participant blockchain.CantonParticipantEndpoints) bool {
if strings.TrimSpace(participant.JWT) == "" {
return false
}

return isLocalDevenvEndpoint(participant.GRPCLedgerAPIURL)
}

// isLocalDevenvEndpoint reports whether the gRPC ledger URL is a local devenv endpoint
// (plain gRPC, not TLS on :443).
func isLocalDevenvEndpoint(grpcURL string) bool {
grpcURL = strings.TrimSpace(grpcURL)
if grpcURL == "" {
return false
}

hostPort := grpcURL
if idx := strings.LastIndex(hostPort, "/"); idx != -1 {
hostPort = hostPort[idx+1:]
}

return !strings.HasSuffix(hostPort, ":443")
}

func firstNonEmpty(values ...string) string {
for _, v := range values {
if v != "" {
return v
}
}

return ""
}

// participantAuthProvider validates auth config and builds an authentication.Provider.
func participantAuthProvider(ctx context.Context, auth commonconfig.AuthConfig) (authentication.Provider, error) {
if err := auth.Validate(); err != nil {
return nil, err
}

return auth.NewProvider(ctx)
}
Loading
Loading