registry/cmd/publisher/auth/azurekeyvault/common.go
Radoslav Dimitrov 8759d9d8c0
ci: bump golangci-lint to v2.13.1 to fix panic on Go 1.27 (#1561)
CI is red on `main` and on every open PR (including the Dependabot PRs
#1531, #1552–#1554) with:

```
panic: file requires newer Go version go1.27 (application built with go1.26)
golangci-lint exit with code 2
```

## Cause

`actions/setup-go` with `go-version: 'stable'` started resolving to
**go1.27.0**. The pinned golangci-lint **v2.11.4** release binary is
built with **go1.26**, and its `go/types` refuses to typecheck source
files declaring a newer language version. Nothing in the failing PRs is
at fault.

## Fix

Bump to **golangci-lint v2.13.1**, whose release binaries are built with
go1.27, so linting works under `stable` again.

That bump also pulls in newer linter versions, which surface 57 findings
this tree did not have before. Handled as follows:

| Linter | Count | Resolution |
|---|---|---|
| `goconst` | 50 | 39 were repeated literals in `_test.go` files —
goconst started counting those in v2.12.0 (golangci-lint#6480 exposed
`ignore-tests`). Fixture data like `"1.0.0"` (89 occurrences) is not a
missing constant, so `goconst.ignore-tests: true`. The other 11 were
real duplication in production code (OpenAPI tags, the `bearer` security
scheme name, `$ref`, `http`/`https`) and became named constants. |
| `gosec` G710 | 1 | **True positive, fixed.** See below. |
| `staticcheck` SA1019 | 6 | Go 1.26 deprecated `ecdsa.PublicKey.X/Y`
and `PrivateKey.D`. Annotated with the same targeted `nolint` +
rationale already used for `ScalarBaseMult` in
`cmd/publisher/auth/common.go`. |

### The open redirect is real

`TrailingSlashMiddleware` copied the entire `*url.URL` before rewriting
the path. For an absolute-form request URI — legal for a Go HTTP server
to parse, e.g. `GET http://evil.com/foo/ HTTP/1.1` — `r.URL.Scheme` and
`r.URL.Host` are populated, and they were echoed straight back in the
`Location` header. The redirect target is now built from the cleaned
path and query alone. The existing `path.Clean` hardening for
GHSA-v8vw-gw5j-w7m6 (protocol-relative `//evil.com/`) is unchanged.

### Also

Switched `gomodguard` → `gomodguard_v2`; the former is deprecated as of
v2.12.0 and was emitting a warning on every run.

## Verification

- `golangci-lint v2.13.1` against this tree: **0 issues**, no
deprecation warnings
- `make validate`: all validations passed
- `go build ./...` and tests for every touched package pass (DB-backed
tests need Postgres; left to CI)

## Note on migrating the ecdsa key paths

Moving those six sites to `PublicKey.Bytes` /
`ParseUncompressedPublicKey` / `crypto/ecdh` touches key parsing and
signature verification. It should be its own reviewed PR rather than
riding along on a CI unblock. Happy to open one.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 00:14:13 +03:00

85 lines
2.5 KiB
Go

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), //nolint:staticcheck // SA1019: needs crypto/ecdh refactor
Y: new(big.Int).SetBytes(keyResp.Key.Y), //nolint:staticcheck // SA1019: needs crypto/ecdh refactor
})
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 &timestamp, signResp.Result, nil
}