Enable Google KMS and Azure Key Vault for publisher login tool (#696)
<!-- Provide a brief summary of your changes --> ## Motivation and Context This allows the private key used for DNS or HTTP based authentication to be stored securely in a cloud key management system. These services often provide HSM storage which makes it very hard to leak the private key. The full context is described in my design document here: https://github.com/modelcontextprotocol/registry/issues/482#issuecomment-3437117171 ## How Has This Been Tested? I have tested it against PROD using an Ed25519 key stored in Google KMS. I have added unit tests. I have tested both ECDSA P-384 and Ed25519 with Az KV and Google KMS against a locally running server. ## Breaking Changes None intended. ## Types of changes <!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update ## Checklist <!-- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [x] My code follows the repository's style guidelines - [x] New and existing tests pass locally - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ## Additional context <!-- Add any other context, implementation notes, or design decisions --> --------- Co-authored-by: adam jones <domdomegg+git@gmail.com>
This commit is contained in:
parent
3d375e6544
commit
88c2e1be16
11 changed files with 641 additions and 91 deletions
|
|
@ -32,6 +32,12 @@ make dev-compose # Start local registry
|
|||
- **`http`** - Domain verification via HTTPS endpoints
|
||||
- **`none`** - No auth (testing only)
|
||||
|
||||
### Signing Providers
|
||||
Optional: enables `dns` and `http` methods to sign out-of-process without direct access to the private key.
|
||||
|
||||
- **`google-kms`** - Google KMS signing
|
||||
- **`azure-key-vault`** - Azure Key Vault signing
|
||||
|
||||
## Key Files
|
||||
|
||||
- **`main.go`** - CLI setup and command routing
|
||||
|
|
|
|||
85
cmd/publisher/auth/azurekeyvault/common.go
Normal file
85
cmd/publisher/auth/azurekeyvault/common.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package azurekeyvault
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys"
|
||||
"github.com/modelcontextprotocol/registry/cmd/publisher/auth"
|
||||
)
|
||||
|
||||
func GetSignatureProvider(vaultName, keyName string) (auth.Signer, error) {
|
||||
if vaultName == "" {
|
||||
return nil, fmt.Errorf("--vault option (vault name) is required")
|
||||
}
|
||||
|
||||
if keyName == "" {
|
||||
return nil, fmt.Errorf("--key option (key name) is required")
|
||||
}
|
||||
|
||||
return Signer{
|
||||
vaultName: vaultName,
|
||||
keyName: keyName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type Signer struct {
|
||||
vaultName string
|
||||
keyName string
|
||||
}
|
||||
|
||||
func (d Signer) GetSignedTimestamp(ctx context.Context) (*string, []byte, error) {
|
||||
fmt.Fprintf(os.Stdout, "Signing using Azure Key Vault %s and key %s\n", d.vaultName, d.keyName)
|
||||
|
||||
cred, err := azidentity.NewDefaultAzureCredential(nil)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("authentication to Azure failed: %w", err)
|
||||
}
|
||||
|
||||
vaultURL := fmt.Sprintf("https://%s.vault.azure.net/", d.vaultName)
|
||||
client, err := azkeys.NewClient(vaultURL, cred, nil)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create Key Vault client: %w", err)
|
||||
}
|
||||
|
||||
keyResp, err := client.GetKey(ctx, d.keyName, "", nil)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to retrieve key for public parameters: %w", err)
|
||||
}
|
||||
|
||||
if *keyResp.Key.Kty != azkeys.KeyTypeEC && *keyResp.Key.Kty != azkeys.KeyTypeECHSM {
|
||||
return nil, nil, fmt.Errorf("unsupported key type: kty: %s (only EC or EC-HSM keys are supported)", *keyResp.Key.Kty)
|
||||
}
|
||||
|
||||
if *keyResp.Key.Crv != azkeys.CurveNameP384 {
|
||||
return nil, nil, fmt.Errorf("unsupported curve: %s (only P-384 is supported)", *keyResp.Key.Crv)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stdout, "Successfully read the public key from Key Vault.")
|
||||
auth.PrintEcdsaP384KeyInfo(ecdsa.PublicKey{
|
||||
Curve: elliptic.P384(),
|
||||
X: new(big.Int).SetBytes(keyResp.Key.X),
|
||||
Y: new(big.Int).SetBytes(keyResp.Key.Y),
|
||||
})
|
||||
|
||||
timestamp := auth.GetTimestamp()
|
||||
digest := sha512.Sum384([]byte(timestamp))
|
||||
alg := azkeys.SignatureAlgorithmES384
|
||||
fmt.Fprintln(os.Stdout, "Executing the sign request...")
|
||||
signResp, err := client.Sign(ctx, d.keyName, "", azkeys.SignParameters{
|
||||
Algorithm: &alg,
|
||||
Value: digest[:],
|
||||
}, nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to sign message: %w", err)
|
||||
}
|
||||
|
||||
return ×tamp, signResp.Result, nil
|
||||
}
|
||||
|
|
@ -8,12 +8,14 @@ import (
|
|||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
|
@ -30,11 +32,35 @@ const (
|
|||
|
||||
// CryptoProvider provides common functionality for DNS and HTTP authentication
|
||||
type CryptoProvider struct {
|
||||
registryURL string
|
||||
domain string
|
||||
privateKey string
|
||||
cryptoAlgorithm CryptoAlgorithm
|
||||
authMethod string
|
||||
registryURL string
|
||||
domain string
|
||||
signer Signer
|
||||
authMethod string
|
||||
}
|
||||
|
||||
type Signer interface {
|
||||
GetSignedTimestamp(ctx context.Context) (*string, []byte, error)
|
||||
}
|
||||
|
||||
func GetTimestamp() string {
|
||||
return time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func NewInProcessSigner(privateKey string, algorithm CryptoAlgorithm) (Signer, error) {
|
||||
if privateKey == "" {
|
||||
return nil, fmt.Errorf("%s private key (hex) is required", algorithm)
|
||||
}
|
||||
|
||||
// Decode private key from hex
|
||||
privateKeyBytes, err := hex.DecodeString(privateKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid hex private key format: %w", err)
|
||||
}
|
||||
|
||||
return &InProcessSigner{
|
||||
privateKey: privateKeyBytes,
|
||||
cryptoAlgorithm: algorithm,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetToken retrieves the registry JWT token using cryptographic authentication
|
||||
|
|
@ -43,26 +69,15 @@ func (c *CryptoProvider) GetToken(ctx context.Context) (string, error) {
|
|||
return "", fmt.Errorf("%s domain is required", c.authMethod)
|
||||
}
|
||||
|
||||
if c.privateKey == "" {
|
||||
return "", fmt.Errorf("%s private key (hex) is required", c.authMethod)
|
||||
}
|
||||
|
||||
// Decode private key from hex
|
||||
privateKeyBytes, err := hex.DecodeString(c.privateKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid hex private key format: %w", err)
|
||||
}
|
||||
|
||||
// Generate current timestamp
|
||||
timestamp := time.Now().UTC().Format(time.RFC3339)
|
||||
signedTimestamp, err := c.signMessage(privateKeyBytes, []byte(timestamp))
|
||||
timestamp, signedTimestamp, err := c.signer.GetSignedTimestamp(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to sign timestamp: %w", err)
|
||||
}
|
||||
signedTimestampHex := hex.EncodeToString(signedTimestamp)
|
||||
|
||||
// Exchange signature for registry token
|
||||
registryToken, err := c.exchangeTokenForRegistry(ctx, c.domain, timestamp, signedTimestampHex)
|
||||
registryToken, err := c.exchangeTokenForRegistry(ctx, c.domain, *timestamp, signedTimestampHex)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to exchange %s signature: %w", c.authMethod, err)
|
||||
}
|
||||
|
|
@ -70,38 +85,52 @@ func (c *CryptoProvider) GetToken(ctx context.Context) (string, error) {
|
|||
return registryToken, nil
|
||||
}
|
||||
|
||||
func (c *CryptoProvider) signMessage(privateKeyBytes []byte, message []byte) ([]byte, error) {
|
||||
type InProcessSigner struct {
|
||||
privateKey []byte
|
||||
cryptoAlgorithm CryptoAlgorithm
|
||||
}
|
||||
|
||||
func (c *InProcessSigner) GetSignedTimestamp(_ context.Context) (*string, []byte, error) {
|
||||
fmt.Fprintf(os.Stdout, "Signing in process using key algorithm %s\n", c.cryptoAlgorithm)
|
||||
|
||||
timestamp := GetTimestamp()
|
||||
|
||||
switch c.cryptoAlgorithm {
|
||||
case AlgorithmEd25519:
|
||||
if len(privateKeyBytes) != ed25519.SeedSize {
|
||||
return nil, fmt.Errorf("invalid seed length: expected %d bytes, got %d", ed25519.SeedSize, len(privateKeyBytes))
|
||||
if len(c.privateKey) != ed25519.SeedSize {
|
||||
return nil, nil, fmt.Errorf("invalid seed length: expected %d bytes, got %d", ed25519.SeedSize, len(c.privateKey))
|
||||
}
|
||||
|
||||
privateKey := ed25519.NewKeyFromSeed(privateKeyBytes)
|
||||
signature := ed25519.Sign(privateKey, message)
|
||||
return signature, nil
|
||||
privateKey := ed25519.NewKeyFromSeed(c.privateKey)
|
||||
|
||||
PrintEd25519KeyInfo(privateKey.Public().(ed25519.PublicKey))
|
||||
|
||||
signature := ed25519.Sign(privateKey, []byte(timestamp))
|
||||
return ×tamp, signature, nil
|
||||
case AlgorithmECDSAP384:
|
||||
if len(privateKeyBytes) != 48 {
|
||||
return nil, fmt.Errorf("invalid seed length for ECDSA P-384: expected 48 bytes, got %d", len(privateKeyBytes))
|
||||
if len(c.privateKey) != 48 {
|
||||
return nil, nil, fmt.Errorf("invalid seed length for ECDSA P-384: expected 48 bytes, got %d", len(c.privateKey))
|
||||
}
|
||||
|
||||
digest := sha512.Sum384(message)
|
||||
digest := sha512.Sum384([]byte(timestamp))
|
||||
curve := elliptic.P384()
|
||||
|
||||
// Parse the raw private key (compatible with Go 1.24)
|
||||
privateKey, err := parseRawPrivateKey(curve, privateKeyBytes)
|
||||
privateKey, err := parseRawPrivateKey(curve, c.privateKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse ECDSA private key: %w", err)
|
||||
return nil, nil, fmt.Errorf("failed to parse ECDSA private key: %w", err)
|
||||
}
|
||||
|
||||
PrintEcdsaP384KeyInfo(privateKey.PublicKey)
|
||||
|
||||
r, s, err := ecdsa.Sign(rand.Reader, privateKey, digest[:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign message: %w", err)
|
||||
return nil, nil, fmt.Errorf("failed to sign message: %w", err)
|
||||
}
|
||||
signature := append(r.Bytes(), s.Bytes()...)
|
||||
return signature, nil
|
||||
return ×tamp, signature, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported crypto algorithm: %s", c.cryptoAlgorithm)
|
||||
return nil, nil, fmt.Errorf("unsupported crypto algorithm: %s", c.cryptoAlgorithm)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -112,6 +141,11 @@ func parseRawPrivateKey(curve elliptic.Curve, privateKeyBytes []byte) (*ecdsa.Pr
|
|||
return nil, fmt.Errorf("nil curve")
|
||||
}
|
||||
|
||||
expectedBytes := (curve.Params().N.BitLen() + 7) / 8
|
||||
if len(privateKeyBytes) != expectedBytes {
|
||||
return nil, fmt.Errorf("invalid private key length: expected %d bytes, got %d", expectedBytes, len(privateKeyBytes))
|
||||
}
|
||||
|
||||
// Only standard NIST curves supported
|
||||
switch curve {
|
||||
case elliptic.P224(), elliptic.P256(), elliptic.P384(), elliptic.P521():
|
||||
|
|
@ -147,6 +181,23 @@ func (c *CryptoProvider) Login(_ context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func PrintEd25519KeyInfo(pubKey ed25519.PublicKey) {
|
||||
pubKeyString := base64.StdEncoding.EncodeToString(pubKey)
|
||||
fmt.Fprint(os.Stdout, "Expected proof record:\n")
|
||||
fmt.Fprintf(os.Stdout, "v=MCPv1; k=ed25519; p=%s\n", pubKeyString)
|
||||
}
|
||||
|
||||
func PrintEcdsaP384KeyInfo(pubKey ecdsa.PublicKey) {
|
||||
printEcdsaKeyInfo("ecdsap384", pubKey)
|
||||
}
|
||||
|
||||
func printEcdsaKeyInfo(k string, pubKey ecdsa.PublicKey) {
|
||||
compressed := elliptic.MarshalCompressed(pubKey.Curve, pubKey.X, pubKey.Y)
|
||||
pubKeyString := base64.StdEncoding.EncodeToString(compressed)
|
||||
fmt.Fprint(os.Stdout, "Expected proof record:\n")
|
||||
fmt.Fprintf(os.Stdout, "v=MCPv1; k=%s; p=%s\n", k, pubKeyString)
|
||||
}
|
||||
|
||||
// exchangeTokenForRegistry exchanges signature for a registry JWT token
|
||||
func (c *CryptoProvider) exchangeTokenForRegistry(ctx context.Context, domain, timestamp, signedTimestamp string) (string, error) {
|
||||
if c.registryURL == "" {
|
||||
|
|
|
|||
|
|
@ -5,14 +5,13 @@ type DNSProvider struct {
|
|||
}
|
||||
|
||||
// NewDNSProvider creates a new DNS-based auth provider
|
||||
func NewDNSProvider(registryURL, domain, privateKey string, cryptoAlgorithm CryptoAlgorithm) Provider {
|
||||
func NewDNSProvider(registryURL, domain string, signer *Signer) Provider {
|
||||
return &DNSProvider{
|
||||
CryptoProvider: &CryptoProvider{
|
||||
registryURL: registryURL,
|
||||
domain: domain,
|
||||
privateKey: privateKey,
|
||||
cryptoAlgorithm: cryptoAlgorithm,
|
||||
authMethod: "dns",
|
||||
registryURL: registryURL,
|
||||
domain: domain,
|
||||
signer: *signer,
|
||||
authMethod: "dns",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
147
cmd/publisher/auth/googlekms/common.go
Normal file
147
cmd/publisher/auth/googlekms/common.go
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
package googlekms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
|
||||
kms "cloud.google.com/go/kms/apiv1"
|
||||
"cloud.google.com/go/kms/apiv1/kmspb"
|
||||
"github.com/modelcontextprotocol/registry/cmd/publisher/auth"
|
||||
)
|
||||
|
||||
// GetSignatureProvider validates inputs and returns a GoogleKMSSigner implementing auth.Signer.
|
||||
func GetSignatureProvider(resourceName string) (auth.Signer, error) {
|
||||
if resourceName == "" {
|
||||
return nil, fmt.Errorf("--resource is required, e.g. projects/my-project/locations/global/keyRings/fellowship/cryptoKeys/bilbo/cryptoKeyVersions/1")
|
||||
}
|
||||
|
||||
return &Signer{
|
||||
resource: resourceName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type Signer struct {
|
||||
resource string
|
||||
}
|
||||
|
||||
type ecdsaSignature struct {
|
||||
R, S *big.Int
|
||||
}
|
||||
|
||||
func derToRS(der []byte, curve elliptic.Curve) ([]byte, error) {
|
||||
var sig ecdsaSignature
|
||||
if _, err := asn1.Unmarshal(der, &sig); err != nil {
|
||||
return nil, fmt.Errorf("invalid DER ECDSA signature: %w", err)
|
||||
}
|
||||
if sig.R == nil || sig.S == nil || sig.R.Sign() <= 0 || sig.S.Sign() <= 0 {
|
||||
return nil, fmt.Errorf("invalid ECDSA signature components")
|
||||
}
|
||||
|
||||
size := (curve.Params().BitSize + 7) / 8
|
||||
rBytes := sig.R.Bytes()
|
||||
sBytes := sig.S.Bytes()
|
||||
if len(rBytes) > size || len(sBytes) > size {
|
||||
return nil, fmt.Errorf("ECDSA signature component too large")
|
||||
}
|
||||
|
||||
out := make([]byte, 2*size)
|
||||
copy(out[size-len(rBytes):size], rBytes)
|
||||
copy(out[2*size-len(sBytes):], sBytes)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (g *Signer) GetSignedTimestamp(ctx context.Context) (*string, []byte, error) {
|
||||
client, err := kms.NewKeyManagementClient(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create KMS client: %w", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// Fetch public key (PEM) so we can output expected proof record.
|
||||
algo, err := g.showPublicKeyAndGetAlgorithm(ctx, client)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
timestamp := auth.GetTimestamp()
|
||||
|
||||
fmt.Fprintln(os.Stdout, "Executing the sign request...")
|
||||
switch algo {
|
||||
case auth.AlgorithmEd25519:
|
||||
signReq := &kmspb.AsymmetricSignRequest{
|
||||
Name: g.resource,
|
||||
Data: []byte(timestamp),
|
||||
}
|
||||
signResp, err := client.AsymmetricSign(ctx, signReq)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to sign with KMS: %w", err)
|
||||
}
|
||||
|
||||
return ×tamp, signResp.Signature, nil
|
||||
case auth.AlgorithmECDSAP384:
|
||||
digest := sha512.Sum384([]byte(timestamp))
|
||||
signReq := &kmspb.AsymmetricSignRequest{
|
||||
Name: g.resource,
|
||||
Digest: &kmspb.Digest{Digest: &kmspb.Digest_Sha384{Sha384: digest[:]}},
|
||||
}
|
||||
signResp, err := client.AsymmetricSign(ctx, signReq)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to sign with KMS: %w", err)
|
||||
}
|
||||
|
||||
sigBytes, err := derToRS(signResp.Signature, elliptic.P384())
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to convert DER signature: %w", err)
|
||||
}
|
||||
|
||||
return ×tamp, sigBytes, nil
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("unsupported algorithm: %s", algo)
|
||||
}
|
||||
|
||||
func (g *Signer) showPublicKeyAndGetAlgorithm(ctx context.Context, client *kms.KeyManagementClient) (auth.CryptoAlgorithm, error) {
|
||||
pubResp, err := client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{Name: g.resource})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get public key: %w", err)
|
||||
}
|
||||
|
||||
block, _ := pem.Decode([]byte(pubResp.Pem))
|
||||
if block == nil {
|
||||
return "", errors.New("failed to decode PEM public key from KMS")
|
||||
}
|
||||
|
||||
parsed, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse public key: %w", err)
|
||||
}
|
||||
|
||||
switch pubResp.Algorithm { //nolint:exhaustive
|
||||
case kmspb.CryptoKeyVersion_EC_SIGN_ED25519:
|
||||
pk, ok := parsed.(ed25519.PublicKey)
|
||||
if !ok {
|
||||
return "", errors.New("KMS reported ED25519 but parsed key is different type")
|
||||
}
|
||||
auth.PrintEd25519KeyInfo(pk)
|
||||
return auth.AlgorithmEd25519, nil
|
||||
case kmspb.CryptoKeyVersion_EC_SIGN_P384_SHA384:
|
||||
pk, ok := parsed.(*ecdsa.PublicKey)
|
||||
if !ok || pk.Curve != elliptic.P384() {
|
||||
return "", errors.New("KMS reported P-384 but parsed key mismatch")
|
||||
}
|
||||
auth.PrintEcdsaP384KeyInfo(*pk)
|
||||
return auth.AlgorithmECDSAP384, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported KMS key algorithm: %s", pubResp.Algorithm.String())
|
||||
}
|
||||
}
|
||||
|
|
@ -5,14 +5,13 @@ type HTTPProvider struct {
|
|||
}
|
||||
|
||||
// NewHTTPProvider creates a new HTTP-based auth provider
|
||||
func NewHTTPProvider(registryURL, domain, privateKey string, cryptoAlgorithm CryptoAlgorithm) Provider {
|
||||
func NewHTTPProvider(registryURL, domain string, signer *Signer) Provider {
|
||||
return &HTTPProvider{
|
||||
CryptoProvider: &CryptoProvider{
|
||||
registryURL: registryURL,
|
||||
domain: domain,
|
||||
privateKey: privateKey,
|
||||
cryptoAlgorithm: cryptoAlgorithm,
|
||||
authMethod: "http",
|
||||
registryURL: registryURL,
|
||||
domain: domain,
|
||||
signer: *signer,
|
||||
authMethod: "http",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,11 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/modelcontextprotocol/registry/cmd/publisher/auth"
|
||||
"github.com/modelcontextprotocol/registry/cmd/publisher/auth/azurekeyvault"
|
||||
"github.com/modelcontextprotocol/registry/cmd/publisher/auth/googlekms"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -19,6 +22,27 @@ const (
|
|||
|
||||
type CryptoAlgorithm auth.CryptoAlgorithm
|
||||
|
||||
type SignerType string
|
||||
|
||||
type LoginFlags struct {
|
||||
Domain string
|
||||
PrivateKey string
|
||||
RegistryURL string
|
||||
KvVault string
|
||||
KvKeyName string
|
||||
KmsResource string
|
||||
CryptoAlgorithm CryptoAlgorithm
|
||||
SignerType SignerType
|
||||
ArgOffset int
|
||||
}
|
||||
|
||||
const (
|
||||
InProcessSignerType SignerType = "in-process"
|
||||
AzureKeyVaultSignerType SignerType = "azure-key-vault"
|
||||
GoogleKMSSignerType SignerType = "google-kms"
|
||||
NoSignerType SignerType = "none"
|
||||
)
|
||||
|
||||
func (c *CryptoAlgorithm) String() string {
|
||||
return string(*c)
|
||||
}
|
||||
|
|
@ -32,56 +56,146 @@ func (c *CryptoAlgorithm) Set(v string) error {
|
|||
return fmt.Errorf("invalid algorithm: %q (allowed: ed25519, ecdsap384)", v)
|
||||
}
|
||||
|
||||
func parseLoginFlags(method string, args []string) (LoginFlags, error) {
|
||||
var flags LoginFlags
|
||||
loginFlags := flag.NewFlagSet("login", flag.ExitOnError)
|
||||
flags.CryptoAlgorithm = CryptoAlgorithm(auth.AlgorithmEd25519)
|
||||
flags.SignerType = NoSignerType
|
||||
flags.ArgOffset = 1
|
||||
loginFlags.StringVar(&flags.RegistryURL, "registry", DefaultRegistryURL, "Registry URL")
|
||||
|
||||
if method == "dns" || method == "http" {
|
||||
loginFlags.StringVar(&flags.Domain, "domain", "", "Domain name")
|
||||
if len(args) > 1 {
|
||||
switch args[1] {
|
||||
case string(AzureKeyVaultSignerType):
|
||||
flags.SignerType = AzureKeyVaultSignerType
|
||||
loginFlags.StringVar(&flags.KvVault, "vault", "", "The name of the Azure Key Vault resource")
|
||||
loginFlags.StringVar(&flags.KvKeyName, "key", "", "Name of the signing key in the Azure Key Vault")
|
||||
flags.ArgOffset = 2
|
||||
case string(GoogleKMSSignerType):
|
||||
flags.SignerType = GoogleKMSSignerType
|
||||
loginFlags.StringVar(&flags.KmsResource, "resource", "", "Google Cloud KMS resource name (e.g. projects/lotr/locations/global/keyRings/fellowship/cryptoKeys/frodo/cryptoKeyVersions/1)")
|
||||
flags.ArgOffset = 2
|
||||
}
|
||||
}
|
||||
if flags.SignerType == NoSignerType {
|
||||
flags.SignerType = InProcessSignerType
|
||||
loginFlags.StringVar(&flags.PrivateKey, "private-key", "", "Private key (hex)")
|
||||
loginFlags.Var(&flags.CryptoAlgorithm, "algorithm", "Cryptographic algorithm (ed25519, ecdsap384)")
|
||||
}
|
||||
}
|
||||
err := loginFlags.Parse(args[flags.ArgOffset:])
|
||||
if err == nil {
|
||||
flags.RegistryURL = strings.TrimRight(flags.RegistryURL, "/")
|
||||
}
|
||||
|
||||
return flags, err
|
||||
}
|
||||
|
||||
func createSigner(flags LoginFlags) (auth.Signer, error) {
|
||||
switch flags.SignerType {
|
||||
case AzureKeyVaultSignerType:
|
||||
return azurekeyvault.GetSignatureProvider(flags.KvVault, flags.KvKeyName)
|
||||
case GoogleKMSSignerType:
|
||||
return googlekms.GetSignatureProvider(flags.KmsResource)
|
||||
case InProcessSignerType:
|
||||
return auth.NewInProcessSigner(flags.PrivateKey, auth.CryptoAlgorithm(flags.CryptoAlgorithm))
|
||||
case NoSignerType:
|
||||
return nil, errors.New("no signing provider specified")
|
||||
default:
|
||||
return nil, errors.New("unknown signing provider specified")
|
||||
}
|
||||
}
|
||||
|
||||
func createAuthProvider(method, registryURL, domain string, signer auth.Signer) (auth.Provider, error) {
|
||||
switch method {
|
||||
case "github":
|
||||
return auth.NewGitHubATProvider(true, registryURL), nil
|
||||
case "github-oidc":
|
||||
return auth.NewGitHubOIDCProvider(registryURL), nil
|
||||
case "dns":
|
||||
if domain == "" {
|
||||
return nil, errors.New("dns authentication requires --domain")
|
||||
}
|
||||
return auth.NewDNSProvider(registryURL, domain, &signer), nil
|
||||
case "http":
|
||||
if domain == "" {
|
||||
return nil, errors.New("http authentication requires --domain")
|
||||
}
|
||||
return auth.NewHTTPProvider(registryURL, domain, &signer), nil
|
||||
case "none":
|
||||
return auth.NewNoneProvider(registryURL), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown authentication method: %s\nFor a list of available methods, run: mcp-publisher login", method)
|
||||
}
|
||||
}
|
||||
|
||||
func LoginCommand(args []string) error {
|
||||
if len(args) < 1 {
|
||||
return errors.New("authentication method required\n\nUsage: mcp-publisher login <method>\n\nMethods:\n github Interactive GitHub authentication\n github-oidc GitHub Actions OIDC authentication\n dns DNS-based authentication (requires --domain and --private-key)\n http HTTP-based authentication (requires --domain and --private-key)\n none Anonymous authentication (for testing)")
|
||||
return errors.New(`authentication method required
|
||||
|
||||
Usage: mcp-publisher login <method> [<signing provider>]
|
||||
|
||||
Methods:
|
||||
github Interactive GitHub authentication
|
||||
github-oidc GitHub Actions OIDC authentication
|
||||
dns DNS-based authentication (requires --domain)
|
||||
http HTTP-based authentication (requires --domain)
|
||||
none Anonymous authentication (for testing)
|
||||
|
||||
Signing providers:
|
||||
azure-key-vault Sign using Azure Key Vault
|
||||
google-kms Sign using Google Cloud KMS
|
||||
|
||||
The dns and http methods require a --private-key for in-process signing. For
|
||||
out-of-process signing, use one of the supported signing providers. Signing is
|
||||
needed for an authentication challenge with the registry.
|
||||
|
||||
The github and github-oidc methods do not support signing providers and
|
||||
authenticate using the GitHub as an identity provider.
|
||||
|
||||
Examples:
|
||||
|
||||
# Interactive GitHub login, using device code flow
|
||||
mcp-publisher login github
|
||||
|
||||
# Sign in using a specific Ed25519 private key for DNS authentication
|
||||
mcp-publisher login dns -algorithm ed25519 -domain example.com -private-key <64 hex chars>
|
||||
|
||||
# Sign in using a specific ECDSA P-384 private key for DNS authentication
|
||||
mcp-publisher login dns -algorithm ecdsap384 -domain example.com -private-key <96 hex chars>
|
||||
|
||||
# Sign in with gcloud CLI, use Google Cloud KMS for signing in DNS authentication
|
||||
gcloud auth application-default login
|
||||
mcp-publisher login dns google-kms -domain example.com -resource projects/lotr/locations/global/keyRings/fellowship/cryptoKeys/frodo/cryptoKeyVersions/1
|
||||
|
||||
# Sign in with az CLI, use Azure Key Vault for signing in HTTP authentication
|
||||
az login
|
||||
mcp-publisher login http azure-key-vault -domain example.com -vault myvault -key mysigningkey
|
||||
|
||||
`)
|
||||
}
|
||||
|
||||
method := args[0]
|
||||
|
||||
// Parse remaining flags based on method
|
||||
loginFlags := flag.NewFlagSet("login", flag.ExitOnError)
|
||||
var domain string
|
||||
var privateKey string
|
||||
var cryptoAlgorithm = CryptoAlgorithm(auth.AlgorithmEd25519)
|
||||
var registryURL string
|
||||
|
||||
loginFlags.StringVar(®istryURL, "registry", DefaultRegistryURL, "Registry URL")
|
||||
|
||||
if method == "dns" || method == "http" {
|
||||
loginFlags.StringVar(&domain, "domain", "", "Domain name")
|
||||
loginFlags.StringVar(&privateKey, "private-key", "", "Private key (hex)")
|
||||
loginFlags.Var(&cryptoAlgorithm, "algorithm", "Cryptographic algorithm (ed25519, ecdsap384)")
|
||||
}
|
||||
|
||||
if err := loginFlags.Parse(args[1:]); err != nil {
|
||||
flags, err := parseLoginFlags(method, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create auth provider based on method
|
||||
var authProvider auth.Provider
|
||||
switch method {
|
||||
case "github":
|
||||
authProvider = auth.NewGitHubATProvider(true, registryURL)
|
||||
case "github-oidc":
|
||||
authProvider = auth.NewGitHubOIDCProvider(registryURL)
|
||||
case "dns":
|
||||
if domain == "" || privateKey == "" {
|
||||
return errors.New("dns authentication requires --domain and --private-key")
|
||||
var signer auth.Signer
|
||||
if flags.SignerType != NoSignerType {
|
||||
signer, err = createSigner(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authProvider = auth.NewDNSProvider(registryURL, domain, privateKey, auth.CryptoAlgorithm(cryptoAlgorithm))
|
||||
case "http":
|
||||
if domain == "" || privateKey == "" {
|
||||
return errors.New("http authentication requires --domain and --private-key")
|
||||
}
|
||||
authProvider = auth.NewHTTPProvider(registryURL, domain, privateKey, auth.CryptoAlgorithm(cryptoAlgorithm))
|
||||
case "none":
|
||||
authProvider = auth.NewNoneProvider(registryURL)
|
||||
default:
|
||||
return fmt.Errorf("unknown authentication method: %s\nFor a list of available methods, run: mcp-publisher login", method)
|
||||
}
|
||||
|
||||
// Perform login
|
||||
authProvider, err := createAuthProvider(method, flags.RegistryURL, flags.Domain, signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
_, _ = fmt.Fprintf(os.Stdout, "Logging in with %s...\n", method)
|
||||
|
||||
|
|
@ -89,13 +203,11 @@ func LoginCommand(args []string) error {
|
|||
return fmt.Errorf("login failed: %w", err)
|
||||
}
|
||||
|
||||
// Get and save token
|
||||
token, err := authProvider.GetToken(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get token: %w", err)
|
||||
}
|
||||
|
||||
// Save token to file
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get home directory: %w", err)
|
||||
|
|
@ -105,7 +217,7 @@ func LoginCommand(args []string) error {
|
|||
tokenData := map[string]string{
|
||||
"token": token,
|
||||
"method": method,
|
||||
"registry": registryURL,
|
||||
"registry": flags.RegistryURL,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(tokenData)
|
||||
|
|
|
|||
|
|
@ -471,7 +471,7 @@ echo "yourcompany.com. IN TXT \"v=MCPv1; k=ed25519; p=$(openssl pkey -in key.pem
|
|||
mcp-publisher login dns --domain yourcompany.com --private-key $(openssl pkey -in key.pem -noout -text | grep -A3 "priv:" | tail -n +2 | tr -d ' :\n')
|
||||
```
|
||||
|
||||
The ECDSA P-384 crypto algorithm is also supported, along with HTTP-based authenication. See the [publisher CLI commands reference](../../reference/cli/commands.md) for more details.
|
||||
The ECDSA P-384 algorithm is also supported, along with HTTP-based authentication. See the [publisher CLI commands reference](../../reference/cli/commands.md) for more details.
|
||||
|
||||
## Step 5: Publish Your Server
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ mcp-publisher login dns --domain=example.com --private-key=HEX_KEY [--registry=U
|
|||
- Verifies domain ownership via DNS TXT record
|
||||
- Grants access to `com.example.*` namespaces
|
||||
- Requires Ed25519 private key (64-character hex) or ECDSA P-384 private key (96-character hex)
|
||||
- The private key can be stored in a cloud signing provider like Google KMS or Azure Key Vault.
|
||||
|
||||
**Setup:** (for Ed25519, recommended)
|
||||
```bash
|
||||
|
|
@ -112,6 +113,62 @@ openssl ec -in key.pem -text -noout -conv_form compressed | grep -A4 "pub:" | ta
|
|||
openssl ec -in <pem path> -noout -text | grep -A4 "priv:" | tail -n +2 | tr -d ' :\n'
|
||||
```
|
||||
|
||||
**Setup:** (for Google KMS signing)
|
||||
|
||||
This requires the [gcloud CLI](https://cloud.google.com/sdk/docs/install).
|
||||
|
||||
```bash
|
||||
# log in and set default project
|
||||
gcloud auth login
|
||||
gcloud config set project myproject
|
||||
|
||||
# Create a keyring in your project
|
||||
gcloud kms keyrings create mykeyring --location global
|
||||
|
||||
# Create an Ed25519 signing key
|
||||
gcloud kms keys create mykey --default-algorithm=ec-sign-ed25519 --purpose=asymmetric-signing --keyring=mykeyring --location=global
|
||||
|
||||
# Enable Application Default Credentials (ADC) so the publisher tool can sign
|
||||
gcloud auth application-default login
|
||||
|
||||
# Attempt login to show the public key
|
||||
mcp-publisher login dns google-kms --domain=example.com --resource=projects/myproject/locations/global/keyRings/mykeyring/cryptoKeys/mykey/cryptoKeyVersions/1
|
||||
|
||||
# Copy the "Expected proof record" and add the TXT record
|
||||
# example.com. IN TXT "v=MCPv1; k=ed25519; p=PUBLIC_KEY"
|
||||
|
||||
# Re-run the login command
|
||||
mcp-publisher login dns google-kms --domain=example.com --resource=projects/myproject/locations/global/keyRings/mykeyring/cryptoKeys/mykey/cryptoKeyVersions/1
|
||||
```
|
||||
|
||||
**Setup:** (for Azure Key Vault signing)
|
||||
|
||||
This requires the [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest).
|
||||
|
||||
```bash
|
||||
# log in and set default subscription
|
||||
az login
|
||||
az account set --subscription "My Subscription (name or ID)"
|
||||
|
||||
# Create a resource group
|
||||
az group create --location westus --resource-group MyResourceGroup
|
||||
|
||||
# Create a Key Vault
|
||||
az keyvault create --name MyKeyVault --location westus --resource-group MyResourceGroup
|
||||
|
||||
# Create an ECDSA P-384 signing key
|
||||
az keyvault key create --name MyKey --vault-name MyKeyVault --curve P-384
|
||||
|
||||
# Attempt login to show the public key
|
||||
mcp-publisher login dns azure-key-vault --domain=example.com --vault MyKeyVault --key MyKey
|
||||
|
||||
# Copy the "Expected proof record" and add the TXT record
|
||||
# example.com. IN TXT "v=MCPv1; k=ecdsap384; p=PUBLIC_KEY"
|
||||
|
||||
# Re-run the login command
|
||||
mcp-publisher login dns azure-key-vault --domain=example.com --vault MyKeyVault --key MyKey
|
||||
```
|
||||
|
||||
#### HTTP Verification
|
||||
```bash
|
||||
mcp-publisher login http --domain=example.com --private-key=HEX_KEY [--registry=URL]
|
||||
|
|
@ -119,6 +176,7 @@ mcp-publisher login http --domain=example.com --private-key=HEX_KEY [--registry=
|
|||
- Verifies domain ownership via HTTPS endpoint
|
||||
- Grants access to `com.example.*` namespaces
|
||||
- Requires Ed25519 private key (64-character hex) or ECDSA P-384 private key (96-character hex)
|
||||
- The private key can be stored in a cloud signing provider like Google KMS or Azure Key Vault.
|
||||
|
||||
**Setup:** (for Ed25519, recommended)
|
||||
```bash
|
||||
|
|
@ -140,6 +198,8 @@ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -out key.pem
|
|||
# Content: v=MCPv1; k=ecdsap384; p=PUBLIC_KEY
|
||||
```
|
||||
|
||||
Cloud signing is also supported for HTTP authentication, similar to the DNS examples above. Just swap out the `dns` positional argument for `http`.
|
||||
|
||||
#### Anonymous (Testing)
|
||||
```bash
|
||||
mcp-publisher login none [--registry=URL]
|
||||
|
|
|
|||
36
go.mod
36
go.mod
|
|
@ -3,6 +3,9 @@ module github.com/modelcontextprotocol/registry
|
|||
go 1.24.6
|
||||
|
||||
require (
|
||||
cloud.google.com/go/kms v1.23.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0
|
||||
github.com/caarlos0/env/v11 v11.3.1
|
||||
github.com/coreos/go-oidc/v3 v3.16.0
|
||||
github.com/danielgtaylor/huma/v2 v2.34.1
|
||||
|
|
@ -24,6 +27,16 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.120.0 // indirect
|
||||
cloud.google.com/go/auth v0.16.4 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.8.0 // indirect
|
||||
cloud.google.com/go/iam v1.5.2 // indirect
|
||||
cloud.google.com/go/longrunning v0.6.7 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
|
||||
|
|
@ -31,19 +44,25 @@ require (
|
|||
github.com/docker/cli v28.2.2+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.3 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
|
|
@ -53,12 +72,21 @@ require (
|
|||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/vbatts/tar-split v0.12.1 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
golang.org/x/oauth2 v0.31.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
google.golang.org/api v0.247.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect
|
||||
google.golang.org/grpc v1.74.2 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
)
|
||||
|
|
|
|||
79
go.sum
79
go.sum
|
|
@ -1,3 +1,33 @@
|
|||
cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=
|
||||
cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=
|
||||
cloud.google.com/go/auth v0.16.4 h1:fXOAIQmkApVvcIn7Pc2+5J8QTMVbUGLscnSVNl11su8=
|
||||
cloud.google.com/go/auth v0.16.4/go.mod h1:j10ncYwjX/g3cdX7GpEzsdM+d+ZNsXAbb6qXA7p1Y5M=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA=
|
||||
cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=
|
||||
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
|
||||
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
|
||||
cloud.google.com/go/kms v1.23.0 h1:WaqAZsUptyHwOo9II8rFC1Kd2I+yvNsNP2IJ14H2sUw=
|
||||
cloud.google.com/go/kms v1.23.0/go.mod h1:rZ5kK0I7Kn9W4erhYVoIRPtpizjunlrfU4fUkumUp8g=
|
||||
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
|
||||
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 h1:5YTBM8QDVIBN3sxBil89WfdAAqDZbyJTgh688DSxX5w=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0 h1:wL5IEG5zb7BVv1Kv0Xm92orq+5hB5Nipn3B5tn4Rqfk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0/go.mod h1:J7MUC/wtRpfGVbQ5sIItY5/FuVWmvzlY21WAOfQnq/I=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 h1:E4MgwLBGeVB5f2MdcIVD3ELVAWpr+WD6MUe1i+tM/PA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0/go.mod h1:Y2b/1clN4zsAoUd/pgNAQHjLDnTis/6ROkUfyob6psM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 h1:XkkQbfMyuH2jTSjQjSoihryI8GINRcs4xp8lNawg0FI=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
|
||||
|
|
@ -19,6 +49,8 @@ github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBi
|
|||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
|
||||
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
|
|
@ -28,12 +60,20 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
|||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU=
|
||||
github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
|
||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
|
||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
|
|
@ -44,6 +84,8 @@ github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
|
|||
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
|
||||
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
|
|
@ -60,6 +102,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
|||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
|
@ -91,6 +135,10 @@ github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnn
|
|||
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
|
|
@ -109,19 +157,34 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
|||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo=
|
||||
golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
google.golang.org/api v0.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc=
|
||||
google.golang.org/api v0.247.0/go.mod h1:r1qZOPmxXffXg6xS5uhx16Fa/UFY8QU/K4bfKrnvovM=
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
|
||||
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
|
||||
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
|||
Loading…
Reference in a new issue