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
33 changes: 30 additions & 3 deletions commonconfig/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ package commonconfig

import (
"context"
"crypto/tls"
"fmt"

"github.com/go-playground/validator/v10"
"google.golang.org/grpc/credentials"

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

"github.com/smartcontractkit/chainlink-canton/deployment/authentication/authorizationcode"
"github.com/smartcontractkit/chainlink-canton/deployment/authentication/clientcredentials"
"github.com/smartcontractkit/chainlink-canton/deployment/authentication/static"
)

// Supported authentication types for Canton participant APIs.
Expand All @@ -33,6 +36,9 @@ type AuthConfig struct {
// Defaults to "static" when omitted (backward compatible).
Type string `toml:"type" validate:"required,oneof=static insecureStatic clientCredentials authorizationCode"`

// InsecureSkipVerify configures the authentication provider to disable TLS certification validation.
InsecureSkipVerify bool `toml:"insecure_skip_verify,omitempty"`

// UserID is the user ID for the authentication. Required for clientCredentials and authorizationCode only.
UserID string `toml:"user_id" validate:"required_if=Type clientCredentials,required_if=Type authorizationCode"`

Expand Down Expand Up @@ -82,21 +88,42 @@ func (a *AuthConfig) NewProvider(ctx context.Context) (authentication.Provider,
return nil, fmt.Errorf("static auth requires a JWT token (set auth.jwt)")
}

return authentication.NewStaticProvider(a.JWT), nil
var options []static.ProviderOption
if a.InsecureSkipVerify {
options = append(options, static.WithTransportCredentials(credentials.NewTLS(&tls.Config{
InsecureSkipVerify: true, //nolint:gosec // Intentionally disabling TLS verification opt-in
})))
}

return static.NewStaticProvider(a.JWT, options...), nil

case AuthTypeClientCredentials:
if a.AuthURL == "" || a.ClientID == "" || a.ClientSecret == "" {
return nil, fmt.Errorf("clientCredentials auth requires auth_url, client_id, and client_secret")
}

return clientcredentials.NewDiscoveryProvider(ctx, a.AuthURL, a.ClientID, a.ClientSecret)
var options []clientcredentials.ProviderOption
if a.InsecureSkipVerify {
options = append(options, clientcredentials.WithTransportCredentials(credentials.NewTLS(&tls.Config{
InsecureSkipVerify: true, //nolint:gosec // Intentionally disabling TLS verification opt-in
})))
}

return clientcredentials.NewDiscoveryProvider(ctx, a.AuthURL, a.ClientID, a.ClientSecret, options...)

case AuthTypeAuthorizationCode:
if a.AuthURL == "" || a.ClientID == "" {
return nil, fmt.Errorf("authorizationCode auth requires auth_url and client_id")
}

return authorizationcode.NewDiscoveryProvider(ctx, a.AuthURL, a.ClientID)
var options []authorizationcode.ProviderOption
if a.InsecureSkipVerify {
options = append(options, authorizationcode.WithTransportCredentials(credentials.NewTLS(&tls.Config{
InsecureSkipVerify: true, //nolint:gosec // Intentionally disabling TLS verification opt-in
})))
}

return authorizationcode.NewDiscoveryProvider(ctx, a.AuthURL, a.ClientID, options...)

default:
return nil, fmt.Errorf("unsupported auth type: %q (expected static, insecureStatic, clientCredentials, or authorizationCode)", authType)
Expand Down
104 changes: 104 additions & 0 deletions deployment/authentication/static/static.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package static

import (
"context"
"crypto/tls"

"golang.org/x/oauth2"
"google.golang.org/grpc/credentials"

"github.com/smartcontractkit/chainlink-deployments-framework/chain/canton/provider/authentication"
)

var _ authentication.Provider = &Provider{}

type Provider struct {
accessToken string
transportCredentials credentials.TransportCredentials
}

type staticProviderConfig struct {
transportCredentials credentials.TransportCredentials
}

func defaultStaticProviderConfig() staticProviderConfig {
return staticProviderConfig{
transportCredentials: credentials.NewTLS(
&tls.Config{
MinVersion: tls.VersionTLS12,
},
),
}
}

// ProviderOption configures the client credentials Provider using the functional options pattern.
// Options allow customization of the provider without breaking API compatibility.
type ProviderOption func(*staticProviderConfig)

// WithTransportCredentials configures the Provider to use the given transport credentials for gRPC connections.
// This allows customization of TLS settings, including certificate verification and minimum TLS version.
// The default transport credentials use TLS 1.2 or higher.
//
// Example:
//
// WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}))
func WithTransportCredentials(credentials credentials.TransportCredentials) ProviderOption {
return func(config *staticProviderConfig) {
config.transportCredentials = credentials
}
}

func NewStaticProvider(accessToken string, options ...ProviderOption) *Provider {
cfg := defaultStaticProviderConfig()
for _, option := range options {
option(&cfg)
}

return &Provider{
accessToken: accessToken,
transportCredentials: cfg.transportCredentials,
}
}

func (p Provider) TokenSource() oauth2.TokenSource {
return oauth2.StaticTokenSource(&oauth2.Token{
AccessToken: p.accessToken,
})
}

func (p Provider) TransportCredentials() credentials.TransportCredentials {
return p.transportCredentials
}

func (p Provider) PerRPCCredentials() credentials.PerRPCCredentials {
return secureTokenSource{
TokenSource: p.TokenSource(),
}
}

// secureTokenSource is a secure OAuth2 PerRPCCredentials implementation that
// requires transport security.
type secureTokenSource struct {
oauth2.TokenSource
}

var _ credentials.PerRPCCredentials = secureTokenSource{}

func (ts secureTokenSource) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
token, err := ts.Token()
if err != nil {
return nil, err
}
if token == nil {
//nolint:nilnil // nothing to do here, just returning no metadata and no error
return nil, nil
}

return map[string]string{
"authorization": "Bearer " + token.AccessToken,
}, nil
}

func (ts secureTokenSource) RequireTransportSecurity() bool {
return true
}
36 changes: 36 additions & 0 deletions deployment/authentication/static/static_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package static

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/credentials/insecure"
)

func TestStaticProvider(t *testing.T) {
t.Parallel()

testToken := "test-token-123"
provider := NewStaticProvider(testToken)

token, err := provider.TokenSource().Token()
require.NoError(t, err)
assert.Equal(t, testToken, token.AccessToken)

transportCredentials := provider.TransportCredentials()
require.NotNil(t, transportCredentials)
assert.NotEqual(t, insecure.NewCredentials(), transportCredentials)

perRPCCredentials := provider.PerRPCCredentials()
require.NotNil(t, perRPCCredentials)

metadata, err := perRPCCredentials.GetRequestMetadata(t.Context())
require.NoError(t, err)
header, ok := metadata["authorization"]
require.True(t, ok, "PerRPCCredentials didn't return authorization header")
assert.Equal(t, "Bearer "+testToken, header)

requireTransportSecurity := perRPCCredentials.RequireTransportSecurity()
assert.True(t, requireTransportSecurity, "PerRPCCredentials must require transport security")
}
Loading