mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-20 01:04:15 +00:00
Update every direct dependency across all five workspace modules to latest. Notable jumps: syft v1.43.0 -> v1.51.1, grype v0.111.1 -> v0.118.0, stereoscope v0.1.23 -> v0.3.1, indigo -> 2026-09-01, aws-sdk-go-v2/service/s3 v1.99.1 -> v1.110.0, grpc v1.80.0 -> v1.83.2, x/crypto v0.50.0 -> v0.55.0. Three deps needed more than a version bump: go-libipfs could not be updated at all. The repo was renamed to boxo, so every tag past v0.7.0 declares `module github.com/ipfs/boxo` and cannot be required under the old path. sqlite_store.go already imported go-block-format alongside it and used the archived package exactly once, inside a function already returning blockformat.Block, so it was relying on structural interface satisfaction. Collapsing to the native type drops the archived dependency entirely. go-didplc moved its package from the repo root into a didplc/ subdir in v0.2.2. Package name is unchanged and every symbol we use (RegularOp, OpEnum, OpService, Client.DirectoryURL, Submit) is intact, so this is an import path change only. The go-diskfs replace in scanner/go.mod had inverted. It pinned v1.7.0 because syft v1.43 passed diskfs entries as os.FileInfo; syft v1.51.1 fixed that upstream and now requires v1.9.4, so the workaround had become the thing breaking the build. Removed per its own "Remove when syft ships a fix" note, closing anchore/syft#4796 for us. The indigo bump needed no code changes: of the 21 packages we import only 5 changed, and the repo/MST/CAR-store core is byte-identical. It does bring a util/ssrf fix blocking 6to4 addresses (2002::/16), which we inherit through atproto/auth/oauth. Go 1.26.7 across go.work, all five go.mod files, the four Dockerfiles, the three tangled workflows, and the stale references in docs/DEVELOPMENT.md. Verified golang:1.26.7-trixie resolves on mirror.gcr.io, which is what the Dockerfiles actually pull from. Makefile's TRIXIE_BUILDER_IMAGE stays on the floating golang:1-trixie. make test, make lint, and make test-race all pass, as do the scanner module's tests and the integration-tagged build. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KWoKzpgtBJ33sCyGxJGR7x
168 lines
5.3 KiB
Go
168 lines
5.3 KiB
Go
package did
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"slices"
|
|
|
|
"github.com/bluesky-social/indigo/atproto/atcrypto"
|
|
didplc "github.com/did-method-plc/go-didplc/didplc"
|
|
)
|
|
|
|
// CreateIdentity builds a genesis PLC operation with the configured verification key and
|
|
// services, signs it with the rotation key, and submits it to the PLC directory.
|
|
func CreateIdentity(ctx context.Context, rotationKey atcrypto.PrivateKey, signingKey *atcrypto.PrivateKeyK256, cfg Config) (string, error) {
|
|
rotPub, err := rotationKey.PublicKey()
|
|
if err != nil {
|
|
return "", fmt.Errorf("did: failed to get rotation public key: %w", err)
|
|
}
|
|
sigPub, err := signingKey.PublicKey()
|
|
if err != nil {
|
|
return "", fmt.Errorf("did: failed to get signing public key: %w", err)
|
|
}
|
|
|
|
host, err := hostWithPort(cfg.PublicURL)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
op := &didplc.RegularOp{
|
|
Type: "plc_operation",
|
|
RotationKeys: []string{rotPub.DIDKey()},
|
|
VerificationMethods: map[string]string{
|
|
cfg.VerificationKeyName: sigPub.DIDKey(),
|
|
},
|
|
AlsoKnownAs: []string{"at://" + host},
|
|
Services: toOpServices(cfg.Services),
|
|
Prev: nil,
|
|
}
|
|
if err := op.Sign(rotationKey); err != nil {
|
|
return "", fmt.Errorf("did: failed to sign genesis operation: %w", err)
|
|
}
|
|
|
|
d, err := op.DID()
|
|
if err != nil {
|
|
return "", fmt.Errorf("did: failed to compute DID from genesis: %w", err)
|
|
}
|
|
|
|
client := &didplc.Client{DirectoryURL: cfg.PLCDirectoryURL}
|
|
if err := client.Submit(ctx, d, op); err != nil {
|
|
return "", fmt.Errorf("did: failed to submit genesis operation: %w", err)
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
// EnsureCurrent reconciles the published DID document with the local config; if the local
|
|
// signing key, public URL, or service set differs, an update operation is signed and submitted.
|
|
// Without a rotation key, mismatches log a warning but are not fatal.
|
|
func EnsureCurrent(ctx context.Context, did string, rotationKey atcrypto.PrivateKey, signingKey *atcrypto.PrivateKeyK256, cfg Config) error {
|
|
client := &didplc.Client{DirectoryURL: cfg.PLCDirectoryURL}
|
|
|
|
opLog, err := client.OpLog(ctx, did)
|
|
if err != nil {
|
|
return fmt.Errorf("did: failed to fetch op log for %s: %w", did, err)
|
|
}
|
|
if len(opLog) == 0 {
|
|
return fmt.Errorf("did: empty op log for %s", did)
|
|
}
|
|
lastEntry := opLog[len(opLog)-1]
|
|
lastOp := lastEntry.Regular
|
|
if lastOp == nil {
|
|
slog.Warn("Last PLC operation is not a regular op, skipping auto-update", "did", did)
|
|
return nil
|
|
}
|
|
|
|
sigPub, err := signingKey.PublicKey()
|
|
if err != nil {
|
|
return fmt.Errorf("did: failed to get signing public key: %w", err)
|
|
}
|
|
localKey := sigPub.DIDKey()
|
|
plcKey := lastOp.VerificationMethods[cfg.VerificationKeyName]
|
|
keyMatch := localKey == plcKey
|
|
|
|
servicesMatch := true
|
|
for name, svc := range cfg.Services {
|
|
plcSvc, ok := lastOp.Services[name]
|
|
if !ok || plcSvc.Type != svc.Type || plcSvc.Endpoint != svc.Endpoint {
|
|
servicesMatch = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if keyMatch && servicesMatch {
|
|
slog.Info("PLC identity is current", "did", did)
|
|
return nil
|
|
}
|
|
|
|
slog.Info("PLC identity needs update",
|
|
"did", did, "signing_key_changed", !keyMatch, "services_changed", !servicesMatch)
|
|
|
|
if rotationKey == nil {
|
|
slog.Warn("PLC document doesn't match local state but no rotation key available. Provide rotation key to auto-update.",
|
|
"did", did, "signing_key_changed", !keyMatch, "services_changed", !servicesMatch)
|
|
return nil
|
|
}
|
|
|
|
rotPub, err := rotationKey.PublicKey()
|
|
if err != nil {
|
|
return fmt.Errorf("did: failed to get rotation public key: %w", err)
|
|
}
|
|
localRotKey := rotPub.DIDKey()
|
|
|
|
// Verify the local rotation key still has authority on the PLC document.
|
|
// If it's been rotated out (possibly maliciously), refuse to submit — PLC would
|
|
// reject anyway, and silent failure here would be confusing.
|
|
localRotKeyPresent := slices.Contains(lastOp.RotationKeys, localRotKey)
|
|
if !localRotKeyPresent {
|
|
slog.Warn("Local rotation key not present in PLC document — refusing to update. Possible compromise or out-of-band rotation. Recover with offline key if available.",
|
|
"did", did, "local_rotation_key", localRotKey, "plc_rotation_keys", lastOp.RotationKeys)
|
|
return nil
|
|
}
|
|
|
|
host, err := hostWithPort(cfg.PublicURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
prevCID := lastEntry.AsOperation().CID().String()
|
|
|
|
op := &didplc.RegularOp{
|
|
Type: "plc_operation",
|
|
RotationKeys: lastOp.RotationKeys,
|
|
VerificationMethods: map[string]string{
|
|
cfg.VerificationKeyName: localKey,
|
|
},
|
|
AlsoKnownAs: []string{"at://" + host},
|
|
Services: toOpServices(cfg.Services),
|
|
Prev: &prevCID,
|
|
}
|
|
if err := op.Sign(rotationKey); err != nil {
|
|
return fmt.Errorf("did: failed to sign update operation: %w", err)
|
|
}
|
|
if err := client.Submit(ctx, did, op); err != nil {
|
|
return fmt.Errorf("did: failed to submit update: %w", err)
|
|
}
|
|
slog.Info("Updated PLC identity",
|
|
"did", did, "signing_key_rotated", !keyMatch, "services_changed", !servicesMatch)
|
|
return nil
|
|
}
|
|
|
|
func toOpServices(in map[string]Service) map[string]didplc.OpService {
|
|
out := make(map[string]didplc.OpService, len(in))
|
|
for name, svc := range in {
|
|
out[name] = didplc.OpService{Type: svc.Type, Endpoint: svc.Endpoint}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func parseOptionalMultibaseKey(encoded string) (atcrypto.PrivateKeyExportable, error) {
|
|
if encoded == "" {
|
|
return nil, nil
|
|
}
|
|
key, err := atcrypto.ParsePrivateMultibase(encoded)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("did: failed to parse multibase key: %w", err)
|
|
}
|
|
return key, nil
|
|
}
|